From ee6df9483fb4bd66581eeab0754a4e92c9cf310e Mon Sep 17 00:00:00 2001 From: Ashley Date: Mon, 29 Aug 2022 22:00:29 +0100 Subject: [PATCH 1/5] Added DropDownToolItem, with implementation for Winforms, WPF and GTK. Added example to test project. --- .../Forms/ToolBar/DropDownToolItemHandler.cs | 90 +++++++++++++++++++ src/Eto.Gtk/Platform.cs | 1 + .../Forms/ToolBar/DropDownToolItemHandler.cs | 65 ++++++++++++++ src/Eto.WinForms/Platform.cs | 1 + .../Forms/ToolBar/DropDownToolItemHandler.cs | 90 +++++++++++++++++++ src/Eto.Wpf/Platform.cs | 1 + src/Eto/Forms/ToolBar/DropDownToolItem.cs | 33 +++++++ test/Eto.Test/MainForm.cs | 12 +++ .../Sections/Behaviors/ToolBarSection.cs | 4 +- 9 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 src/Eto.Gtk/Forms/ToolBar/DropDownToolItemHandler.cs create mode 100644 src/Eto.WinForms/Forms/ToolBar/DropDownToolItemHandler.cs create mode 100644 src/Eto.Wpf/Forms/ToolBar/DropDownToolItemHandler.cs create mode 100644 src/Eto/Forms/ToolBar/DropDownToolItem.cs diff --git a/src/Eto.Gtk/Forms/ToolBar/DropDownToolItemHandler.cs b/src/Eto.Gtk/Forms/ToolBar/DropDownToolItemHandler.cs new file mode 100644 index 0000000000..5f4e3b42c0 --- /dev/null +++ b/src/Eto.Gtk/Forms/ToolBar/DropDownToolItemHandler.cs @@ -0,0 +1,90 @@ +using System; +using Eto.Forms; +using Gdk; + +namespace Eto.GtkSharp.Forms.ToolBar +{ + + public class DropDownToolItemHandler : ToolItemHandler, DropDownToolItem.IHandler + { + // GTK3 MenuToolButton looks kind of horrible with a huge visually separate drop button, and forces + // the main button and drop button to function separately. So, use a normal ToolButton with a Menu, but + // add a drop arrow to the button text. + + private Gtk.Menu dropMenu; + + public DropDownToolItemHandler() + { + dropMenu = new Gtk.Menu(); + } + + #region IToolBarButton Members + + + #endregion + + public override void CreateControl(ToolBarHandler handler, int index) + { + Gtk.Toolbar tb = handler.Control; + + Control = new Gtk.ToolButton(GtkImage, Text + " ▾"); + Control.IsImportant = true; + Control.Sensitive = Enabled; + Control.TooltipText = this.ToolTip; + Control.ShowAll(); + Control.NoShowAll = true; + Control.Visible = Visible; + //control.CanFocus = false; // why is this disabled and not true??? + tb.Insert(Control, index); + Control.Clicked += HandleClicked; + } + + private void HandleClicked(object sender, EventArgs e) + { + dropMenu.PopupAtWidget(Control, Gravity.SouthWest, Gravity.NorthWest, null); + Connector.HandleClicked(sender, e); + } + + protected new DropDownToolItemConnector Connector { get { return (DropDownToolItemConnector)base.Connector; } } + + protected override WeakConnector CreateConnector() + { + return new DropDownToolItemConnector(); + } + + protected class DropDownToolItemConnector : WeakConnector + { + public new DropDownToolItemHandler Handler { get { return (DropDownToolItemHandler)base.Handler; } } + + public void HandleClicked(object sender, EventArgs e) + { + Handler.Widget.OnClick(e); + } + } + + public void AddMenu(int index, MenuItem item) + { + dropMenu.Insert((Gtk.Widget)item.ControlObject, index); + var handler = item.Handler as Menu.IMenuHandler; + //SetChildAccelGroup(item); + } + + public void RemoveMenu(MenuItem item) + { + dropMenu.Remove((Gtk.Widget)item.ControlObject); + /* + var handler = item.Handler as Menu.IMenuHandler; + if (handler != null) + handler.SetAccelGroup(null); + */ + } + + public void Clear() + { + foreach (Gtk.Widget w in dropMenu.Children) + { + dropMenu.Remove(w); + } + } + } +} diff --git a/src/Eto.Gtk/Platform.cs b/src/Eto.Gtk/Platform.cs index 7a7d4ccd51..42568850b4 100644 --- a/src/Eto.Gtk/Platform.cs +++ b/src/Eto.Gtk/Platform.cs @@ -198,6 +198,7 @@ public static void AddTo(Eto.Platform p) p.Add(() => new RadioToolItemHandler()); p.Add(() => new SeparatorToolItemHandler()); p.Add(() => new ButtonToolItemHandler()); + p.Add(() => new DropDownToolItemHandler()); p.Add(() => new ToolBarHandler()); // Forms diff --git a/src/Eto.WinForms/Forms/ToolBar/DropDownToolItemHandler.cs b/src/Eto.WinForms/Forms/ToolBar/DropDownToolItemHandler.cs new file mode 100644 index 0000000000..9f52819be9 --- /dev/null +++ b/src/Eto.WinForms/Forms/ToolBar/DropDownToolItemHandler.cs @@ -0,0 +1,65 @@ +using System; +using SD = System.Drawing; +using SWF = System.Windows.Forms; +using Eto.Forms; + +namespace Eto.WinForms.Forms.ToolBar +{ + public class DropDownToolItemHandler : ToolItemHandler, DropDownToolItem.IHandler + { + bool openedHandled; + + public DropDownToolItemHandler() + { + Control = new SWF.ToolStripDropDownButton(); + Control.Tag = this; + Control.Click += control_Click; + } + + void HandleDropDownOpened(object sender, EventArgs e) + { + foreach (var item in Widget.Items) + { + var callback = ((ICallbackSource)item).Callback as MenuItem.ICallback; + if (callback != null) + callback.OnValidate(item, e); + } + } + + void control_Click(object sender, EventArgs e) + { + Widget.OnClick(EventArgs.Empty); + } + + public override bool Enabled + { + get { return Control.Enabled; } + set { Control.Enabled = value; } + } + + public override void CreateControl(ToolBarHandler handler, int index) + { + handler.Control.Items.Insert(index, Control); + } + + public void AddMenu(int index, MenuItem item) + { + Control.DropDownItems.Insert(index, (SWF.ToolStripItem)item.ControlObject); + if (!openedHandled) + { + Control.DropDownOpening += HandleDropDownOpened; + openedHandled = true; + } + } + + public void RemoveMenu(MenuItem item) + { + Control.DropDownItems.Remove((SWF.ToolStripItem)item.ControlObject); + } + + public void Clear() + { + Control.DropDownItems.Clear(); + } + } +} diff --git a/src/Eto.WinForms/Platform.cs b/src/Eto.WinForms/Platform.cs index e65f3a7c68..5beabcb930 100644 --- a/src/Eto.WinForms/Platform.cs +++ b/src/Eto.WinForms/Platform.cs @@ -142,6 +142,7 @@ public static void AddTo(Eto.Platform p) p.Add(() => new RadioToolItemHandler()); p.Add(() => new SeparatorToolBarItemHandler()); p.Add(() => new ButtonToolItemHandler()); + p.Add(() => new DropDownToolItemHandler()); p.Add(() => new ToolBarHandler()); // Forms diff --git a/src/Eto.Wpf/Forms/ToolBar/DropDownToolItemHandler.cs b/src/Eto.Wpf/Forms/ToolBar/DropDownToolItemHandler.cs new file mode 100644 index 0000000000..762fe421e7 --- /dev/null +++ b/src/Eto.Wpf/Forms/ToolBar/DropDownToolItemHandler.cs @@ -0,0 +1,90 @@ +using System; +using Eto.Drawing; +using Eto.Forms; +using swc = System.Windows.Controls; +using swm = System.Windows.Media; +using sw = System.Windows; +using System.Windows; + +namespace Eto.Wpf.Forms.ToolBar +{ + public class DropDownToolItemHandler : ToolItemHandler, DropDownToolItem.IHandler + { + Image image; + readonly swc.Image swcImage; + readonly swc.TextBlock label; + readonly swc.MenuItem root; + + public DropDownToolItemHandler () + { + root = new swc.MenuItem(); + Control = new swc.Menu(); + Control.Items.Add(root); + swcImage = new swc.Image { MaxHeight = 16, MaxWidth = 16 }; + label = new swc.TextBlock(); + var arrow = new sw.Shapes.Path { Data = swm.Geometry.Parse("M 0 0 L 3 3 L 6 0 Z"), VerticalAlignment = sw.VerticalAlignment.Center, Margin = new Thickness(8, 2, 0, 0), Fill = swm.Brushes.Black }; + var panel = new swc.StackPanel { Orientation = swc.Orientation.Horizontal, Children = { swcImage, label, arrow } }; + + root.Header = panel; + root.Click += Control_Click; + root.SubmenuOpened += Control_Click; + sw.Automation.AutomationProperties.SetLabeledBy(Control, label); + } + + private void Control_Click(object sender, RoutedEventArgs e) + { + // WPF raises this event for all child items as well as the root menu, so check sender + if (e.OriginalSource != root) + return; + + Widget.OnClick(EventArgs.Empty); + } + + public override string Text + { + get { return label.Text.ToEtoMnemonic(); } + set { label.Text = value.ToPlatformMnemonic(); } + } + + public override string ToolTip + { + get { return Control.ToolTip as string; } + set { Control.ToolTip = value; } + } + + public override Image Image + { + get { return image; } + set + { + image = value; + swcImage.Source = image.ToWpf(Screen.PrimaryScreen.LogicalPixelSize, swcImage.GetMaxSize().ToEtoSize()); + } + } + + public override bool Enabled + { + get { return Control.IsEnabled; } + set + { + Control.IsEnabled = value; + swcImage.IsEnabled = value; + } + } + + public void AddMenu(int index, MenuItem item) + { + root.Items.Insert(index, (swc.MenuItem)item.ControlObject); + } + + public void RemoveMenu(MenuItem item) + { + root.Items.Remove((swc.MenuItem)item.ControlObject); + } + + public void Clear() + { + root.Items.Clear(); + } + } +} diff --git a/src/Eto.Wpf/Platform.cs b/src/Eto.Wpf/Platform.cs index 8f90db320e..4ae4b77309 100755 --- a/src/Eto.Wpf/Platform.cs +++ b/src/Eto.Wpf/Platform.cs @@ -161,6 +161,7 @@ public static void AddTo(Eto.Platform p) p.Add(() => new RadioToolItemHandler()); p.Add(() => new SeparatorToolItemHandler()); p.Add(() => new ButtonToolItemHandler()); + p.Add(() => new DropDownToolItemHandler()); p.Add(() => new ToolBarHandler()); // Forms diff --git a/src/Eto/Forms/ToolBar/DropDownToolItem.cs b/src/Eto/Forms/ToolBar/DropDownToolItem.cs new file mode 100644 index 0000000000..1ef240abb3 --- /dev/null +++ b/src/Eto/Forms/ToolBar/DropDownToolItem.cs @@ -0,0 +1,33 @@ +namespace Eto.Forms +{ + /// + /// Tool item to display a drop-down menu + /// + [Handler(typeof(DropDownToolItem.IHandler))] + public class DropDownToolItem : ToolItem + { + MenuItemCollection items; + + new IHandler Handler { get { return (IHandler)base.Handler; } } + + /// + /// Initializes a new instance of the class. + /// + public DropDownToolItem() + { + } + + /// + /// Gets the collection of menu items + /// + /// The menu items + public MenuItemCollection Items { get { return items ?? (items = new MenuItemCollection(Handler, null)); } } + + /// + /// Handler for the . + /// + public new interface IHandler : ToolItem.IHandler, Menu.ISubmenuHandler + { + } + } +} diff --git a/test/Eto.Test/MainForm.cs b/test/Eto.Test/MainForm.cs index 987104f9c0..779b0c6093 100644 --- a/test/Eto.Test/MainForm.cs +++ b/test/Eto.Test/MainForm.cs @@ -333,6 +333,13 @@ void CreateMenuToolBar() ToolBar = new ToolBar(); ToolBar.Items.Add(about); + if (Platform.Supports()) + { + var dropItem = new DropDownToolItem { Text = "Dropdown", Image = TestIcons.TestImage }; + dropItem.Items.Add(new ButtonMenuItem { Text = "Subitem 1", Items = { new ButtonMenuItem { Text = "Nested subitem 1" } } }); + dropItem.Items.Add(new ButtonMenuItem { Text = "Subitem 2", Image = TestIcons.TestIcon }); + ToolBar.Items.Add(LogEvents(dropItem)); + } if (Platform.Supports()) { ToolBar.Items.Add(new SeparatorToolItem { Type = SeparatorToolItemType.Divider }); @@ -379,6 +386,11 @@ ButtonToolItem LogEvents(ButtonToolItem item) item.Click += (sender, e) => Log.Write(sender, $"Click: {item.Text}"); return item; } + DropDownToolItem LogEvents(DropDownToolItem item) + { + item.Click += (sender, e) => Log.Write(sender, $"Click: {item.Text}"); + return item; + } protected override void OnWindowStateChanged(EventArgs e) { diff --git a/test/Eto.Test/Sections/Behaviors/ToolBarSection.cs b/test/Eto.Test/Sections/Behaviors/ToolBarSection.cs index fe71fe4bd1..eefe105e78 100644 --- a/test/Eto.Test/Sections/Behaviors/ToolBarSection.cs +++ b/test/Eto.Test/Sections/Behaviors/ToolBarSection.cs @@ -30,7 +30,7 @@ private void ShowTestDialog() var indexStepper = new NumericStepper { MinValue = -1, MaxValue = -1, Value = -1 }; var typeDropDown = new DropDown { - Items = { "Button", "Radio", "Check", "Separator:Divider", "Separator:Space", "Separator:FlexableSpace" }, + Items = { "Button", "DropDown", "Radio", "Check", "Separator:Divider", "Separator:Space", "Separator:FlexableSpace" }, SelectedIndex = 0 }; @@ -62,6 +62,8 @@ ToolItem CreateItem() default: case "button": return new ButtonToolItem { Text = $"Button{++count}", Image = GetImage() }; + case "dropdown": + return new DropDownToolItem { Text = $"DropDown{++count}", Image = GetImage(), Items = { new ButtonMenuItem { Text = "Sub1" }, new ButtonMenuItem { Text = "Sub2" }, new ButtonMenuItem { Text = "Sub3" } } }; case "radio": return new RadioToolItem { Text = $"Radio{++count}", Image = GetImage() }; case "check": From 80943b61c3dc9a55150411b5bf36964d90ba3861 Mon Sep 17 00:00:00 2001 From: Ashley Date: Mon, 29 Aug 2022 22:47:13 +0100 Subject: [PATCH 2/5] Fix for older GTK platforms. --- .../Forms/ToolBar/DropDownToolItemHandler.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Eto.Gtk/Forms/ToolBar/DropDownToolItemHandler.cs b/src/Eto.Gtk/Forms/ToolBar/DropDownToolItemHandler.cs index 5f4e3b42c0..38acb45af0 100644 --- a/src/Eto.Gtk/Forms/ToolBar/DropDownToolItemHandler.cs +++ b/src/Eto.Gtk/Forms/ToolBar/DropDownToolItemHandler.cs @@ -1,4 +1,5 @@ using System; +using Eto.Drawing; using Eto.Forms; using Gdk; @@ -39,11 +40,33 @@ public override void CreateControl(ToolBarHandler handler, int index) Control.Clicked += HandleClicked; } +#if GTKCORE private void HandleClicked(object sender, EventArgs e) { dropMenu.PopupAtWidget(Control, Gravity.SouthWest, Gravity.NorthWest, null); Connector.HandleClicked(sender, e); } +#else + private void HandleClicked(object sender, EventArgs e) + { + var buttonRect = Control.Allocation; + var pt = new PointF(buttonRect.Left, buttonRect.Bottom); + var parentWindow = (Widget.Parent as Eto.Forms.ToolBar).Parent as Eto.Forms.Window; + showLocation = parentWindow.PointToScreen(pt); + dropMenu.Popup(null, null, PopupMenuPosition, 3u, Gtk.Global.CurrentEventTime); + + Connector.HandleClicked(sender, e); + } + + PointF showLocation; + + void PopupMenuPosition(Gtk.Menu menu, out int x, out int y, out bool push_in) + { + x = (int)showLocation.X; + y = (int)showLocation.Y; + push_in = false; + } +#endif protected new DropDownToolItemConnector Connector { get { return (DropDownToolItemConnector)base.Connector; } } From 6f87240345e17c7fd3960f63ba88975d1d8b456f Mon Sep 17 00:00:00 2001 From: Ashley Date: Mon, 29 Aug 2022 23:40:36 +0100 Subject: [PATCH 3/5] Use ToggleToolButton instead of ToolButton for GTK so that button can look 'pressed' whilst menu is open. Made the dropdown arrow optional. --- .../Forms/ToolBar/DropDownToolItemHandler.cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Eto.Gtk/Forms/ToolBar/DropDownToolItemHandler.cs b/src/Eto.Gtk/Forms/ToolBar/DropDownToolItemHandler.cs index 38acb45af0..fba5a1ec53 100644 --- a/src/Eto.Gtk/Forms/ToolBar/DropDownToolItemHandler.cs +++ b/src/Eto.Gtk/Forms/ToolBar/DropDownToolItemHandler.cs @@ -6,7 +6,7 @@ namespace Eto.GtkSharp.Forms.ToolBar { - public class DropDownToolItemHandler : ToolItemHandler, DropDownToolItem.IHandler + public class DropDownToolItemHandler : ToolItemHandler, DropDownToolItem.IHandler { // GTK3 MenuToolButton looks kind of horrible with a huge visually separate drop button, and forces // the main button and drop button to function separately. So, use a normal ToolButton with a Menu, but @@ -28,7 +28,10 @@ public override void CreateControl(ToolBarHandler handler, int index) { Gtk.Toolbar tb = handler.Control; - Control = new Gtk.ToolButton(GtkImage, Text + " ▾"); + var buttonText = ShowDropArrow ? Text + " ▾" : Text; + Control = new Gtk.ToggleToolButton(); + Control.Label = buttonText; + Control.IconWidget = GtkImage; Control.IsImportant = true; Control.Sensitive = Enabled; Control.TooltipText = this.ToolTip; @@ -38,17 +41,29 @@ public override void CreateControl(ToolBarHandler handler, int index) //control.CanFocus = false; // why is this disabled and not true??? tb.Insert(Control, index); Control.Clicked += HandleClicked; + dropMenu.Hidden += HandleMenuClosed; } + /// + /// Gets or sets whether the drop arrow is shown on the button. + /// + public bool ShowDropArrow { get; set; } = true; + #if GTKCORE private void HandleClicked(object sender, EventArgs e) { + if (!Control.Active) + return; + dropMenu.PopupAtWidget(Control, Gravity.SouthWest, Gravity.NorthWest, null); Connector.HandleClicked(sender, e); } #else private void HandleClicked(object sender, EventArgs e) { + if (!Control.Active) + return; + var buttonRect = Control.Allocation; var pt = new PointF(buttonRect.Left, buttonRect.Bottom); var parentWindow = (Widget.Parent as Eto.Forms.ToolBar).Parent as Eto.Forms.Window; @@ -68,6 +83,11 @@ void PopupMenuPosition(Gtk.Menu menu, out int x, out int y, out bool push_in) } #endif + private void HandleMenuClosed(Object sender, EventArgs e) + { + Control.Active = false; + } + protected new DropDownToolItemConnector Connector { get { return (DropDownToolItemConnector)base.Connector; } } protected override WeakConnector CreateConnector() From 80bff282be4b85141dac89f3e7b2cfd3d5285550 Mon Sep 17 00:00:00 2001 From: Ashley Date: Thu, 1 Sep 2022 02:15:38 +0100 Subject: [PATCH 4/5] Enabled hiding the drop arrow on Winforms and WPF. --- .../Forms/ToolBar/DropDownToolItemHandler.cs | 9 +++++++++ src/Eto.Wpf/Forms/ToolBar/DropDownToolItemHandler.cs | 12 +++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Eto.WinForms/Forms/ToolBar/DropDownToolItemHandler.cs b/src/Eto.WinForms/Forms/ToolBar/DropDownToolItemHandler.cs index 9f52819be9..8d215f0b97 100644 --- a/src/Eto.WinForms/Forms/ToolBar/DropDownToolItemHandler.cs +++ b/src/Eto.WinForms/Forms/ToolBar/DropDownToolItemHandler.cs @@ -37,6 +37,15 @@ public override bool Enabled set { Control.Enabled = value; } } + /// + /// Gets or sets whether the drop arrow is shown on the button. + /// + public bool ShowDropArrow + { + get { return Control.ShowDropDownArrow; } + set { Control.ShowDropDownArrow = value; } + } + public override void CreateControl(ToolBarHandler handler, int index) { handler.Control.Items.Insert(index, Control); diff --git a/src/Eto.Wpf/Forms/ToolBar/DropDownToolItemHandler.cs b/src/Eto.Wpf/Forms/ToolBar/DropDownToolItemHandler.cs index 762fe421e7..30fdc7ab41 100644 --- a/src/Eto.Wpf/Forms/ToolBar/DropDownToolItemHandler.cs +++ b/src/Eto.Wpf/Forms/ToolBar/DropDownToolItemHandler.cs @@ -14,6 +14,7 @@ public class DropDownToolItemHandler : ToolItemHandler + /// Gets or sets whether the drop arrow is shown on the button. + /// + public bool ShowDropArrow + { + get { return arrow.Visibility == Visibility.Visible; } + set { arrow.Visibility = value ? Visibility.Visible : Visibility.Collapsed; } + } + public void AddMenu(int index, MenuItem item) { root.Items.Insert(index, (swc.MenuItem)item.ControlObject); From 56c893b0e1c709ae0e284fd1bc7846d9f60bb8a0 Mon Sep 17 00:00:00 2001 From: Curtis Wensley Date: Mon, 19 Sep 2022 08:35:30 -0700 Subject: [PATCH 5/5] Add Mac implementation for DropDownToolItem - Add ShowDropArrow for all platforms, and ensure it updates at runtime - Mac has two implementations, one that uses NSSegmentedControl (10.14 and earlier) and another that uses NSMenuToolbarItem (10.15 and later) --- lib/monomac | 2 +- .../Forms/ToolBar/DropDownToolItemHandler.cs | 31 +++++-- src/Eto.Gtk/Forms/ToolBar/ToolItemHandler.cs | 42 ++++++++- src/Eto.Mac/Eto.Mac64.csproj | 1 + src/Eto.Mac/Eto.XamMac2.csproj | 1 + .../Forms/Controls/SegmentedButtonHandler.cs | 6 +- src/Eto.Mac/Forms/Menu/MenuHandler.cs | 9 ++ .../Forms/ToolBar/DropDownToolItemHandler.cs | 85 +++++++++++++++++++ .../DropDownToolItemPreCatalinaHandler.cs | 84 ++++++++++++++++++ .../Forms/ToolBar/SeparatorToolItemHandler.cs | 2 + src/Eto.Mac/Forms/ToolBar/ToolBarHandler.cs | 2 +- src/Eto.Mac/Forms/ToolBar/ToolItemHandler.cs | 84 ++++++++++++------ src/Eto.Mac/Platform.cs | 4 + .../Forms/ToolBar/DropDownToolItemHandler.cs | 2 +- src/Eto/Forms/Menu/CheckMenuItem.cs | 9 ++ src/Eto/Forms/ToolBar/DropDownToolItem.cs | 22 ++++- test/Eto.Test/MainForm.cs | 20 +++-- 17 files changed, 356 insertions(+), 50 deletions(-) create mode 100644 src/Eto.Mac/Forms/ToolBar/DropDownToolItemHandler.cs create mode 100644 src/Eto.Mac/Forms/ToolBar/DropDownToolItemPreCatalinaHandler.cs diff --git a/lib/monomac b/lib/monomac index 98a263a215..d3a6207a6b 160000 --- a/lib/monomac +++ b/lib/monomac @@ -1 +1 @@ -Subproject commit 98a263a2153980e9735e42cf36a6845514a48ab2 +Subproject commit d3a6207a6b13990dcaac7e99a559de47a3d051b3 diff --git a/src/Eto.Gtk/Forms/ToolBar/DropDownToolItemHandler.cs b/src/Eto.Gtk/Forms/ToolBar/DropDownToolItemHandler.cs index fba5a1ec53..26349a680d 100644 --- a/src/Eto.Gtk/Forms/ToolBar/DropDownToolItemHandler.cs +++ b/src/Eto.Gtk/Forms/ToolBar/DropDownToolItemHandler.cs @@ -13,24 +13,23 @@ public class DropDownToolItemHandler : ToolItemHandler /// Gets or sets whether the drop arrow is shown on the button. /// - public bool ShowDropArrow { get; set; } = true; + public bool ShowDropArrow + { + get => showDropArrow; + set + { + if (showDropArrow != value) + { + showDropArrow = value; + SetText(); + } + } + } #if GTKCORE private void HandleClicked(object sender, EventArgs e) @@ -108,7 +125,7 @@ public void HandleClicked(object sender, EventArgs e) public void AddMenu(int index, MenuItem item) { dropMenu.Insert((Gtk.Widget)item.ControlObject, index); - var handler = item.Handler as Menu.IMenuHandler; + var handler = item.Handler as Menu.IMenuHandler; //SetChildAccelGroup(item); } diff --git a/src/Eto.Gtk/Forms/ToolBar/ToolItemHandler.cs b/src/Eto.Gtk/Forms/ToolBar/ToolItemHandler.cs index 26cc37d401..d91ce6cf54 100644 --- a/src/Eto.Gtk/Forms/ToolBar/ToolItemHandler.cs +++ b/src/Eto.Gtk/Forms/ToolBar/ToolItemHandler.cs @@ -10,20 +10,56 @@ public interface IToolBarItemHandler } public abstract class ToolItemHandler : WidgetHandler, ToolItem.IHandler, IToolBarItemHandler - where TControl: Gtk.Widget + where TControl: Gtk.ToolItem where TWidget: ToolItem { bool enabled = true; bool visible = true; + string text; + string toolTip; Image image; protected Gtk.Image GtkImage { get; set; } public abstract void CreateControl(ToolBarHandler handler, int index); - public string Text { get; set; } + public string Text + { + get => text; + set + { + if (text != value) + { + text = value; + SetText(); + } + } + } + + public virtual string ToolTip + { + get => toolTip; + set + { + if (toolTip != value) + { + toolTip = value; + SetToolTip(); + } + } + } + + protected virtual void SetText() + { + if (Control is Gtk.ToolButton button) + button.Label = Text; + } - public string ToolTip { get; set; } + protected virtual void SetToolTip() + { + if (Control is Gtk.ToolButton button) + button.TooltipText = ToolTip; + } public Image Image { diff --git a/src/Eto.Mac/Eto.Mac64.csproj b/src/Eto.Mac/Eto.Mac64.csproj index 6d2d4b96d1..e1c2992e5c 100644 --- a/src/Eto.Mac/Eto.Mac64.csproj +++ b/src/Eto.Mac/Eto.Mac64.csproj @@ -45,6 +45,7 @@ You do not need to use any of the classes of this assembly (unless customizing t + diff --git a/src/Eto.Mac/Eto.XamMac2.csproj b/src/Eto.Mac/Eto.XamMac2.csproj index 073a2a99fa..3a16081460 100644 --- a/src/Eto.Mac/Eto.XamMac2.csproj +++ b/src/Eto.Mac/Eto.XamMac2.csproj @@ -23,6 +23,7 @@ + diff --git a/src/Eto.Mac/Forms/Controls/SegmentedButtonHandler.cs b/src/Eto.Mac/Forms/Controls/SegmentedButtonHandler.cs index 08d0be01c8..802d103d37 100644 --- a/src/Eto.Mac/Forms/Controls/SegmentedButtonHandler.cs +++ b/src/Eto.Mac/Forms/Controls/SegmentedButtonHandler.cs @@ -391,9 +391,9 @@ public void InsertItem(int index, SegmentedItem item) static readonly IntPtr selSetShowsMenuIndicator = Selector.GetHandle("setShowsMenuIndicator:forSegment:"); // 10.13+ - static readonly bool supportsTooltip = ObjCExtensions.InstancesRespondToSelector(selSetToolTipForSegment); - static readonly bool supportsMenuIndicator = ObjCExtensions.InstancesRespondToSelector(selSetShowsMenuIndicator); - + internal static readonly bool supportsTooltip = ObjCExtensions.InstancesRespondToSelector(selSetToolTipForSegment); + internal static readonly bool supportsMenuIndicator = ObjCExtensions.InstancesRespondToSelector(selSetShowsMenuIndicator); + public void SetItem(int index, SegmentedItem item) { Control.SetLabel(item.Text ?? string.Empty, index); diff --git a/src/Eto.Mac/Forms/Menu/MenuHandler.cs b/src/Eto.Mac/Forms/Menu/MenuHandler.cs index cab1249f1a..acb580ad92 100644 --- a/src/Eto.Mac/Forms/Menu/MenuHandler.cs +++ b/src/Eto.Mac/Forms/Menu/MenuHandler.cs @@ -15,6 +15,15 @@ public interface IMenuHandler public class EtoMenu : NSMenu { public bool WorksWhenModal { get; set; } + + public EtoMenu() + { + } + + public EtoMenu(NativeHandle handle) + : base(handle) + { + } } static class MenuHandler diff --git a/src/Eto.Mac/Forms/ToolBar/DropDownToolItemHandler.cs b/src/Eto.Mac/Forms/ToolBar/DropDownToolItemHandler.cs new file mode 100644 index 0000000000..7acdd4ffd8 --- /dev/null +++ b/src/Eto.Mac/Forms/ToolBar/DropDownToolItemHandler.cs @@ -0,0 +1,85 @@ +using System; +using Eto.Drawing; +using Eto.Forms; + +namespace Eto.Mac.Forms.ToolBar +{ + public class DropDownToolItemHandler : ToolItemHandler, DropDownToolItem.IHandler + { + NSMenu menu; + + protected override bool UseButtonStyle => false; + protected override bool UseAction => false; + +#if MACOS || XAMMAC2 + static readonly IntPtr selInitWithItemIdentifier_Handle = Selector.GetHandle("initWithItemIdentifier:"); + + // constructor with identifier isn't available in the version of macOS workload we need yet.. + class EtoMenuToolbarItem : NSMenuToolbarItem + { + public EtoMenuToolbarItem() + { + } + + [Export ("initWithItemIdentifier:")] + public EtoMenuToolbarItem (string itemIdentifier) + : base (NSObjectFlag.Empty) + { + NSApplication.EnsureUIThread (); + if (itemIdentifier == null) + throw new ArgumentNullException ("itemIdentifier"); +#if USE_CFSTRING + var nsitemIdentifier = CFString.CreateNative(itemIdentifier); +#else + var nsitemIdentifier = NSString.CreateNative(itemIdentifier); +#endif + + if (IsDirectBinding) { + Handle = Messaging.IntPtr_objc_msgSend_IntPtr (this.Handle, selInitWithItemIdentifier_Handle, nsitemIdentifier); + } else { + Handle = Messaging.IntPtr_objc_msgSendSuper_IntPtr (this.SuperHandle, selInitWithItemIdentifier_Handle, nsitemIdentifier); + } + NSString.ReleaseNative (nsitemIdentifier); + + } + } + + protected override NSMenuToolbarItem CreateControl() => new EtoMenuToolbarItem(Identifier); +#else + protected override NSMenuToolbarItem CreateControl() => new NSMenuToolbarItem(Identifier); +#endif + + public bool ShowDropArrow + { + get => Control.ShowsIndicator; + set => Control.ShowsIndicator = value; + } + + protected override void Initialize() + { + base.Initialize(); + Control.ShowsIndicator = true; + menu = new NSMenu(); + menu.AutoEnablesItems = true; + menu.ShowsStateColumn = true; + Control.Menu = menu; + // first item is never shown, it's the "title" of the pull down?? weird. + menu.InsertItem(new NSMenuItem(string.Empty), 0); + } + + public void AddMenu(int index, MenuItem item) + { + menu.InsertItem((NSMenuItem)item.ControlObject, index + 1); + } + + public void RemoveMenu(MenuItem item) + { + menu.RemoveItem((NSMenuItem)item.ControlObject); + } + + public void Clear() + { + menu.RemoveAllItems(); + } + } +} diff --git a/src/Eto.Mac/Forms/ToolBar/DropDownToolItemPreCatalinaHandler.cs b/src/Eto.Mac/Forms/ToolBar/DropDownToolItemPreCatalinaHandler.cs new file mode 100644 index 0000000000..52384111e7 --- /dev/null +++ b/src/Eto.Mac/Forms/ToolBar/DropDownToolItemPreCatalinaHandler.cs @@ -0,0 +1,84 @@ +using System; +using Eto.Drawing; +using Eto.Forms; +using Eto.Mac.Forms.Controls; +using Eto.Mac.Forms.Menu; + +namespace Eto.Mac.Forms.ToolBar +{ + /// + /// Drop down handler pre-catalina. This could probably be done a little better. + /// + public class DropDownToolItemPreCatalinaHandler : ToolItemHandler, DropDownToolItem.IHandler + { + ContextMenu contextMenu; + MenuSegmentedItem menuItem; + SegmentedButton segmentedButton; + bool showDropArrow = true; + protected override bool UseButtonStyle => false; + + protected override bool IsButton => true; + + protected override bool UseAction => false; + + public bool ShowDropArrow + { + get => showDropArrow; + set + { + showDropArrow = value; + if (SegmentedButtonHandler.supportsMenuIndicator) + { + var nssegmentedControl = SegmentedButtonHandler.GetControl(segmentedButton); + nssegmentedControl.SetShowsMenuIndicator(value, 0); + } + } + } + + protected override void Initialize() + { + contextMenu = new ContextMenu(); + + menuItem = new MenuSegmentedItem { Menu = contextMenu }; + + segmentedButton = new SegmentedButton(); + segmentedButton.Items.Add(menuItem); + + Control.View = segmentedButton.ToNative(true); + + base.Initialize(); + } + + protected override NSMenuItem CreateMenuFormRepresentation() + { + var menu = base.CreateMenuFormRepresentation(); + menu.Submenu = contextMenu.ToNS(); + return menu; + } + + public void AddMenu(int index, MenuItem item) + { + contextMenu.Items.Insert(index, item); + } + + public void RemoveMenu(MenuItem item) + { + contextMenu.Items.Remove(item); + } + + public void Clear() + { + contextMenu.Items.Clear(); + } + + protected override void SetImage() + { + base.SetImage(); + if (Image is Bitmap bmp) + menuItem.Image = bmp.WithSize(ImageSize, ImageSize); + else if (Image is Icon icon) + menuItem.Image = icon.WithSize(ImageSize, ImageSize); + } + + } +} \ No newline at end of file diff --git a/src/Eto.Mac/Forms/ToolBar/SeparatorToolItemHandler.cs b/src/Eto.Mac/Forms/ToolBar/SeparatorToolItemHandler.cs index 22ab4e516d..dee4c387e3 100644 --- a/src/Eto.Mac/Forms/ToolBar/SeparatorToolItemHandler.cs +++ b/src/Eto.Mac/Forms/ToolBar/SeparatorToolItemHandler.cs @@ -13,6 +13,8 @@ public class SeparatorToolItemHandler : ToolItemHandler false; + protected override bool UseButtonStyle => false; + public override string Identifier { get diff --git a/src/Eto.Mac/Forms/ToolBar/ToolBarHandler.cs b/src/Eto.Mac/Forms/ToolBar/ToolBarHandler.cs index 575a413d41..bb9e4d7640 100644 --- a/src/Eto.Mac/Forms/ToolBar/ToolBarHandler.cs +++ b/src/Eto.Mac/Forms/ToolBar/ToolBarHandler.cs @@ -271,7 +271,7 @@ internal int GetIndex(ToolItem item, bool forInsert = false, bool checkVisible = else if (curitem.Visible) { var nativeItem = nativeItems[idx]; - if (nativeItem.Identifier == GetIdentifier(curitem)) + if (nativeItem.Identifier == GetIdentifier(curitem) || object.Equals(nativeItem, curitem.ControlObject)) idx++; } } diff --git a/src/Eto.Mac/Forms/ToolBar/ToolItemHandler.cs b/src/Eto.Mac/Forms/ToolBar/ToolItemHandler.cs index 656f602d70..6561ec5bcf 100644 --- a/src/Eto.Mac/Forms/ToolBar/ToolItemHandler.cs +++ b/src/Eto.Mac/Forms/ToolBar/ToolItemHandler.cs @@ -30,7 +30,7 @@ public interface IToolBarItemHandler : IToolBarBaseItemHandler NSButton Button { get; } - MacToolBarItemStyle ToolBarItemStyle {get; set;} + MacToolBarItemStyle ToolBarItemStyle { get; set; } } class ToolBarItemHandlerTarget : NSObject @@ -83,8 +83,8 @@ static class ToolItemHandler } public abstract class ToolItemHandler : WidgetHandler, ToolItem.IHandler, IToolBarItemHandler - where TControl: NSToolbarItem - where TWidget: ToolItem + where TControl : NSToolbarItem + where TWidget : ToolItem { Image image; NSButton button; @@ -93,39 +93,47 @@ public abstract class ToolItemHandler : WidgetHandler (toolBarItemStyle == MacToolBarItemStyle.StandardButton) ? 20 : 32; + protected virtual bool UseAction => true; + protected virtual bool UseButtonStyle => true; + MacToolBarItemStyle toolBarItemStyle; public MacToolBarItemStyle ToolBarItemStyle { get { return toolBarItemStyle; } - set { + set + { toolBarItemStyle = value; // set the value first because ButtonSize and ImageSize depend on it. button = null; - if (value == MacToolBarItemStyle.StandardButton || value == MacToolBarItemStyle.LargeButton) { - button = new NSButton { + if (UseButtonStyle && (value == MacToolBarItemStyle.StandardButton || value == MacToolBarItemStyle.LargeButton)) + { + button = new NSButton + { Title = string.Empty, BezelStyle = NSBezelStyle.TexturedRounded, Bordered = toolBarItemStyle == MacToolBarItemStyle.StandardButton, // no border or bezel in the large button style Frame = new CGRect(CGPoint.Empty, ButtonSize), Target = Control.Target, Action = Control.Action, + Image = Control.Image }; if (value == MacToolBarItemStyle.LargeButton) - button.SetButtonType (NSButtonType.MomentaryChange); // prevents a flash in the large button view. See the comment at the bottom of http://yellowfieldtechnologies.wordpress.com/2011/11/18/nspopover-from-nstoolbaritem/#comments + button.SetButtonType(NSButtonType.MomentaryChange); // prevents a flash in the large button view. See the comment at the bottom of http://yellowfieldtechnologies.wordpress.com/2011/11/18/nspopover-from-nstoolbaritem/#comments Control.View = button; } - SetImage (); + SetImage(); } } @@ -140,7 +148,12 @@ public Color? Tint } } - public virtual string Identifier { get; set; } + string _identifier; + public virtual string Identifier + { + get => _identifier ?? (_identifier = Guid.NewGuid().ToString()); + set => _identifier = value; + } protected override TControl CreateControl() => (TControl)new NSToolbarItem(Identifier); @@ -150,24 +163,36 @@ public Color? Tint protected override void Initialize() { - this.Identifier = Guid.NewGuid().ToString(); if (IsButton) { - Control.Target = new ToolBarItemHandlerTarget { Handler = this }; - Control.Action = ToolItemHandler.selAction; + if (UseAction) + { + Control.Target = new ToolBarItemHandlerTarget { Handler = this }; + Control.Action = ToolItemHandler.selAction; + } Control.Autovalidates = false; Control.Label = string.Empty; - menuItem = new NSMenuItem(string.Empty) + Control.Enabled = true; + + ToolBarItemStyle = DefaultStyle; + menuItem = CreateMenuFormRepresentation(); + Control.MenuFormRepresentation = menuItem; + } + base.Initialize(); + } + + protected virtual NSMenuItem CreateMenuFormRepresentation() + { + if (Control != null) + { + return new NSMenuItem(string.Empty) { Action = Control.Action, Target = Control.Target }; - Control.MenuFormRepresentation = menuItem; - Control.Enabled = true; - this.ToolBarItemStyle = DefaultStyle; } - base.Initialize(); + return null; } [Obsolete("Use ToolBarItemStyle and Tint properties instead")] @@ -189,7 +214,7 @@ public virtual void InvokeButton() { } - public string Text + public virtual string Text { get => Control?.Label; set @@ -197,7 +222,13 @@ public string Text if (Control == null) return; - Control.Label = menuItem.Title = value ?? string.Empty; + var text = value ?? string.Empty; + Control.Label = text; + if (menuItem != null) + { + menuItem.Title = text; + Control.MenuFormRepresentation = menuItem; + } } } @@ -207,7 +238,10 @@ public string ToolTip set { if (menuItem != null) + { menuItem.ToolTip = value ?? string.Empty; + Control.MenuFormRepresentation = menuItem; + } if (button != null) button.ToolTip = value ?? string.Empty; } @@ -223,7 +257,7 @@ public Image Image } } - void SetImage() + protected virtual void SetImage() { if (Control == null) return; @@ -231,7 +265,7 @@ void SetImage() if (tint != null && nsimage != null) nsimage = nsimage.Tint(tint.Value.ToNSUI()); Control.Image = nsimage; - + var menu = Control.MenuFormRepresentation; if (menu != null) menu.Image = nsimage; diff --git a/src/Eto.Mac/Platform.cs b/src/Eto.Mac/Platform.cs index 71b789bdcb..3bfa23a6e4 100644 --- a/src/Eto.Mac/Platform.cs +++ b/src/Eto.Mac/Platform.cs @@ -233,6 +233,10 @@ public static void AddTo(Eto.Platform p) p.Add(() => new RadioToolItemHandler()); p.Add(() => new SeparatorToolItemHandler()); p.Add(() => new ButtonToolItemHandler()); + if (MacVersion.IsAtLeast(10, 15)) + p.Add(() => new DropDownToolItemHandler()); + else + p.Add(() => new DropDownToolItemPreCatalinaHandler()); p.Add(() => new ToolBarHandler()); // Forms diff --git a/src/Eto.Wpf/Forms/ToolBar/DropDownToolItemHandler.cs b/src/Eto.Wpf/Forms/ToolBar/DropDownToolItemHandler.cs index 30fdc7ab41..3e74ab9cd5 100644 --- a/src/Eto.Wpf/Forms/ToolBar/DropDownToolItemHandler.cs +++ b/src/Eto.Wpf/Forms/ToolBar/DropDownToolItemHandler.cs @@ -31,7 +31,7 @@ public DropDownToolItemHandler () root.SubmenuOpened += Control_Click; sw.Automation.AutomationProperties.SetLabeledBy(Control, label); } - + private void Control_Click(object sender, RoutedEventArgs e) { // WPF raises this event for all child items as well as the root menu, so check sender diff --git a/src/Eto/Forms/Menu/CheckMenuItem.cs b/src/Eto/Forms/Menu/CheckMenuItem.cs index cc50b42b5f..e285a4d783 100644 --- a/src/Eto/Forms/Menu/CheckMenuItem.cs +++ b/src/Eto/Forms/Menu/CheckMenuItem.cs @@ -23,6 +23,15 @@ public CheckMenuItem() { } + /// + /// Initializes a new instance of the class with the specified handler. + /// + /// Event handler to call when the property changes. + public CheckMenuItem(EventHandler checkedChanged) + { + CheckedChanged += checkedChanged; + } + /// /// Initializes a new instance of the class with the specified command. /// diff --git a/src/Eto/Forms/ToolBar/DropDownToolItem.cs b/src/Eto/Forms/ToolBar/DropDownToolItem.cs index 1ef240abb3..5a83af1ccf 100644 --- a/src/Eto/Forms/ToolBar/DropDownToolItem.cs +++ b/src/Eto/Forms/ToolBar/DropDownToolItem.cs @@ -1,3 +1,5 @@ +using System.ComponentModel; + namespace Eto.Forms { /// @@ -8,7 +10,18 @@ public class DropDownToolItem : ToolItem { MenuItemCollection items; - new IHandler Handler { get { return (IHandler)base.Handler; } } + new IHandler Handler => (IHandler)base.Handler; + + /// + /// Gets or sets a value indicating that the drop arrow should be shown + /// + /// true to show the drop arrow, false to hide it + [DefaultValue(true)] + public bool ShowDropArrow + { + get => Handler.ShowDropArrow; + set => Handler.ShowDropArrow = value; + } /// /// Initializes a new instance of the class. @@ -21,13 +34,18 @@ public DropDownToolItem() /// Gets the collection of menu items /// /// The menu items - public MenuItemCollection Items { get { return items ?? (items = new MenuItemCollection(Handler, null)); } } + public MenuItemCollection Items => items ?? (items = new MenuItemCollection(Handler, null)); /// /// Handler for the . /// public new interface IHandler : ToolItem.IHandler, Menu.ISubmenuHandler { + /// + /// Gets or sets a value indicating that the drop arrow should be shown + /// + /// true to show the drop arrow, false to hide it + bool ShowDropArrow { get; set; } } } } diff --git a/test/Eto.Test/MainForm.cs b/test/Eto.Test/MainForm.cs index 779b0c6093..b5c8639440 100644 --- a/test/Eto.Test/MainForm.cs +++ b/test/Eto.Test/MainForm.cs @@ -333,13 +333,6 @@ void CreateMenuToolBar() ToolBar = new ToolBar(); ToolBar.Items.Add(about); - if (Platform.Supports()) - { - var dropItem = new DropDownToolItem { Text = "Dropdown", Image = TestIcons.TestImage }; - dropItem.Items.Add(new ButtonMenuItem { Text = "Subitem 1", Items = { new ButtonMenuItem { Text = "Nested subitem 1" } } }); - dropItem.Items.Add(new ButtonMenuItem { Text = "Subitem 2", Image = TestIcons.TestIcon }); - ToolBar.Items.Add(LogEvents(dropItem)); - } if (Platform.Supports()) { ToolBar.Items.Add(new SeparatorToolItem { Type = SeparatorToolItemType.Divider }); @@ -366,6 +359,19 @@ void CreateMenuToolBar() invisibleButton.Visible = !invisibleButton.Visible; sep.Visible = invisibleButton.Visible; }; + + if (Platform.Supports()) + { + var dropItem = new DropDownToolItem { Text = "Dropdown", Image = TestIcons.TestImage }; + dropItem.Items.Add(new SubMenuItem { Text = "Subitem 1", Items = { new ButtonMenuItem { Text = "Nested subitem 1" } } }); + dropItem.Items.Add(new ButtonMenuItem { Text = "Button 2", Image = TestIcons.TestIcon }); + dropItem.Items.Add(new CheckMenuItem { Text = "Check 3" }); + dropItem.Items.Add(new ButtonMenuItem { Text = "Disabled Button", Image = TestIcons.TestIcon, Enabled = false }); + dropItem.Items.Add(new CheckMenuItem((sender, e) => { + dropItem.ShowDropArrow = ((CheckMenuItem)sender).Checked == true; + }) { Text = "Toggle ShowDropArrow", Checked = dropItem.ShowDropArrow }); + ToolBar.Items.Add(LogEvents(dropItem)); + } } }