diff --git a/Qrakhen.TilingFrames/Controls/DragAndDrop/DragAndDropControl.cs b/Qrakhen.TilingFrames/Controls/DragAndDrop/DragAndDropControl.cs
index 4f1b3c4..ae04508 100644
--- a/Qrakhen.TilingFrames/Controls/DragAndDrop/DragAndDropControl.cs
+++ b/Qrakhen.TilingFrames/Controls/DragAndDrop/DragAndDropControl.cs
@@ -2,97 +2,255 @@
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
+using System.Xml.Linq;
-namespace Qrakhen.TilingFrames.Controls.DragAndDrop;
+namespace CopaData.FileInspector.GUI.TilingPanels.Controls.DragAndDrop;
-public abstract partial class DragAndDropControl : Control
+///
+/// Used to handle drag and drop callbacks via the abstract methods and .
+/// Can be used as a parent to any amount of s that refer to the their handler via .
+/// If no Handler is provided, the element itself will be considered its own handler, as long as it is a .
+/// In order to make an element draggable, set to true within the element's XAML attributes.
+/// To mark a FrameworkElement as a drop zone, set to true.
+/// If a draggable element is dropped on a drop zone, the handler's is called.
+///
+public abstract class DragAndDropControl : Control
{
- public virtual void OnDragStart(object model, UIElement draggedElement) { }
+ ///
+ /// Called when a DragDrop operation starts.
+ ///
+ protected virtual void OnElementDragStart(UIElement draggedElement) { }
- public virtual void OnDragEnd(object model, UIElement targetElement) { }
+ ///
+ /// Called during a DragDrop operation in order to update the cursor, if needed.
+ ///
+ protected virtual void OnElementDragGiveCursorFeedback(object sender, GiveFeedbackEventArgs args) { }
- public virtual void OnDrag(object sender, MouseEventArgs args) { }
+ ///
+ /// Called during a DragDrop operation.
+ ///
+ protected virtual void OnElementDrag(UIElement draggedElement, MouseEventArgs args) { }
- #region Dependency Properties
+ ///
+ /// Called when an element was dropped without being on an element marked as a DropZone.
+ ///
+ ///
+ protected virtual void OnElementDragStop(UIElement draggedElement) { }
- public bool AllowDrag {
- get => (bool)GetValue(AllowDragProperty);
- set => SetValue(AllowDragProperty, value);
- }
+ ///
+ /// Called when this element is the potential target of a DragDrop operation.
+ ///
+ protected virtual void OnElementDragOver(DragAndDropData data, UIElement targetElement) { }
- public static DependencyProperty AllowDragProperty = DependencyProperty
- .Register(
- nameof(AllowDrag),
- typeof(bool),
- typeof(DragAndDropControl),
- new PropertyMetadata(true));
+ ///
+ /// Called when this element stops being a potential target of a DragDrop operation.
+ ///
+ protected virtual void OnElementDragLeave(UIElement targetElement) { }
- #endregion
+ ///
+ /// Required callback to handle ,
+ /// provided with containing both the source element being dragged and it's original data context.
+ ///
+ protected abstract void OnElementDropped(DragAndDropData data, UIElement targetElement);
- #region Attached Properties
+ #region Attached Properties
- public static readonly DependencyProperty IsDraggableProperty = DependencyProperty
+ /* === IsDraggable === */
+
+ public static readonly DependencyProperty IsDraggableProperty = DependencyProperty
.RegisterAttached(
"IsDraggable",
typeof(bool),
typeof(DragAndDropControl),
new PropertyMetadata(false, OnIsDraggableChanged));
- public static bool GetIsDraggable(DependencyObject obj) => (bool)obj.GetValue(IsDraggableProperty);
- public static void SetIsDraggable(DependencyObject obj, bool value) => obj.SetValue(IsDraggableProperty, value);
+ public static bool GetIsDraggable(DependencyObject obj) => (bool)obj.GetValue(IsDraggableProperty);
+ public static void SetIsDraggable(DependencyObject obj, bool value) => obj.SetValue(IsDraggableProperty, value);
- private static void OnIsDraggableChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
- {
- if (sender is UIElement element && (bool)e.NewValue) {
+ ///
+ /// Adds event listeners to the element's GiveFeedback, MouseLeftButtonUp and MouseMove events.
+ ///
+ private static void OnIsDraggableChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
+ {
+ if (sender is not FrameworkElement element)
+ {
+ throw new InvalidOperationException($"IsDraggable may only be attached to FrameworkElements, got {sender.GetType()} instead.");
+ }
- }
- }
+ if ((bool)e.NewValue)
+ {
+ element.GiveFeedback += HandleGiveFeedback;
+ element.MouseLeftButtonUp += HandleLeftMouseButtonUp;
+ element.MouseMove += HandleMouseMove;
+ }
+ else
+ {
+ element.GiveFeedback -= HandleGiveFeedback;
+ element.MouseLeftButtonUp -= HandleLeftMouseButtonUp;
+ element.MouseMove -= HandleMouseMove;
+ }
+ }
- private static void HandleDragStart(object sender, MouseButtonEventArgs args, DragAndDropControl dndControl)
- {
- if (sender is UIElement element) {
+ /* === IsDropZone === */
- }
- }
+ public static readonly DependencyProperty IsDropZoneProperty = DependencyProperty
+ .RegisterAttached(
+ "IsDropZone",
+ typeof(bool),
+ typeof(DragAndDropControl),
+ new PropertyMetadata(false, OnIsDropZoneChanged));
- private static void HandleDrop(object sender, MouseButtonEventArgs args, DragAndDropControl dndControl)
- {
- // moi schaun
- }
+ public static bool GetIsDropZone(DependencyObject obj) => (bool)obj.GetValue(IsDropZoneProperty);
+ public static void SetIsDropZone(DependencyObject obj, bool value) => obj.SetValue(IsDropZoneProperty, value);
- private static T? FindVisualParent(DependencyObject child) where T : DependencyObject
- {
- // Todo: find a better solution to this, if there even is one.
- // There are, in fact, many controls even in the WPF standard that do similar lookups,
- // so I assume it can't be _that_ bad, but every clean solution kind of seems to need some ugly spot in it. :(
+ ///
+ /// Adds an event listener to this element's event.
+ ///
+ private static void OnIsDropZoneChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
+ {
+ if (sender is not FrameworkElement element)
+ {
+ throw new InvalidOperationException($"IsDraggable may only be attached to FrameworkElements, got {sender.GetType()} instead.");
+ }
- DependencyObject parent = VisualTreeHelper.GetParent(child);
- while (parent != null) {
- if (parent is T expected)
- return expected;
- parent = VisualTreeHelper.GetParent(parent);
- }
- return null;
- }
+ if ((bool)e.NewValue)
+ {
+ element.AllowDrop = true;
+ element.DragOver += HandleDragOver;
+ element.DragLeave += HandleDragLeave;
+ element.Drop += HandleDrop;
+ }
+ else
+ {
+ element.DragOver -= HandleDragOver;
+ element.DragLeave -= HandleDragLeave;
+ element.Drop -= HandleDrop;
+ }
+ }
- public static readonly DependencyProperty HandlerProperty = DependencyProperty
+ /* === Handler === */
+
+ public static readonly DependencyProperty HandlerProperty = DependencyProperty
.RegisterAttached(
"Handler",
typeof(DependencyObject),
typeof(DragAndDropControl),
new PropertyMetadata(null, OnHandlerChanged));
- public static DependencyObject GetHandler(DependencyObject obj) => (DependencyObject)obj.GetValue(HandlerProperty);
- public static void SetHandler(DependencyObject obj, bool value) => obj.SetValue(HandlerProperty, value);
-
- private static void OnHandlerChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
- {
- if (sender is UIElement element && e.NewValue is DragAndDropControl dndControl) {
-
- }
+ public static DependencyObject GetHandler(DependencyObject obj) => (DependencyObject)obj.GetValue(HandlerProperty);
+ public static void SetHandler(DependencyObject obj, bool value) => obj.SetValue(HandlerProperty, value);
+ private static void OnHandlerChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
+ {
+ // Simple validation
+ if (sender is not UIElement element || e.NewValue is not DragAndDropControl dndControl)
+ {
throw new InvalidOperationException($"Can not set {e.NewValue} as handler. Handler needs to be of type DragAndDropControl.");
- }
+ }
+ }
- #endregion
+ #endregion
+
+ ///
+ /// Attempts to retrieve the correct handler for the provided element.
+ /// This may either be via the defined Handler property, or the element itself if no handler property was attached.
+ ///
+ private static DragAndDropControl RetrieveHandler(DependencyObject obj)
+ {
+ if (obj.GetValue(HandlerProperty) is not DragAndDropControl handler)
+ {
+ if (obj is DragAndDropControl)
+ {
+ handler = (DragAndDropControl)obj;
+ }
+ else // Todo: last resort if ((handler = FindVisualParent(obj)!) == null)
+ {
+ throw new InvalidOperationException($"Could not find a DragAndDropControl for element {obj}. You may explicitly provide one using the DragAndDropControl.Handler attached property.");
+ }
+ }
+
+ return handler;
+ }
+
+ ///
+ /// Dynamic handler for the event.
+ /// Called when the mouse cursor moves while on an element.
+ /// This also calls the method in order to handle any specific behaviour during the operation.
+ ///
+ private static void HandleMouseMove(object sender, MouseEventArgs args)
+ {
+ if (args.LeftButton == MouseButtonState.Pressed)
+ {
+ FrameworkElement element = (FrameworkElement)sender;
+ DragAndDropControl handler = RetrieveHandler(element);
+ DragAndDropData data = new(element, element.DataContext);
+ DragDrop.DoDragDrop(element, data, DragDropEffects.All);
+ handler.OnElementDrag(element, args);
+ }
+ }
+
+ ///
+ /// Dynamic handler for the event.
+ ///
+ private static void HandleGiveFeedback(object sender, GiveFeedbackEventArgs args)
+ {
+ FrameworkElement element = (FrameworkElement)sender;
+ DragAndDropControl handler = RetrieveHandler(element);
+ handler.OnElementDragGiveCursorFeedback(element, args);
+ }
+
+ ///
+ /// Dynamic handler for the event.
+ ///
+ private static void HandleLeftMouseButtonUp(object sender, MouseButtonEventArgs args)
+ {
+ FrameworkElement element = (FrameworkElement)sender;
+ DragAndDropControl handler = RetrieveHandler(element);
+ handler.OnElementDragStop(element);
+ }
+
+ ///
+ /// Dynamic handler for the event.
+ ///
+ private static void HandleDragOver(object sender, DragEventArgs args)
+ {
+ FrameworkElement element = (FrameworkElement)sender;
+ DragAndDropControl handler = RetrieveHandler(element);
+ handler.OnElementDragOver((DragAndDropData)args.Data.GetData(typeof(DragAndDropData)), element);
+ }
+
+ ///
+ /// Dynamic handler for the event.
+ ///
+ private static void HandleDragLeave(object sender, DragEventArgs args)
+ {
+ FrameworkElement element = (FrameworkElement)sender;
+ DragAndDropControl handler = RetrieveHandler(element);
+ handler.OnElementDragLeave(element);
+ }
+
+ ///
+ /// Dynamic handler for the event.
+ ///
+ private static void HandleDrop(object sender, DragEventArgs args)
+ {
+ FrameworkElement element = (FrameworkElement)sender;
+ DragAndDropControl handler = RetrieveHandler(element);
+ handler.OnElementDropped((DragAndDropData)args.Data.GetData(typeof(DragAndDropData)), element);
+ }
+
+ private static T? FindVisualParent(DependencyObject child) where T : DependencyObject
+ {
+ // Todo: find a better solution to this, if there even is one.
+ // There are, in fact, many controls even in the WPF standard that do similar lookups,
+ // so I assume it can't be _that_ bad, but every clean solution kind of seems to need some ugly spot in it. :(
+
+ DependencyObject parent = VisualTreeHelper.GetParent(child);
+ while (parent != null)
+ {
+ if (parent is T expected) return expected;
+ parent = VisualTreeHelper.GetParent(parent);
+ }
+ return null;
+ }
}
diff --git a/Qrakhen.TilingFrames/Controls/DragAndDrop/DragAndDropData.cs b/Qrakhen.TilingFrames/Controls/DragAndDrop/DragAndDropData.cs
new file mode 100644
index 0000000..6717f42
--- /dev/null
+++ b/Qrakhen.TilingFrames/Controls/DragAndDrop/DragAndDropData.cs
@@ -0,0 +1,15 @@
+using System.Windows;
+
+namespace CopaData.FileInspector.GUI.TilingPanels.Controls.DragAndDrop;
+
+public class DragAndDropData
+{
+ public FrameworkElement DraggedElement { get; }
+ public object DataModel { get; }
+
+ public DragAndDropData(FrameworkElement draggedElement, object dataModel)
+ {
+ DraggedElement = draggedElement;
+ DataModel = dataModel;
+ }
+}
diff --git a/Qrakhen.TilingFrames/Controls/DropArea/DropArea.cs b/Qrakhen.TilingFrames/Controls/DropArea/DropArea.cs
index 57d8e5a..7f3dcbd 100644
--- a/Qrakhen.TilingFrames/Controls/DropArea/DropArea.cs
+++ b/Qrakhen.TilingFrames/Controls/DropArea/DropArea.cs
@@ -1,9 +1,17 @@
-using Qrakhen.TilingFrames.Controls.DragAndDrop;
+using System.Windows;
using System.Windows.Controls;
-namespace Qrakhen.TilingFrames.Controls.DropArea;
+namespace CopaData.FileInspector.GUI.TilingPanels.Controls.DropArea;
-public class DropArea : DragAndDropControl
+public class DropArea : Control
{
+ public static readonly DependencyProperty DropDecisionProperty = DependencyProperty
+ .RegisterAttached(
+ "Decision",
+ typeof(DropDecision),
+ typeof(DropArea));
+
+ public static DropDecision GetDropDecision(DependencyObject obj) => (DropDecision)obj.GetValue(DropDecisionProperty);
+ public static void SetDropDecision(DependencyObject obj, bool value) => obj.SetValue(DropDecisionProperty, value);
}
diff --git a/Qrakhen.TilingFrames/Controls/DropArea/DropAreaDecisionEvent.cs b/Qrakhen.TilingFrames/Controls/DropArea/DropAreaDecisionEvent.cs
index 58f66f1..dc0686e 100644
--- a/Qrakhen.TilingFrames/Controls/DropArea/DropAreaDecisionEvent.cs
+++ b/Qrakhen.TilingFrames/Controls/DropArea/DropAreaDecisionEvent.cs
@@ -1,3 +1,3 @@
-namespace Qrakhen.TilingFrames.Controls.DropArea;
+namespace CopaData.FileInspector.GUI.TilingPanels.Controls.DropArea;
-public delegate void DropAreaDecisionEvent(DropArea sender, HostControl host, DropDecision decision);
+public delegate void DropAreaDecisionEvent(DropArea sender, TilingHostControl host, DropDecision decision);
diff --git a/Qrakhen.TilingFrames/Controls/DropArea/DropDecision.cs b/Qrakhen.TilingFrames/Controls/DropArea/DropDecision.cs
index b367311..99b9b2d 100644
--- a/Qrakhen.TilingFrames/Controls/DropArea/DropDecision.cs
+++ b/Qrakhen.TilingFrames/Controls/DropArea/DropDecision.cs
@@ -1,11 +1,11 @@
-namespace Qrakhen.TilingFrames.Controls.DropArea;
+namespace CopaData.FileInspector.GUI.TilingPanels.Controls.DropArea;
public enum DropDecision
{
- Cancel = 0,
- Center,
- Left,
- Right,
- Top,
- Bottom
+ Center = 0,
+ Left = 1,
+ Top = 2,
+ Right = 3,
+ Bottom = 4,
+ Cancel = 5
}
\ No newline at end of file
diff --git a/Qrakhen.TilingFrames/Controls/TilingGridAttachment.cs b/Qrakhen.TilingFrames/Controls/TilingGridAttachment.cs
index 82c8131..d86b1c1 100644
--- a/Qrakhen.TilingFrames/Controls/TilingGridAttachment.cs
+++ b/Qrakhen.TilingFrames/Controls/TilingGridAttachment.cs
@@ -1,55 +1,57 @@
using System.Windows;
using System.Windows.Controls;
-namespace Qrakhen.TilingFrames.Controls
+namespace CopaData.FileInspector.GUI.TilingPanels.Controls
{
- ///
- /// Used to force-update columns into rows when orientation is vertical
- ///
- public static class TilingGridAttachment
- {
- #region AlphaRow
- public static readonly DependencyProperty AlphaRowProperty =
+ ///
+ /// Used to force-update columns into rows when orientation is vertical
+ ///
+ public static class TilingGridAttachment
+ {
+ #region AlphaRow
+ public static readonly DependencyProperty AlphaRowProperty =
DependencyProperty.RegisterAttached(
"AlphaRow",
typeof(int),
typeof(TilingGridAttachment),
new PropertyMetadata(0, OnAlphaRowChanged));
- public static int GetAlphaRow(DependencyObject obj)
- => (int)obj.GetValue(AlphaRowProperty);
- public static void SetAlphaRow(DependencyObject obj, int value)
- => obj.SetValue(AlphaRowProperty, value);
+ public static int GetAlphaRow(DependencyObject obj)
+ => (int)obj.GetValue(AlphaRowProperty);
+ public static void SetAlphaRow(DependencyObject obj, int value)
+ => obj.SetValue(AlphaRowProperty, value);
- private static void OnAlphaRowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ private static void OnAlphaRowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is ContentPresenter presenter)
{
- if (d is ContentPresenter presenter) {
- Grid.SetRow(presenter, (int)e.NewValue);
- Grid.SetColumn(presenter, 0); // Always Column 0 for vertical split
- }
+ Grid.SetRow(presenter, (int)e.NewValue);
+ Grid.SetColumn(presenter, 0); // Always Column 0 for vertical split
}
- #endregion
+ }
+ #endregion
- #region BetaRow
- public static readonly DependencyProperty BetaRowProperty =
+ #region BetaRow
+ public static readonly DependencyProperty BetaRowProperty =
DependencyProperty.RegisterAttached(
"BetaRow",
typeof(int),
typeof(TilingGridAttachment),
new PropertyMetadata(0, OnBetaRowChanged));
- public static int GetBetaRow(DependencyObject obj)
- => (int)obj.GetValue(BetaRowProperty);
- public static void SetBetaRow(DependencyObject obj, int value)
- => obj.SetValue(BetaRowProperty, value);
+ public static int GetBetaRow(DependencyObject obj)
+ => (int)obj.GetValue(BetaRowProperty);
+ public static void SetBetaRow(DependencyObject obj, int value)
+ => obj.SetValue(BetaRowProperty, value);
- private static void OnBetaRowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ private static void OnBetaRowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is ContentPresenter presenter)
{
- if (d is ContentPresenter presenter) {
- Grid.SetRow(presenter, (int)e.NewValue);
- Grid.SetColumn(presenter, 0); // Always Column 0 for vertical split
- }
+ Grid.SetRow(presenter, (int)e.NewValue);
+ Grid.SetColumn(presenter, 0); // Always Column 0 for vertical split
}
- #endregion
- }
+ }
+ #endregion
+ }
}
diff --git a/Qrakhen.TilingFrames/Controls/TilingHostControl.cs b/Qrakhen.TilingFrames/Controls/TilingHostControl.cs
new file mode 100644
index 0000000..21b71f9
--- /dev/null
+++ b/Qrakhen.TilingFrames/Controls/TilingHostControl.cs
@@ -0,0 +1,74 @@
+using System.Windows;
+using System.Windows.Controls;
+using CopaData.FileInspector.GUI.TilingPanels.Controls.DragAndDrop;
+using CopaData.FileInspector.GUI.TilingPanels.Controls.DropArea;
+using CopaData.FileInspector.GUI.TilingPanels.Models;
+using CopaData.FileInspector.GUI.ViewModels;
+
+namespace CopaData.FileInspector.GUI.TilingPanels.Controls;
+
+///
+/// Control that represents the state of a ,
+/// with all the necessary interaction logic tied to it.
+///
+public class TilingHostControl : DragAndDropControl
+{
+ public TilingHost? Host => DataContext as TilingHost;
+
+ static TilingHostControl()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(
+ typeof(TilingHostControl),
+ new FrameworkPropertyMetadata(typeof(TilingHostControl)));
+ }
+
+ protected override void OnElementDragOver(DragAndDropData data, UIElement targetElement)
+ {
+ SetValue(IsDragTargetPropertyKey, true);
+ }
+
+ protected override void OnElementDragLeave(UIElement targetElement)
+ {
+ SetValue(IsDragTargetPropertyKey, false);
+ }
+
+ protected override void OnElementDropped(DragAndDropData data, UIElement targetElement)
+ {
+ if (data.DataModel is not TilingFrame frame)
+ {
+ // not for us
+ return;
+ }
+
+ DropDecision decision = (DropDecision)targetElement.GetValue(DropArea.DropArea.DropDecisionProperty);
+ if (decision == DropDecision.Cancel)
+ return;
+
+ TilingDirection direction = (TilingDirection)decision; // simple cast suffices here
+ if (direction == TilingDirection.None) {
+ TilingHost.Insert(TilingManagerViewModel.GlobalRoot, Host!, frame);
+ } else {
+ TilingPanel.Attach(TilingManagerViewModel.GlobalRoot, Host!, frame, direction);
+ }
+ }
+
+ #region Dependency Properties
+
+ ///
+ private static readonly DependencyPropertyKey IsDragTargetPropertyKey =
+ DependencyProperty.RegisterReadOnly(
+ nameof(IsDragTarget),
+ typeof(bool),
+ typeof(TilingHostControl),
+ new PropertyMetadata(false));
+
+ ///
+ public static readonly DependencyProperty IsDragTargetProperty = IsDragTargetPropertyKey.DependencyProperty;
+
+ ///
+ /// Whether this is being a potential drop target at the moment.
+ ///
+ public bool IsDragTarget => (bool)GetValue(IsDragTargetProperty);
+
+ #endregion
+}
diff --git a/Qrakhen.TilingFrames/Controls/TilingHostFrameControl.cs b/Qrakhen.TilingFrames/Controls/TilingHostFrameControl.cs
new file mode 100644
index 0000000..969b149
--- /dev/null
+++ b/Qrakhen.TilingFrames/Controls/TilingHostFrameControl.cs
@@ -0,0 +1,40 @@
+using System.Windows;
+using System.Windows.Controls;
+using CopaData.FileInspector.GUI.TilingPanels.Models;
+
+namespace CopaData.FileInspector.GUI.TilingPanels.Controls;
+
+///
+/// Control that represents the state of a ,
+/// with all the necessary interaction logic tied to it.
+///
+public class TilingHostFrameControl : ContentControl
+{
+ static TilingHostFrameControl()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(
+ typeof(TilingHostFrameControl),
+ new FrameworkPropertyMetadata(typeof(TilingHostFrameControl)));
+ }
+
+ public TilingHostFrameControl()
+ {
+
+ }
+
+ #region DependencyProperties
+
+ public TilingFrame Frame
+ {
+ get => (TilingFrame)GetValue(FrameProperty);
+ set => SetValue(FrameProperty, value);
+ }
+
+ public static DependencyProperty FrameProperty = DependencyProperty
+ .Register(nameof(Frame),
+ typeof(TilingFrame),
+ typeof(TilingHostFrameControl),
+ new PropertyMetadata(null));
+
+ #endregion
+}
diff --git a/Qrakhen.TilingFrames/Controls/TilingPanelControl.cs b/Qrakhen.TilingFrames/Controls/TilingPanelControl.cs
index ccd2d69..7c44903 100644
--- a/Qrakhen.TilingFrames/Controls/TilingPanelControl.cs
+++ b/Qrakhen.TilingFrames/Controls/TilingPanelControl.cs
@@ -1,69 +1,55 @@
-using Qrakhen.TilingFrames.Models;
+using CopaData.FileInspector.GUI.TilingPanels.Models;
using System.Windows;
using System.Windows.Controls;
-namespace Qrakhen.TilingFrames.Controls;
-
-///
-/// For Pop-Outs etc.
-///
-public class TilingWindowControl : Window
-{
- static TilingWindowControl()
- {
- DefaultStyleKeyProperty.OverrideMetadata(
- typeof(TilingWindowControl),
- new FrameworkPropertyMetadata(typeof(TilingWindowControl)));
- }
-
- public TilingPanel Root {
- get => (TilingPanel)GetValue(RootProperty);
- set => SetValue(RootProperty, value);
- }
-
- public static DependencyProperty RootProperty = DependencyProperty
- .Register(
- nameof(Root),
- typeof(TilingPanel),
- typeof(TilingWindowControl));
-}
-
-public class TilingRootControl : TilingPanelControl
-{
- static TilingRootControl()
- {
- DefaultStyleKeyProperty.OverrideMetadata(
- typeof(TilingRootControl),
- new FrameworkPropertyMetadata(typeof(TilingRootControl)));
- }
-}
+namespace CopaData.FileInspector.GUI.TilingPanels.Controls;
public class TilingPanelControl : Control
{
- static TilingPanelControl()
- {
- DefaultStyleKeyProperty.OverrideMetadata(
- typeof(TilingPanelControl),
- new FrameworkPropertyMetadata(typeof(TilingPanelControl)));
- }
+ static TilingPanelControl()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(
+ typeof(TilingPanelControl),
+ new FrameworkPropertyMetadata(typeof(TilingPanelControl)));
+ }
+
+ public TilingPanelControl()
+ {
+
+ }
+
+ #region DependencyProperties
+
+ public Orientation SplitOrientation => Panel.SplitOrientation;
+
+ public TilingNode Alpha => Panel.Alpha;
+ public TilingNode? Beta => Panel.Beta;
+
+ public TilingPanel Panel
+ {
+ get => (TilingPanel)GetValue(PanelProperty);
+ set => SetValue(PanelProperty, value);
+ }
+
+ public static DependencyProperty PanelProperty = DependencyProperty
+ .Register(nameof(Panel),
+ typeof(TilingPanel),
+ typeof(TilingPanelControl),
+ new PropertyMetadata(null));
+
+ public double SplitterWidth
+ {
+ get => (double)GetValue(SplitterWidthProperty);
+ set => SetValue(SplitterWidthProperty, value);
+ }
+
+ public static DependencyProperty SplitterWidthProperty = DependencyProperty
+ .Register(
+ nameof(SplitterWidth),
+ typeof(double),
+ typeof(TilingPanelControl),
+ new PropertyMetadata(6.0));
+
+ #endregion
}
-public class TilingHostControl : Control
-{
- static TilingHostControl()
- {
- DefaultStyleKeyProperty.OverrideMetadata(
- typeof(TilingHostControl),
- new FrameworkPropertyMetadata(typeof(TilingHostControl)));
- }
-}
-
-public class TilingFrameControl : ContentControl
-{
- static TilingFrameControl()
- {
- DefaultStyleKeyProperty.OverrideMetadata(
- typeof(TilingFrameControl),
- new FrameworkPropertyMetadata(typeof(TilingFrameControl)));
- }
-}
\ No newline at end of file
diff --git a/Qrakhen.TilingFrames/Controls/TilingRootControl.cs b/Qrakhen.TilingFrames/Controls/TilingRootControl.cs
new file mode 100644
index 0000000..7d3fa8e
--- /dev/null
+++ b/Qrakhen.TilingFrames/Controls/TilingRootControl.cs
@@ -0,0 +1,45 @@
+using CopaData.FileInspector.GUI.TilingPanels.Models;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace CopaData.FileInspector.GUI.TilingPanels.Controls;
+
+public class TilingRootControl : Control
+{
+ static TilingRootControl()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(
+ typeof(TilingRootControl),
+ new FrameworkPropertyMetadata(typeof(TilingRootControl)));
+ }
+
+ #region Dependency Properties
+
+ public double SplitterWidth
+ {
+ get => (double)GetValue(SplitterWidthProperty);
+ set => SetValue(SplitterWidthProperty, value);
+ }
+
+ public static DependencyProperty SplitterWidthProperty = DependencyProperty
+ .Register(
+ nameof(SplitterWidth),
+ typeof(double),
+ typeof(TilingRootControl),
+ new PropertyMetadata(6.0));
+
+ public bool IsPrimaryRoot
+ {
+ get => (bool)GetValue(IsPrimaryRootProperty);
+ set => SetValue(IsPrimaryRootProperty, value);
+ }
+
+ public static DependencyProperty IsPrimaryRootProperty = DependencyProperty
+ .Register(
+ nameof(IsPrimaryRoot),
+ typeof(bool),
+ typeof(TilingRootControl),
+ new PropertyMetadata(true));
+
+ #endregion
+}
diff --git a/Qrakhen.TilingFrames/Controls/TilingWindow.cs b/Qrakhen.TilingFrames/Controls/TilingWindow.cs
new file mode 100644
index 0000000..e6e5e48
--- /dev/null
+++ b/Qrakhen.TilingFrames/Controls/TilingWindow.cs
@@ -0,0 +1,45 @@
+using System.Windows;
+
+namespace CopaData.FileInspector.GUI.TilingPanels.Controls;
+
+///
+/// A dedicated window for hosting its own , to be used for pop-out interactions.
+///
+public class TilingWindow : Window
+{
+ static TilingWindow()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(
+ typeof(TilingWindow),
+ new FrameworkPropertyMetadata(typeof(TilingWindow)));
+ }
+
+ #region Dependency Properties
+
+ public double SplitterWidth
+ {
+ get => (double)GetValue(SplitterWidthProperty);
+ set => SetValue(SplitterWidthProperty, value);
+ }
+
+ public static DependencyProperty SplitterWidthProperty = DependencyProperty
+ .Register(
+ nameof(SplitterWidth),
+ typeof(double),
+ typeof(TilingWindow),
+ new PropertyMetadata(6.0));
+
+ public TilingRootControl Root
+ {
+ get => (TilingRootControl)GetValue(RootProperty);
+ set => SetValue(RootProperty, value);
+ }
+
+ public static DependencyProperty RootProperty = DependencyProperty
+ .Register(
+ nameof(Root),
+ typeof(TilingRootControl),
+ typeof(TilingWindow));
+
+ #endregion
+}
diff --git a/Qrakhen.TilingFrames/Converters/AlphaRatioConverter.cs b/Qrakhen.TilingFrames/Converters/AlphaRatioConverter.cs
index ad23da8..f89feea 100644
--- a/Qrakhen.TilingFrames/Converters/AlphaRatioConverter.cs
+++ b/Qrakhen.TilingFrames/Converters/AlphaRatioConverter.cs
@@ -3,28 +3,45 @@ using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
-namespace Qrakhen.TilingFrames.Converters
+namespace CopaData.FileInspector.GUI.TilingPanels.Converters;
+
+///
+/// Converts a double between 0 and 1 to a star-length representation,
+/// to be used in conjunction with .
+///
+public class AlphaRatioConverter : MarkupExtension, IValueConverter
{
- public class AlphaRatioConverter : MarkupExtension, IValueConverter
- {
- public override object ProvideValue(IServiceProvider serviceProvider) => this;
+ public override object ProvideValue(IServiceProvider serviceProvider) => this;
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is double ratio)
+ {
+ if (ratio <= double.Epsilon)
{
- if (value is double ratio) {
- if (ratio <= double.Epsilon)
- return new GridLength(0, GridUnitType.Star);
- if (ratio <= .5)
- return new GridLength(1, GridUnitType.Star);
- else if (ratio >= 1)
- return new GridLength(1, GridUnitType.Star);
- return new GridLength((1 - ratio) / ratio, GridUnitType.Star);
- }
-
- throw new ArgumentException($"Expected value to be ratio (double), god {value} instead.");
+ // If a ratio near zero, alpha is essentially hidden.
+ return new GridLength(0, GridUnitType.Star);
}
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
- => throw new NotImplementedException();
- }
+ if (ratio <= .5)
+ {
+ // With a ratio less than or equal to .5, alpha will be calculated as the minor component
+ return new GridLength(1, GridUnitType.Star);
+ }
+
+ if (ratio >= 1)
+ {
+ // With a ratio near 1, alpha will be 1.
+ return new GridLength(1, GridUnitType.Star);
+ }
+
+ // Returns the parts-per-ratio that alpha represents
+ return new GridLength((1 - ratio) / ratio, GridUnitType.Star);
+ }
+
+ throw new ArgumentException($"Expected value to be ratio (double), god {value} instead.");
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ => throw new NotImplementedException();
}
diff --git a/Qrakhen.TilingFrames/Converters/BetaRatioConverter.cs b/Qrakhen.TilingFrames/Converters/BetaRatioConverter.cs
index 2e92fe1..be1dc40 100644
--- a/Qrakhen.TilingFrames/Converters/BetaRatioConverter.cs
+++ b/Qrakhen.TilingFrames/Converters/BetaRatioConverter.cs
@@ -3,28 +3,45 @@ using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
-namespace Qrakhen.TilingFrames.Converters
+namespace CopaData.FileInspector.GUI.TilingPanels.Converters;
+
+///
+/// Converts a double between 0 and 1 to a star-length representation,
+/// to be used in conjunction with .
+///
+public class BetaRatioConverter : MarkupExtension, IValueConverter
{
- public class BetaRatioConverter : MarkupExtension, IValueConverter
- {
- public override object ProvideValue(IServiceProvider serviceProvider) => this;
+ public override object ProvideValue(IServiceProvider serviceProvider) => this;
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is double ratio)
+ {
+ if (ratio >= 1)
{
- if (value is double ratio) {
- if (ratio >= 1)
- return new GridLength(0, GridUnitType.Star);
- if (ratio >= .5)
- return new GridLength(1, GridUnitType.Star);
- else if (ratio <= double.Epsilon)
- return new GridLength(1, GridUnitType.Star);
- return new GridLength((1 - ratio) / ratio, GridUnitType.Star);
- }
-
- throw new ArgumentException($"Expected value to be ratio (double), god {value} instead.");
+ // If near 1, beta is essentially hidden.
+ return new GridLength(0, GridUnitType.Star);
}
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
- => throw new NotImplementedException();
- }
+ if (ratio >= .5)
+ {
+ // With a ratio larger than or equal to 0.5, beta is the lesser component of the two.
+ return new GridLength(1, GridUnitType.Star);
+ }
+
+ if (ratio <= double.Epsilon)
+ {
+ // If near zero, beta covers the entire panel.
+ return new GridLength(1, GridUnitType.Star);
+ }
+
+ // Return the parts-per-ratio that beta represents.
+ return new GridLength((1 - ratio) / ratio, GridUnitType.Star);
+ }
+
+ throw new ArgumentException($"Expected value to be ratio (double), god {value} instead.");
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ => throw new NotImplementedException();
}
diff --git a/Qrakhen.TilingFrames/Models/EmptyFrame.cs b/Qrakhen.TilingFrames/Models/EmptyFrame.cs
index 60e0bae..f13ff84 100644
--- a/Qrakhen.TilingFrames/Models/EmptyFrame.cs
+++ b/Qrakhen.TilingFrames/Models/EmptyFrame.cs
@@ -1,17 +1,17 @@
-namespace Qrakhen.TilingFrames.Models;
+namespace CopaData.FileInspector.GUI.TilingPanels.Models;
-public class EmptyFrame : HostFrame
+///
+/// Initial empty frame, can be turned of by setting to false.
+///
+public class EmptyFrame : TilingFrame
{
- public EmptyFrame()
- {
- HeaderText = "Empty";
- }
+ public EmptyFrame()
+ {
+ HeaderText = "Empty";
+ }
}
-public class TestOverrideFrame : HostFrame
-{
- public TestOverrideFrame()
- {
- HeaderText = "Testerinho";
- }
-}
+///
+/// Todo: remove this later, only used to test the non-overriden case of a data template
+///
+public class TestOverrideFrame : TilingFrame { public TestOverrideFrame() { HeaderText = "Testerinho"; } }
diff --git a/Qrakhen.TilingFrames/Models/EmptyHost.cs b/Qrakhen.TilingFrames/Models/EmptyHost.cs
index 5f97b49..0f0e716 100644
--- a/Qrakhen.TilingFrames/Models/EmptyHost.cs
+++ b/Qrakhen.TilingFrames/Models/EmptyHost.cs
@@ -1,8 +1,8 @@
-namespace Qrakhen.TilingFrames.Models;
+namespace CopaData.FileInspector.GUI.TilingPanels.Models;
public class EmptyHost : TilingHost
{
- public EmptyHost() : base(new EmptyFrame(), new TestOverrideFrame())
- {
- }
+ public EmptyHost() : base(new EmptyFrame(), new TestOverrideFrame())
+ {
+ }
}
diff --git a/Qrakhen.TilingFrames/Models/FrameButtons.cs b/Qrakhen.TilingFrames/Models/FrameButtons.cs
index 4c4fa6e..0773fef 100644
--- a/Qrakhen.TilingFrames/Models/FrameButtons.cs
+++ b/Qrakhen.TilingFrames/Models/FrameButtons.cs
@@ -1,19 +1,19 @@
-namespace Qrakhen.TilingFrames.Models;
+namespace CopaData.FileInspector.GUI.TilingPanels.Models;
///
-/// Flags for declaring the buttons on a
+/// Flags for declaring the buttons on a
///
[Flags]
public enum FrameButtons
{
- None = 0,
- Close = 1 << 0,
- Minimize = 1 << 1,
- Pin = 1 << 2,
- Split = 1 << 3,
- PopOut = 1 << 4,
- Options = 1 << 5,
- Help = 1 << 6,
+ None = 0,
+ Close = 1 << 0,
+ Minimize = 1 << 1,
+ Pin = 1 << 2,
+ Split = 1 << 3,
+ PopOut = 1 << 4,
+ Options = 1 << 5,
+ Help = 1 << 6,
- Default = Close | Pin | PopOut
+ Default = Close | Pin | PopOut
}
diff --git a/Qrakhen.TilingFrames/Models/TilingDirection.cs b/Qrakhen.TilingFrames/Models/TilingDirection.cs
index 05fb647..1983b54 100644
--- a/Qrakhen.TilingFrames/Models/TilingDirection.cs
+++ b/Qrakhen.TilingFrames/Models/TilingDirection.cs
@@ -1,24 +1,25 @@
-namespace Qrakhen.TilingFrames.Models;
+namespace CopaData.FileInspector.GUI.TilingPanels.Models;
///
/// Used to declare where a new shall be placed when tiling.
///
public enum TilingDirection
{
- ///
- /// Right side of the target host, the new child becomes beta.
- ///
- Right = 0,
- ///
- /// The bottom of the target host, the new child becomes beta.
- ///
- Bottom = 1,
+ None = 0,
///
/// Left side of the target host, the new child becomes alpha and the target host moves to beta.
///
- Left = 2,
+ Left = 1,
///
/// The top of the target host, the new child becomes alpha and the target host moves to beta.
///
- Top = 3
+ Top = 2,
+ ///
+ /// Right side of the target host, the new child becomes beta.
+ ///
+ Right = 3,
+ ///
+ /// The bottom of the target host, the new child becomes beta.
+ ///
+ Bottom = 4
}
\ No newline at end of file
diff --git a/Qrakhen.TilingFrames/Models/TilingFrame.cs b/Qrakhen.TilingFrames/Models/TilingFrame.cs
new file mode 100644
index 0000000..13bb199
--- /dev/null
+++ b/Qrakhen.TilingFrames/Models/TilingFrame.cs
@@ -0,0 +1,60 @@
+using CopaData.FileInspector.GUI.Models;
+
+namespace CopaData.FileInspector.GUI.TilingPanels.Models;
+
+///
+/// Base data class for content that is displayed for a 's tabs or stand-alone pop-out windows.
+/// Anything that you can physically see from the root is a .
+/// Serving a header text, pin state and button configuration properties.
+///
+public abstract class TilingFrame : Observable
+{
+ ///
+ private string _headerText = string.Empty;
+ ///
+ /// The title of the tab that will be displayed inside the tab button.
+ ///
+ public string HeaderText
+ {
+ get => _headerText;
+ set => SetField(ref _headerText, value);
+ }
+
+ ///
+ private FrameButtons _frameButtons = FrameButtons.Default;
+ ///
+ /// The buttons to be displayed next to the header.
+ ///
+ public FrameButtons FrameButtons
+ {
+ get => _frameButtons;
+ set => SetField(ref _frameButtons, value);
+ }
+
+ ///
+ private bool _isPinned = false;
+ ///
+ /// The title of the tab that will be displayed inside the tab button.
+ ///
+ public bool IsPinned
+ {
+ get => _isPinned;
+ set => SetField(ref _isPinned, value);
+ }
+
+ ///
+ private TilingHost? _parent;
+ ///
+ /// The parent hosting this frame.
+ ///
+ public TilingHost? Parent
+ {
+ get => _parent;
+ set => SetField(ref _parent, value);
+ }
+
+ public virtual object? GetOptions() => null;
+ public virtual void SetOptions(object? options) { }
+
+ public virtual object? GetHelp() => null;
+}
diff --git a/Qrakhen.TilingFrames/Models/TilingHost.cs b/Qrakhen.TilingFrames/Models/TilingHost.cs
index 546d4bd..72b1341 100644
--- a/Qrakhen.TilingFrames/Models/TilingHost.cs
+++ b/Qrakhen.TilingFrames/Models/TilingHost.cs
@@ -2,7 +2,7 @@
using System.Collections.ObjectModel;
using System.Collections.Specialized;
-namespace Qrakhen.TilingFrames.Models;
+namespace CopaData.FileInspector.GUI.TilingPanels.Models;
///
/// Host node that by paradigm is located only at the very end of the tree's branches.
@@ -12,63 +12,83 @@ namespace Qrakhen.TilingFrames.Models;
///
public class TilingHost : TilingNode
{
- public ObservableCollection Frames { get; } = [];
+ public ObservableCollection Frames { get; } = [];
- private HostFrame? _activeFrame;
- public HostFrame? ActiveFrame {
- get => _activeFrame;
- set => SetField(ref _activeFrame, value);
- }
+ private TilingFrame? _activeFrame;
+ public TilingFrame? ActiveFrame
+ {
+ get => _activeFrame;
+ set => SetField(ref _activeFrame, value);
+ }
- public bool ShowTabs => Frames.Count > 1;
+ public bool ShowTabs => Frames.Count > 1;
- public bool IsEmpty => Frames.Count == 0;
+ public bool IsEmpty => Frames.Count == 0;
- public TilingHost(params HostFrame[] frames)
- {
- if (frames != null && frames.Length > 0) {
- Frames = new ObservableCollection(frames);
- ActiveFrame = Frames[^1];
+ public TilingHost(params TilingFrame[] frames)
+ {
+ if (frames != null && frames.Length > 0)
+ {
+ Frames = new ObservableCollection(frames);
+ ActiveFrame = Frames[^1];
+ foreach (var frame in Frames)
+ {
+ frame.Parent = this;
}
- Frames.CollectionChanged += OnTabsChanged;
- }
+ }
- private void OnTabsChanged(object? sender, NotifyCollectionChangedEventArgs e)
- {
- if (e.NewItems != null && e.NewItems.Count > 0) {
- // Make the last item active
- ActiveFrame = e.NewItems[^1] as HostFrame;
- } else if (e.OldItems != null && e.OldItems.Count > 0) {
- if (ActiveFrame != null && !Frames.Contains(ActiveFrame)) {
- // Previous active tab got removed, revert back to the most recent tab or set ActiveTab to null if tabs are empty.
- if (Frames.Count > 0) {
- ActiveFrame = Frames[^1];
- } else {
- ActiveFrame = null;
- }
- }
+ Frames.CollectionChanged += OnFramesItemsChanged;
+ }
+
+ private void OnFramesItemsChanged(object? sender, NotifyCollectionChangedEventArgs e)
+ {
+ if (e.NewItems != null && e.NewItems.Count > 0)
+ {
+ // Make the last item active
+ ActiveFrame = e.NewItems[^1] as TilingFrame;
+ foreach (var item in e.NewItems)
+ {
+ if (item is TilingFrame frame)
+ {
+ // We're using a reference here as this is the only place that always listens to updates.
+ frame.Parent = this;
+ }
}
- }
-
- ///
- /// Inserts into as a new tab
- /// after being dropped in the center region by the user.
- /// If the previous host only had one frame inside it, it will be detached from its parent.
- /// Otherwise, only the frame will be removed and inserted into 's frames.
- ///
- public static void InsertHost(TilingPanel rootPanel, TilingHost targetHost, TilingHost newHost)
- {
- HostFrame? frame = newHost.ActiveFrame;
- if (frame == null) {
- throw new InvalidOperationException($"No active frame to be inserted could be found within {newHost}'s frames.");
+ }
+ else if (e.OldItems != null && e.OldItems.Count > 0)
+ {
+ if (ActiveFrame != null && !Frames.Contains(ActiveFrame))
+ {
+ // Previous active tab got removed, revert back to the most recent tab or set ActiveTab to null if tabs are empty.
+ if (Frames.Count > 0)
+ {
+ ActiveFrame = Frames[^1];
+ }
+ else
+ {
+ ActiveFrame = null;
+ }
}
+ }
+ }
- if (newHost.Frames.Count == 1) {
- TilingPanel.Detach(rootPanel, newHost);
- } else {
- newHost.Frames.Remove(frame);
- }
+ ///
+ /// Inserts into as a new tab
+ /// after being dropped in the center region by the user.
+ /// If the previous host only had one frame inside it, it will be detached from its parent.
+ /// Otherwise, only the frame will be removed and inserted into 's frames.
+ ///
+ public static void Insert(TilingPanel rootPanel, TilingHost targetHost, TilingFrame frame)
+ {
+ if (frame.Parent?.Frames.Count < 2)
+ {
+ TilingPanel.Detach(rootPanel, frame.Parent);
+ }
+ else
+ {
+ frame.Parent?.Frames.Remove(frame);
+ }
- targetHost.Frames.Add(frame);
- }
+ targetHost.Frames.Add(frame);
+ }
}
diff --git a/Qrakhen.TilingFrames/Models/TilingMethod.cs b/Qrakhen.TilingFrames/Models/TilingMethod.cs
index 3346b53..22eba42 100644
--- a/Qrakhen.TilingFrames/Models/TilingMethod.cs
+++ b/Qrakhen.TilingFrames/Models/TilingMethod.cs
@@ -1,16 +1,16 @@
-namespace Qrakhen.TilingFrames.Models;
+namespace CopaData.FileInspector.GUI.TilingPanels.Models;
///
/// Declares the intent of dropping a host into another.
///
public enum TilingMethod
{
- ///
- /// In the case of the host being placed as a split next to the target host.
- ///
- Split = 0,
- ///
- /// In case of the new host becoming a tab of the target host, rather than splitting.
- ///
- Drop = 1
+ ///
+ /// In the case of the host being placed as a split next to the target host.
+ ///
+ Split = 0,
+ ///
+ /// In case of the new host becoming a tab of the target host, rather than splitting.
+ ///
+ Drop = 1
}
diff --git a/Qrakhen.TilingFrames/Models/TilingNode.cs b/Qrakhen.TilingFrames/Models/TilingNode.cs
index 7c75a7d..0efb773 100644
--- a/Qrakhen.TilingFrames/Models/TilingNode.cs
+++ b/Qrakhen.TilingFrames/Models/TilingNode.cs
@@ -1,8 +1,10 @@
-namespace Qrakhen.TilingFrames.Models;
+using CopaData.FileInspector.GUI.Models;
+
+namespace CopaData.FileInspector.GUI.TilingPanels.Models;
///
/// Base node for the binary tree structure to work.
/// A tiling node is either a , which has a reference to two s.
/// Those nodes may then be additional s, or, when at the end of the branch, a .
///
-public abstract class TilingNode : ObservableObject;
+public abstract class TilingNode : Observable;
diff --git a/Qrakhen.TilingFrames/Models/TilingPanel.cs b/Qrakhen.TilingFrames/Models/TilingPanel.cs
index fb5042f..7e63153 100644
--- a/Qrakhen.TilingFrames/Models/TilingPanel.cs
+++ b/Qrakhen.TilingFrames/Models/TilingPanel.cs
@@ -1,6 +1,6 @@
using System.Windows.Controls;
-namespace Qrakhen.TilingFrames.Models;
+namespace CopaData.FileInspector.GUI.TilingPanels.Models;
///
/// This class represents a branch in the tree structure, having two children,
@@ -15,204 +15,245 @@ namespace Qrakhen.TilingFrames.Models;
///
public class TilingPanel : TilingNode
{
- ///
- private Orientation _splitOrientation = Orientation.Horizontal;
- ///
- /// The orientation of the split, so either vertical or horizontal.
- ///
- public Orientation SplitOrientation {
- get => _splitOrientation;
- set => SetField(ref _splitOrientation, value);
- }
+ ///
+ private Orientation _splitOrientation = Orientation.Horizontal;
+ ///
+ /// The orientation of the split, so either vertical or horizontal.
+ ///
+ public Orientation SplitOrientation
+ {
+ get => _splitOrientation;
+ set => SetField(ref _splitOrientation, value);
+ }
- ///
- private double _splitRatio = 0.5;
- ///
- /// The width or height ratio of the split, with 0.5 being the exact middle.
- ///
- public double SplitRatio {
- get => _splitRatio;
- set => SetField(ref _splitRatio, Math.Min(1, Math.Max(0, value)));
- }
+ ///
+ private double _splitRatio = 0.5;
+ ///
+ /// The width or height ratio of the split, with 0.5 being the exact middle.
+ ///
+ public double SplitRatio
+ {
+ get => _splitRatio;
+ set => SetField(ref _splitRatio, Math.Min(1, Math.Max(0, value)));
+ }
- ///
- private TilingNode _alpha;
- ///
- /// 'Alpha is always set' is an enforced paradigm, so it will always point to an existing node.
- ///
- public TilingNode Alpha {
- get => _alpha;
- private set => SetField(ref _alpha, value);
- }
+ ///
+ private double _cutoffLength = 256;
+ ///
+ /// TODO: Move this into the view/control, it really got nothing in common with the model i think.
+ /// The width or height of alpha in pixels, will be initialized with 50% of the panel's available width.
+ ///
+ public double CutoffLength
+ {
+ get => _cutoffLength;
+ set => SetField(ref _cutoffLength, Math.Min(1, Math.Max(0, value)));
+ }
- ///
- private TilingNode? _beta;
- ///
- /// The beta node may be null, for example if a panel is not yet split.
- ///
- public TilingNode? Beta {
- get => _beta;
- private set => SetField(ref _beta, value);
- }
+ ///
+ private TilingNode _alpha;
+ ///
+ /// 'Alpha is always set' is an enforced paradigm, so it will always point to an existing node.
+ ///
+ public TilingNode Alpha
+ {
+ get => _alpha;
+ private set => SetField(ref _alpha, value);
+ }
- ///
- /// Whether this panel is split in two, so whether beta is not null.
- ///
- public bool IsSplit => Beta != null;
+ ///
+ private TilingNode? _beta;
+ ///
+ /// The beta node may be null, for example if a panel is not yet split.
+ ///
+ public TilingNode? Beta
+ {
+ get => _beta;
+ private set => SetField(ref _beta, value);
+ }
- public TilingPanel(TilingNode alpha, TilingNode? beta = null)
- : base()
- {
- _alpha = alpha;
- _beta = beta;
- }
+ ///
+ /// Whether this panel is split in two, so whether beta is not null.
+ ///
+ public bool IsSplit => Beta != null;
- ///
- /// Traverses down the entire tree from until
- /// is encountered, returning its direct parent panel.
- ///
- ///
- /// This may not be the quickest approach, but we're looking at UI tiling panels with perhaps 20 children at most.
- ///
- public static TilingPanel? GetParentPanel(TilingPanel rootPanel, TilingNode node)
- {
- if (rootPanel.Alpha == node || rootPanel.Beta == node) {
- return rootPanel;
+ public TilingPanel(TilingNode alpha, TilingNode? beta = null)
+ : base()
+ {
+ _alpha = alpha;
+ _beta = beta;
+ }
+
+ ///
+ /// Traverses down the entire tree from until
+ /// is encountered, returning its direct parent panel.
+ ///
+ ///
+ /// This may not be the quickest approach, but we're looking at UI tiling panels with perhaps 20 children at most.
+ ///
+ public static TilingPanel? GetParentPanel(TilingPanel rootPanel, TilingNode node)
+ {
+ if (rootPanel.Alpha == node || rootPanel.Beta == node)
+ {
+ return rootPanel;
+ }
+
+ TilingPanel? parent = null;
+ if (rootPanel.Alpha is TilingPanel alphaPanel)
+ {
+ parent = GetParentPanel(alphaPanel, node);
+ }
+
+ if (parent == null && rootPanel.Beta is TilingPanel betaPanel)
+ {
+ parent = GetParentPanel(betaPanel, node);
+ }
+
+ return parent;
+ }
+
+ ///
+ ///
+ /// Attaches to 's parent as a new .
+ /// The parent will be looked up by traversing the tree from .
+ /// The following ordered rules apply when attaching:
+ ///
+ ///
+ /// 1. If 's parent has an open beta slot, will become the beta node, wrappped in a new host.
+ /// 2. If one of the parent's child nodes is an , will be inserted
+ /// 3. If none of the above apply, the parent's beta node will be split into a new tiling panel.
+ ///
+ ///
+ ///
+ /// All of these actions will automatically split the parent panel.
+ /// If had a parent assigned previously, that parent will be detached from the branch first.
+ /// If more frames are present in 's parent, only that frame will be removed from it,
+ /// and a new is created, containing only that frame.
+ /// Note that all passed elements have to be children to the same tree.
+ /// In order to attach a to a foreign tree, it first and insert it to the foreign host.
+ ///
+ /// The root to start branch traversal from.
+ /// The host to be split.
+ ///
+ /// The frame to be attached inside a new host to 's parent.
+ /// If set to null, an will be created in its place.
+ ///
+ /// The direction at which to drop relative to .
+ /// The that now contains and .
+ public static TilingPanel Attach(TilingPanel rootPanel,
+ TilingHost targetHost,
+ TilingFrame? newFrame,
+ TilingDirection direction)
+ {
+ // This method looks way more complicated than it actually is,
+ // all I wanted to achieve is one centralized place where branching and child assignments happen,
+ // as tree structures are notoriously hard to navigate and handle when putting references everywhere.
+ // It really boils down to a simple three-step process:
+ // 1. Get or create correct host to attach,
+ // 2. Find out where to place the new host (Alpha or Beta, depending on tiling direction),
+ // 3. Create branch to be attached, or attach to free slot of parent.
+ // Todo: This note is in place for the reviewer to have a bit more context. Remove when done.
+
+ TilingPanel? parent = GetParentPanel(rootPanel, targetHost);
+ if (parent == null)
+ {
+ throw new NullReferenceException($"Detached target: Could not find a parent for {targetHost} in any of {rootPanel}'s child nodes.");
+ }
+
+ TilingHost? newHost;
+ if (newFrame == null)
+ {
+ // Substitute for the split.
+ newHost = new EmptyHost();
+ }
+ else
+ {
+ if (newFrame.Parent?.Frames.Count < 2)
+ {
+ // Detach newFrame's host from its parent.
+ Detach(rootPanel, newFrame.Parent);
}
- TilingPanel? parent = null;
- if (rootPanel.Alpha is TilingPanel alphaPanel) {
- parent = GetParentPanel(alphaPanel, node);
- }
+ newHost = new TilingHost([ newFrame ]);
+ }
- if (parent == null && rootPanel.Beta is TilingPanel betaPanel) {
- parent = GetParentPanel(betaPanel, node);
- }
+ // I tried formulating an explanation for this step but I simply can't.
+ // "Left or Top go alpha, Bottom or Right go beta."
+ bool newHostIsAlpha = direction is TilingDirection.Left or TilingDirection.Top;
+ // Check whether there's a free beta slot in the parent to use
+ if (parent.Beta == null || parent.Beta is EmptyHost emptyHost)
+ {
+ if (newHostIsAlpha)
+ {
+ // Open beta slot but newHost wants to be in alpha slot, simply swap and attach.
+ parent.Beta = parent.Alpha;
+ parent.Alpha = newHost;
+ }
+ else
+ {
+ // Open beta slot means we just put newHost there, no branching needed.
+ parent.Beta = newHost;
+ }
return parent;
- }
+ }
- ///
- ///
- /// Attaches 's active frame to 's parent as a new .
- /// The parent will be looked up by traversing the tree from .
- /// The following ordered rules apply when attaching:
- ///
- ///
- /// 1. If 's parent has an open beta slot, will become the beta node.
- /// 2. If one of the parent's child nodes is an , it will be replaced with
- /// 3. If none of the above apply, the parent's beta node will be split into a new tiling panel.
- ///
- ///
- ///
- /// All of these actions will automatically split the parent panel.
- /// If had a parent assigned previously and only a single frame, it will be detached from that branch first.
- /// If more frames are present in , only the active frame will be removed from it,
- /// and a new is created, containing only that frame.
- /// Note that all passed elements have to be children to the same tree.
- ///
- /// The root to start branch traversal from.
- /// The host to be split.
- ///
- /// The host to be attached to 's parent.
- /// If set to null, an will be created in its place.
- ///
- /// The direction at which to drop relative to .
- /// The that now contains and .
- public static TilingPanel Attach(TilingPanel rootPanel,
- TilingHost targetHost,
- TilingHost? newHost,
- TilingDirection direction)
- {
- // This method looks way more complicated than it actually is,
- // all I wanted to achieve is one centralized place where branching and child assignments happen,
- // as tree structures are notoriously hard to navigate and handle when putting references everywhere.
- // It really boils down to a simple three-step process:
- // 1. Get or create correct host to attach,
- // 2. Find out where to place the new host (Alpha or Beta, depending on tiling direction),
- // 3. Create branch to be attached, or attach to free slot of parent.
- // Todo: This note is in place for the reviewer to have a bit more context. Remove when done.
+ // Create a new branch to hold both target- and newHost.
+ TilingPanel branch;
+ if (direction is TilingDirection.Left or TilingDirection.Top)
+ {
+ branch = new TilingPanel(newHost, targetHost);
+ }
+ else
+ {
+ branch = new TilingPanel(targetHost, newHost);
+ }
- TilingPanel? parent = GetParentPanel(rootPanel, targetHost);
- if (parent == null) {
- throw new NullReferenceException($"Detached target: Could not find a parent for {targetHost} in any of {rootPanel}'s child nodes.");
+ // Assign new branch to correct chíld node.
+ if (parent.Alpha == targetHost)
+ {
+ parent.Alpha = branch;
+ }
+ else if (parent.Beta == targetHost)
+ {
+ parent.Beta = branch;
+ }
+
+ return branch;
+ }
+
+ ///
+ /// Detaches from its parent node, which will be looked up by traversing the .
+ /// If the child happened to be the alpha value, the beta value will be moved into the alpha slot to ensure alpha always having a value.
+ /// If the child's parent node ends up having no children left, which would violate the 'alpha is always set' rule,
+ /// that node itself will also be detached in order to prevent trailing zombie nodes.
+ ///
+ public static void Detach(TilingPanel rootPanel, TilingNode child)
+ {
+ TilingPanel? parent = GetParentPanel(rootPanel, child);
+ if (parent == null)
+ {
+ return; // Already an orphan.
+ }
+
+ if (parent.Alpha == child)
+ {
+ if (parent.Beta != null)
+ {
+ // Move beta over to alpha if beta has a value.
+ parent.Alpha = parent.Beta;
+ parent.Beta = null;
}
-
- if (newHost == null) {
- // Substitute for the split.
- newHost = new EmptyHost();
- } else {
- if (newHost.Frames.Count < 2) {
- // Detach newHost from a potential parent.
- Detach(rootPanel, newHost);
- } else {
- // Only take the active frame from newHost and instantiate a host to attach.
- HostFrame frame = newHost.ActiveFrame!; // Frames.Count >= 2 already ensures ActiveFrame not to be null.
- newHost = new TilingHost([frame]);
- }
+ else
+ {
+ // Both branches have detached, which means we'll detach the parent itself
+ // in order to prevent empty, trailing panels with no children.
+ Detach(rootPanel, child);
}
-
- // I tried formulating an explanation for this step but I simply can't.
- // "Left or Top go alpha, Bottom or Right go beta."
- bool newHostIsAlpha = direction is TilingDirection.Left or TilingDirection.Top;
-
- // Check whether there's a free beta slot in the parent to use
- if (parent.Beta == null) {
- if (newHostIsAlpha) {
- // Open beta slot but newHost wants to be in alpha slot, simply swap and attach.
- parent.Beta = parent.Alpha;
- parent.Alpha = newHost;
- } else {
- // Open beta slot means we just put newHost there, no branching needed.
- parent.Beta = newHost;
- }
- return parent;
- }
-
- // Create a new branch to hold both target- and newHost.
- TilingPanel branch;
- if (direction is TilingDirection.Left or TilingDirection.Top) {
- branch = new TilingPanel(newHost, targetHost);
- } else {
- branch = new TilingPanel(targetHost, newHost);
- }
-
- // Assign new branch to correct chíld node.
- if (parent.Alpha == targetHost) {
- parent.Alpha = branch;
- } else if (parent.Beta == targetHost) {
- parent.Beta = branch;
- }
-
- return branch;
- }
-
- ///
- /// Detaches from its parent node, which will be looked up by traversing the .
- /// If the child happened to be the alpha value, the beta value will be moved into the alpha slot to ensure alpha always having a value.
- /// If the child's parent node ends up having no children left, which would violate the 'alpha is always set' rule,
- /// that node itself will also be detached in order to prevent trailing zombie nodes.
- ///
- public static void Detach(TilingPanel rootPanel, TilingNode child)
- {
- TilingPanel? parent = GetParentPanel(rootPanel, child);
- if (parent == null) {
- return; // Already an orphan.
- }
-
- if (parent.Alpha == child) {
- if (parent.Beta != null) {
- // Move beta over to alpha if beta has a value.
- parent.Alpha = parent.Beta;
- parent.Beta = null;
- } else {
- // Both branches have detached, which means we'll detach the parent itself
- // in order to prevent empty, trailing panels with no children.
- Detach(rootPanel, child);
- }
- } else {
- // Child was the beta node, so we simply null it.
- parent.Beta = null;
- }
- }
+ }
+ else
+ {
+ // Child was the beta node, so we simply null it.
+ parent.Beta = null;
+ }
+ }
}
diff --git a/Qrakhen.TilingFrames/README.md b/Qrakhen.TilingFrames/README.md
index 7833a67..815dc7e 100644
--- a/Qrakhen.TilingFrames/README.md
+++ b/Qrakhen.TilingFrames/README.md
@@ -1,4 +1,6 @@
# TilingPanels
+## Tiling window manager control library for WPF
+### CopaData.Ui.Wpf.TilingPanels
Cool Library to have tiling panels.
Is that not cool?
@@ -7,13 +9,15 @@ Very nice, yes. Alpha & Beta. No Gamma or Delta. Just Alpha-Beta.
Data structure resembles a binary tree with uniform branch- and end-nodes (TilingPanels & TilingHosts).
-### TilingNode
+### Node
-### TilingPanel
+### Panel
-### TilingHost
+### Host
-### HostFrame
+### Frame
+
+### Window
### Node Structure
```cs
diff --git a/Qrakhen.TilingFrames/Themes/Controls/DropArea.xaml b/Qrakhen.TilingFrames/Themes/Controls/DropArea.xaml
index 07edfc6..43fb407 100644
--- a/Qrakhen.TilingFrames/Themes/Controls/DropArea.xaml
+++ b/Qrakhen.TilingFrames/Themes/Controls/DropArea.xaml
@@ -1,11 +1,9 @@
-
-
+ xmlns:converters="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Converters"
+ xmlns:local="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Controls.DropArea"
+ xmlns:dnd="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Controls.DragAndDrop"
+ xmlns:models="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Models">
-
+
-
+
\ No newline at end of file
diff --git a/Qrakhen.TilingFrames/Themes/Controls/TilingRoot.xaml b/Qrakhen.TilingFrames/Themes/Controls/TilingRoot.xaml
index 6ba5fe4..a9e3c13 100644
--- a/Qrakhen.TilingFrames/Themes/Controls/TilingRoot.xaml
+++ b/Qrakhen.TilingFrames/Themes/Controls/TilingRoot.xaml
@@ -1,10 +1,8 @@
-
-
+ xmlns:converters="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Converters"
+ xmlns:local="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Controls"
+ xmlns:models="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Models">