diff --git a/CustomWidgets/DockingTabControl.cs b/CustomWidgets/DockingTabControl.cs index eea9602cb..ba1c81021 100644 --- a/CustomWidgets/DockingTabControl.cs +++ b/CustomWidgets/DockingTabControl.cs @@ -29,6 +29,7 @@ either expressed or implied, of the FreeBSD Project. using System; using System.Collections.Generic; +using System.Linq; using MatterHackers.Agg; using MatterHackers.Agg.Font; using MatterHackers.Agg.ImageProcessing; @@ -43,25 +44,32 @@ namespace MatterHackers.MatterControl.CustomWidgets { public class DockingTabControl : GuiWidget { - public event EventHandler PinStatusChanged; + private Dictionary allTabs = new Dictionary(); // TODO: Pinned state should preferably come from MCWS, default to local data if guest and be per user not printer private bool isPinned; - public bool ControlIsPinned - { - get => isPinned; - set { - isPinned = value; - PinStatusChanged?.Invoke(this, null); - } - } private GuiWidget topToBottom; - Dictionary allTabs = new Dictionary(); + protected GuiWidget widgetTodockTo; + public DockSide DockSide { get; set; } - public DockingTabControl() + public DockingTabControl(GuiWidget widgetTodockTo, DockSide dockSide) { + this.widgetTodockTo = widgetTodockTo; + this.DockSide = dockSide; + } + + public event EventHandler PinStatusChanged; + + public bool ControlIsPinned + { + get => isPinned; + set + { + isPinned = value; + PinStatusChanged?.Invoke(this, null); + } } public void AddPage(string name, GuiWidget widget) @@ -70,9 +78,25 @@ namespace MatterHackers.MatterControl.CustomWidgets Rebuild(); } - void Rebuild() + public override void Initialize() { - foreach(var nameWidget in allTabs) + base.Initialize(); + + Width = 30; + VAnchor = VAnchor.ParentBottomTop; + HAnchor = HAnchor.FitToChildren; + topToBottom = new FlowLayoutWidget(FlowDirection.TopToBottom) + { + HAnchor = HAnchor.FitToChildren, + VAnchor = VAnchor.ParentBottomTop + }; + AddChild(topToBottom); + } + + private void Rebuild() + { + Focus(); + foreach (var nameWidget in allTabs) { nameWidget.Value.Parent?.RemoveChild(nameWidget.Value); nameWidget.Value.ClearRemovedFlag(); @@ -119,7 +143,7 @@ namespace MatterHackers.MatterControl.CustomWidgets else // control is floating { var rotatedLabel = new VertexSourceApplyTransform( - new TypeFacePrinter(tabTitle, 12), + new TypeFacePrinter(tabTitle, 12), Affine.NewRotation(MathHelper.DegreesToRadians(-90))); var bounds = rotatedLabel.Bounds(); @@ -128,6 +152,7 @@ namespace MatterHackers.MatterControl.CustomWidgets var optionsText = new GuiWidget(bounds.Width, bounds.Height) { DoubleBuffer = true, + Margin = new BorderDouble(3, 10) }; optionsText.AfterDraw += (s, e) => { @@ -143,6 +168,7 @@ namespace MatterHackers.MatterControl.CustomWidgets { BackgroundColor = ActiveTheme.Instance.PrimaryBackgroundColor }; + settingsButton.PopupLayoutEngine = new UnpinnedLayoutEngine(settingsButton.PopupContent, widgetTodockTo, DockSide); topToBottom.AddChild(settingsButton); } } @@ -157,21 +183,6 @@ namespace MatterHackers.MatterControl.CustomWidgets } } - public override void Initialize() - { - base.Initialize(); - - Width = 30; - VAnchor = VAnchor.ParentBottomTop; - HAnchor = HAnchor.FitToChildren; - topToBottom = new FlowLayoutWidget(FlowDirection.TopToBottom) - { - HAnchor = HAnchor.FitToChildren, - VAnchor = VAnchor.ParentBottomTop - }; - AddChild(topToBottom); - } - internal class ResizeContainer : FlowLayoutWidget { private double downWidth = 0; @@ -186,14 +197,14 @@ namespace MatterHackers.MatterControl.CustomWidgets this.Cursor = Cursors.WaitCursor; } + public RGBA_Bytes BorderColor { get; set; } = ActiveTheme.Instance.TertiaryBackgroundColor; + public override void OnDraw(Graphics2D graphics2D) { graphics2D.FillRectangle(LocalBounds.Left, LocalBounds.Bottom, LocalBounds.Left + resizeWidth, LocalBounds.Top, this.BorderColor); base.OnDraw(graphics2D); } - public RGBA_Bytes BorderColor { get; set; } = ActiveTheme.Instance.TertiaryBackgroundColor; - public override void OnMouseDown(MouseEventArgs mouseEvent) { if (mouseEvent.Position.x < resizeWidth) @@ -272,10 +283,119 @@ namespace MatterHackers.MatterControl.CustomWidgets Width = 500; Height = 640; + //VAnchor = VAnchor.ParentBottomTop; topToBottom.AddChild(child); AddChild(topToBottom); } } } + + public enum DockSide { Left, Bottom, Right, Top }; + + public class UnpinnedLayoutEngine : IPopupLayoutEngine + { + protected GuiWidget widgetTodockTo; + private GuiWidget contentWidget; + private HashSet hookedParents = new HashSet(); + private PopupWidget popupWidget; + + public DockSide DockSide { get; set; } + + public UnpinnedLayoutEngine(GuiWidget contentWidget, GuiWidget widgetTodockTo, DockSide dockSide) + { + this.contentWidget = contentWidget; + this.widgetTodockTo = widgetTodockTo; + DockSide = dockSide; + } + + public double MaxHeight { get; private set; } + + public void Closed() + { + // Unbind callbacks on parents for position_changed if we're closing + foreach (GuiWidget widget in hookedParents) + { + widget.PositionChanged -= widgetRelativeTo_PositionChanged; + widget.BoundsChanged -= widgetRelativeTo_PositionChanged; + } + + // Long lived originating item must be unregistered + widgetTodockTo.Closed -= widgetRelativeTo_Closed; + + // Restore focus to originating widget on close + if (this.widgetTodockTo != null + && !widgetTodockTo.HasBeenClosed) + { + // On menu close, select the first scrollable parent of the widgetRelativeTo + var scrollableParent = widgetTodockTo.Parents().FirstOrDefault(); + if (scrollableParent != null) + { + scrollableParent.Focus(); + } + } + } + + public void ShowPopup(PopupWidget popupWidget) + { + this.popupWidget = popupWidget; + SystemWindow windowToAddTo = widgetTodockTo.Parents().FirstOrDefault(); + windowToAddTo?.AddChild(popupWidget); + + GuiWidget topParent = widgetTodockTo.Parent; + while (topParent.Parent != null + && topParent as SystemWindow == null) + { + // Regrettably we don't know who it is that is the window that will actually think it is moving relative to its parent + // but we need to know anytime our widgetRelativeTo has been moved by any change, so we hook them all. + if (!hookedParents.Contains(topParent)) + { + hookedParents.Add(topParent); + topParent.PositionChanged += widgetRelativeTo_PositionChanged; + topParent.BoundsChanged += widgetRelativeTo_PositionChanged; + } + + topParent = topParent.Parent; + } + + widgetRelativeTo_PositionChanged(widgetTodockTo, null); + widgetTodockTo.Closed += widgetRelativeTo_Closed; + } + + private void widgetRelativeTo_Closed(object sender, ClosedEventArgs e) + { + // If the owning widget closed, so should we + popupWidget.CloseMenu(); + } + + private void widgetRelativeTo_PositionChanged(object sender, EventArgs e) + { + if (widgetTodockTo != null) + { + RectangleDouble bounds = widgetTodockTo.BoundsRelativeToParent; + + GuiWidget topParent = widgetTodockTo.Parent; + while (topParent != null && topParent.Parent != null) + { + topParent.ParentToChildTransform.transform(ref bounds); + topParent = topParent.Parent; + } + + switch (DockSide) + { + case DockSide.Left: + throw new NotImplementedException(); + case DockSide.Bottom: + throw new NotImplementedException(); + case DockSide.Right: + popupWidget.LocalBounds = new RectangleDouble(bounds.Right - contentWidget.Width, bounds.Bottom, bounds.Right, bounds.Top); + break; + case DockSide.Top: + throw new NotImplementedException(); + default: + throw new NotImplementedException(); + } + } + } + } } \ No newline at end of file diff --git a/PartPreviewWindow/OverflowDropdown.cs b/PartPreviewWindow/OverflowDropdown.cs index fe52fd6a8..1dd2d4f88 100644 --- a/PartPreviewWindow/OverflowDropdown.cs +++ b/PartPreviewWindow/OverflowDropdown.cs @@ -123,6 +123,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow public bool AlignToRightEdge { get; set; } public RGBA_Bytes BorderColor { get; set; } = RGBA_Bytes.Gray; public Func DynamicPopupContent { get; set; } + public IPopupLayoutEngine PopupLayoutEngine { get; set; } public Direction PopDirection { get; set; } = Direction.Down; public GuiWidget PopupContent { get; set; } @@ -149,6 +150,10 @@ namespace MatterHackers.MatterControl.PartPreviewWindow public void ShowPopup() { + if (PopupLayoutEngine == null) + { + PopupLayoutEngine = new PopupLayoutEngine(this.PopupContent, this, Vector2.Zero, this.PopDirection, 0, this.AlignToRightEdge); + } menuVisible = true; this.PopupContent?.ClearRemovedFlag(); @@ -165,7 +170,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow this.BeforeShowPopup(); - popupWidget = new PopupWidget(this.PopupContent, this, Vector2.Zero, this.PopDirection, 0, this.AlignToRightEdge) + popupWidget = new PopupWidget(this.PopupContent, PopupLayoutEngine) { BorderWidth = 1, BorderColor = this.BorderColor, diff --git a/PartPreviewWindow/PartPreviewContent.cs b/PartPreviewWindow/PartPreviewContent.cs index 106168092..ed682816a 100644 --- a/PartPreviewWindow/PartPreviewContent.cs +++ b/PartPreviewWindow/PartPreviewContent.cs @@ -178,7 +178,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow this.gcodeViewer.Visible = false; leftToRight.AddChild(gcodeViewer); - AddSettingsTabBar(leftToRight); + AddSettingsTabBar(leftToRight, modelViewer); modelViewer.BackgroundColor = ActiveTheme.Instance.TertiaryBackgroundColor; gcodeViewer.BackgroundColor = ActiveTheme.Instance.TertiaryBackgroundColor; @@ -206,9 +206,9 @@ namespace MatterHackers.MatterControl.PartPreviewWindow this.AnchorAll(); } - private void AddSettingsTabBar(GuiWidget parent) + private void AddSettingsTabBar(GuiWidget parent, GuiWidget widgetTodockTo) { - var sideBar = new DockingTabControl() + var sideBar = new DockingTabControl(widgetTodockTo, DockSide.Right) { ControlIsPinned = ApplicationController.Instance.PrintSettingsPinned }; diff --git a/Submodules/agg-sharp b/Submodules/agg-sharp index 3bce91aed..b57a72d8c 160000 --- a/Submodules/agg-sharp +++ b/Submodules/agg-sharp @@ -1 +1 @@ -Subproject commit 3bce91aedc18346195b8a21498096eaed47c342d +Subproject commit b57a72d8c9ae93914b007c4725b562e2c4d8730e