diff --git a/Terminal.Gui/View/IDesignable.cs b/Terminal.Gui/View/IDesignable.cs new file mode 100644 index 0000000000..febaaf45ea --- /dev/null +++ b/Terminal.Gui/View/IDesignable.cs @@ -0,0 +1,23 @@ +namespace Terminal.Gui; + +/// +/// Interface declaring common functionality useful for designer implementations. +/// +public interface IDesignable +{ + /// + /// Causes the View to enable design-time mode. This typically means that the view will load demo data and + /// be configured to allow for design-time manipulation. + /// + /// Optional arbitrary, View-specific, context. + /// A non-null type for . + /// if the view successfully loaded demo data. + public bool EnableForDesign (ref readonly TContext context) where TContext : notnull => EnableForDesign (); + + /// + /// Causes the View to enable design-time mode. This typically means that the view will load demo data and + /// be configured to allow for design-time manipulation. + /// + /// if the view successfully loaded demo data. + public bool EnableForDesign () => false; +} diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index e811b90c82..f6acd3338f 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -27,7 +27,7 @@ namespace Terminal.Gui; /// invoked repeatedly while the button is pressed. /// /// -public class Button : View +public class Button : View, IDesignable { private readonly Rune _leftBracket; private readonly Rune _leftDefault; @@ -190,4 +190,12 @@ protected override void UpdateTextFormatterText () } } } + + /// + public bool EnableForDesign () + { + Title = "_Button"; + + return true; + } } \ No newline at end of file diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index 2495199853..b98cceb7ea 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -12,7 +12,7 @@ namespace Terminal.Gui; /// Provides a drop-down list of items the user can select from. -public class ComboBox : View +public class ComboBox : View, IDesignable { private readonly ComboListView _listview; private readonly int _minimumHeight = 2; @@ -994,4 +994,14 @@ private void SetInitialProperties (ComboBox container, bool hideDropdownListOnCl AddCommand (Command.LineUp, () => _container.MoveUpList ()); } } + + /// + public bool EnableForDesign () + { + var source = new ObservableCollection (["Combo Item 1", "Combo Item two", "Combo Item Quattro", "Last Combo Item"]); + SetSource (source); + Height = Dim.Auto (DimAutoStyle.Content, minimumContentDim: source.Count + 1); + + return true; + } } diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 2e72873345..1fc106031b 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -101,7 +101,7 @@ void Render ( /// first item that starts with what the user types will be selected. /// /// -public class ListView : View +public class ListView : View, IDesignable { private bool _allowsMarking; private bool _allowsMultipleSelection = true; @@ -921,6 +921,15 @@ public void ResumeSuspendCollectionChangedEvent () Source.SuspendCollectionChangedEvent = false; } } + + /// + public bool EnableForDesign () + { + var source = new ListWrapper (["List Item 1", "List Item two", "List Item Quattro", "Last List Item"]); + Source = source; + + return true; + } } /// diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index 9111f6bc21..b08b263b51 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -34,7 +34,7 @@ namespace Terminal.Gui; /// duplicates a shortcut (e.g. _File and Alt-F), the hot key wins. /// /// -public class MenuBar : View +public class MenuBar : View, IDesignable { // Spaces before the Title private static readonly int _leftPadding = 1; @@ -1591,4 +1591,177 @@ private MenuBar GetMouseGrabViewInstance (View view) } #endregion Mouse Handling + + + /// + public bool EnableForDesign (in TContext context) where TContext : notnull + { + if (context is not Func actionFn) + { + actionFn = (s) => true; + } + + Menus = + [ + new MenuBarItem ( + "_File", + new MenuItem [] + { + new ( + "_New", + "", + () => actionFn ("New"), + null, + null, + KeyCode.CtrlMask | KeyCode.N + ), + new ( + "_Open", + "", + () => actionFn ("Open"), + null, + null, + KeyCode.CtrlMask | KeyCode.O + ), + new ( + "_Save", + "", + () => actionFn ("Save"), + null, + null, + KeyCode.CtrlMask | KeyCode.S + ), + null, + + // Don't use Application.Quit so we can disambiguate between quitting and closing the toplevel + new ( + "_Quit", + "", + () => actionFn ("Quit"), + null, + null, + KeyCode.CtrlMask | KeyCode.Q + ) + } + ), + new MenuBarItem ( + "_Edit", + new MenuItem [] + { + new ( + "_Copy", + "", + () => actionFn ("Copy"), + null, + null, + KeyCode.CtrlMask | KeyCode.C + ), + new ( + "C_ut", + "", + () => actionFn ("Cut"), + null, + null, + KeyCode.CtrlMask | KeyCode.X + ), + new ( + "_Paste", + "", + () => actionFn ("Paste"), + null, + null, + KeyCode.CtrlMask | KeyCode.V + ), + new MenuBarItem ( + "_Find and Replace", + new MenuItem [] + { + new ( + "F_ind", + "", + () => actionFn ("Find"), + null, + null, + KeyCode.CtrlMask | KeyCode.F + ), + new ( + "_Replace", + "", + () => actionFn ("Replace"), + null, + null, + KeyCode.CtrlMask | KeyCode.H + ), + new MenuBarItem ( + "_3rd Level", + new MenuItem [] + { + new ( + "_1st", + "", + () => actionFn ( + "1" + ), + null, + null, + KeyCode.F1 + ), + new ( + "_2nd", + "", + () => actionFn ( + "2" + ), + null, + null, + KeyCode.F2 + ) + } + ), + new MenuBarItem ( + "_4th Level", + new MenuItem [] + { + new ( + "_5th", + "", + () => actionFn ( + "5" + ), + null, + null, + KeyCode.CtrlMask + | KeyCode.D5 + ), + new ( + "_6th", + "", + () => actionFn ( + "6" + ), + null, + null, + KeyCode.CtrlMask + | KeyCode.D6 + ) + } + ) + } + ), + new ( + "_Select All", + "", + () => actionFn ("Select All"), + null, + null, + KeyCode.CtrlMask + | KeyCode.ShiftMask + | KeyCode.S + ) + } + ), + new MenuBarItem ("_About", "Top-Level", () => actionFn ("About")) + ]; + return true; + } } diff --git a/Terminal.Gui/Views/ProgressBar.cs b/Terminal.Gui/Views/ProgressBar.cs index 80b7545b7c..b7afd2459a 100644 --- a/Terminal.Gui/Views/ProgressBar.cs +++ b/Terminal.Gui/Views/ProgressBar.cs @@ -35,7 +35,7 @@ public enum ProgressBarFormat /// method is called. Call repeatedly as progress is made. /// /// -public class ProgressBar : View +public class ProgressBar : View, IDesignable { private int [] _activityPos; private bool _bidirectionalMarquee = true; @@ -277,4 +277,13 @@ private void SetInitialProperties () _fraction = 0; Initialized += ProgressBar_Initialized; } + + /// + public bool EnableForDesign () + { + Width = Dim.Fill (); + Height = Dim.Auto (DimAutoStyle.Text, minimumContentDim: 1); + Fraction = 0.75f; + return true; + } } diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs index aaaf8b9812..f0dc5174be 100644 --- a/Terminal.Gui/Views/RadioGroup.cs +++ b/Terminal.Gui/Views/RadioGroup.cs @@ -1,7 +1,7 @@ namespace Terminal.Gui; /// Displays a group of labels each with a selected indicator. Only one of those can be selected at a given time. -public class RadioGroup : View +public class RadioGroup : View, IDesignable { private int _cursor; private List<(int pos, int length)> _horizontal; @@ -229,32 +229,6 @@ public string [] RadioLabels } } - /// - public override string Text - { - get - { - if (_radioLabels.Count == 0) - { - return string.Empty; - } - - // Return labels as a CSV string - return string.Join (",", _radioLabels); - } - set - { - if (string.IsNullOrEmpty (value)) - { - RadioLabels = []; - } - else - { - RadioLabels = value.Split (',').Select (x => x.Trim ()).ToArray (); - } - } - } - /// The currently selected item from the list of radio labels /// The selected. public int SelectedItem @@ -487,4 +461,11 @@ private void SetContentSize () break; } } + + /// + public bool EnableForDesign () + { + RadioLabels = new [] { "Option _1", "Option _2", "Option _3" }; + return true; + } } diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs index 9c74cc5292..5e0b8d45d1 100644 --- a/UICatalog/Scenarios/AllViewsTester.cs +++ b/UICatalog/Scenarios/AllViewsTester.cs @@ -348,49 +348,17 @@ private View CreateClass (Type type) view.ColorScheme = Colors.ColorSchemes ["Base"]; } - // If the view supports a Text property, set it so we have something to look at - if (view.GetType ().GetProperty ("Text") != null) + if (view is IDesignable designable) { - try - { - view.GetType () - .GetProperty ("Text") - ?.GetSetMethod () - ?.Invoke (view, new [] { _demoText }); - } - catch (TargetInvocationException e) - { - MessageBox.ErrorQuery ("Exception", e.InnerException.Message, "Ok"); - view = null; - } - } - - // If the view supports a Title property, set it so we have something to look at - if (view != null && view.GetType ().GetProperty ("Title") != null) - { - if (view.GetType ().GetProperty ("Title")!.PropertyType == typeof (string)) - { - view?.GetType () - .GetProperty ("Title") - ?.GetSetMethod () - ?.Invoke (view, new [] { "Test Title" }); - } - else - { - view?.GetType () - .GetProperty ("Title") - ?.GetSetMethod () - ?.Invoke (view, new [] { "Test Title" }); - } + designable.EnableForDesign (ref _demoText); } - - // If the view supports a Source property, set it so we have something to look at - if (view != null && view.GetType ().GetProperty ("Source") != null && view.GetType ().GetProperty ("Source").PropertyType == typeof (IListDataSource)) + else { - var source = new ListWrapper (["Test Text #1", "Test Text #2", "Test Text #3"]); - view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, [source]); + view.Text = _demoText; + view.Title = "_Test Title"; } + // TODO: Add IOrientation so this doesn't require reflection // If the view supports a Title property, set it so we have something to look at if (view?.GetType ().GetProperty ("Orientation") is { } prop) { diff --git a/UICatalog/Scenarios/MenuBarScenario.cs b/UICatalog/Scenarios/MenuBarScenario.cs index 4dce938b4d..3e0c2a6f44 100644 --- a/UICatalog/Scenarios/MenuBarScenario.cs +++ b/UICatalog/Scenarios/MenuBarScenario.cs @@ -3,7 +3,7 @@ namespace UICatalog.Scenarios; -[ScenarioMetadata ("MenuBar", "Demonstrates the MenuBar using the same menu used in unit tests.")] +[ScenarioMetadata ("MenuBar", "Demonstrates the MenuBar using the demo menu.")] [ScenarioCategory ("Controls")] [ScenarioCategory ("Menus")] public class MenuBarScenario : Scenario @@ -14,186 +14,6 @@ public class MenuBarScenario : Scenario private Label _lastAction; private Label _lastKey; - /// - /// This method creates at test menu bar. It is called by the MenuBar unit tests, so it's possible to do both unit - /// testing and user-experience testing with the same setup. - /// - /// - /// - public static MenuBar CreateTestMenu (Func actionFn) - { - // TODO: add a disabled menu item to this - var mb = new MenuBar - { - Menus = - [ - new MenuBarItem ( - "_File", - new MenuItem [] - { - new ( - "_New", - "", - () => actionFn ("New"), - null, - null, - KeyCode.CtrlMask | KeyCode.N - ), - new ( - "_Open", - "", - () => actionFn ("Open"), - null, - null, - KeyCode.CtrlMask | KeyCode.O - ), - new ( - "_Save", - "", - () => actionFn ("Save"), - null, - null, - KeyCode.CtrlMask | KeyCode.S - ), - null, - - // Don't use Application.Quit so we can disambiguate between quitting and closing the toplevel - new ( - "_Quit", - "", - () => actionFn ("Quit"), - null, - null, - KeyCode.CtrlMask | KeyCode.Q - ) - } - ), - new MenuBarItem ( - "_Edit", - new MenuItem [] - { - new ( - "_Copy", - "", - () => actionFn ("Copy"), - null, - null, - KeyCode.CtrlMask | KeyCode.C - ), - new ( - "C_ut", - "", - () => actionFn ("Cut"), - null, - null, - KeyCode.CtrlMask | KeyCode.X - ), - new ( - "_Paste", - "", - () => actionFn ("Paste"), - null, - null, - KeyCode.CtrlMask | KeyCode.V - ), - new MenuBarItem ( - "_Find and Replace", - new MenuItem [] - { - new ( - "F_ind", - "", - () => actionFn ("Find"), - null, - null, - KeyCode.CtrlMask | KeyCode.F - ), - new ( - "_Replace", - "", - () => actionFn ("Replace"), - null, - null, - KeyCode.CtrlMask | KeyCode.H - ), - new MenuBarItem ( - "_3rd Level", - new MenuItem [] - { - new ( - "_1st", - "", - () => actionFn ( - "1" - ), - null, - null, - KeyCode.F1 - ), - new ( - "_2nd", - "", - () => actionFn ( - "2" - ), - null, - null, - KeyCode.F2 - ) - } - ), - new MenuBarItem ( - "_4th Level", - new MenuItem [] - { - new ( - "_5th", - "", - () => actionFn ( - "5" - ), - null, - null, - KeyCode.CtrlMask - | KeyCode.D5 - ), - new ( - "_6th", - "", - () => actionFn ( - "6" - ), - null, - null, - KeyCode.CtrlMask - | KeyCode.D6 - ) - } - ) - } - ), - new ( - "_Select All", - "", - () => actionFn ("Select All"), - null, - null, - KeyCode.CtrlMask - | KeyCode.ShiftMask - | KeyCode.S - ) - } - ), - new MenuBarItem ("_About", "Top-Level", () => actionFn ("About")) - ] - }; - mb.UseKeysUpDownAsKeysLeftRight = true; - mb.Key = KeyCode.F9; - mb.Title = "TestMenuBar"; - - return mb; - } - public override void Main () { // Init @@ -239,14 +59,19 @@ public override void Main () _focusedView = new Label { X = Pos.Right (label), Y = Pos.Top (label), Text = "" }; appWindow.Add (_focusedView); - MenuBar menuBar = CreateTestMenu ( - s => - { - _lastAction.Text = s; + MenuBar menuBar = new MenuBar (); + menuBar.UseKeysUpDownAsKeysLeftRight = true; + menuBar.Key = KeyCode.F9; + menuBar.Title = "TestMenuBar"; + + bool fnAction (string s) + { + _lastAction.Text = s; + + return true; + } - return true; - } - ); + menuBar.EnableForDesign ((Func)fnAction); menuBar.MenuOpening += (s, e) => { diff --git a/UICatalog/Scenarios/TextFormatterDemo.cs b/UICatalog/Scenarios/TextFormatterDemo.cs index ebf3659fbf..85938f65b0 100644 --- a/UICatalog/Scenarios/TextFormatterDemo.cs +++ b/UICatalog/Scenarios/TextFormatterDemo.cs @@ -62,19 +62,7 @@ public override void Main () }; app.Add (unicodeCheckBox); - - static IEnumerable GetUniqueEnumValues () where T : Enum - { - var values = new HashSet (); - foreach (T v in Enum.GetValues (typeof (T))) - { - if (values.Add (v)) - { - yield return v; - } - } - } - + List alignments = new () { Alignment.Start, Alignment.End, Alignment.Center, Alignment.Fill }; Label [] singleLines = new Label [alignments.Count]; Label [] multipleLines = new Label [alignments.Count]; diff --git a/UnitTests/Views/MenuBarTests.cs b/UnitTests/Views/MenuBarTests.cs index 8be35667f3..f7804dd426 100644 --- a/UnitTests/Views/MenuBarTests.cs +++ b/UnitTests/Views/MenuBarTests.cs @@ -1253,14 +1253,16 @@ params KeyCode [] keys MenuItem mbiCurrent = null; MenuItem miCurrent = null; - MenuBar menu = MenuBarScenario.CreateTestMenu ( - s => + MenuBar menu = new MenuBar (); + menu.EnableForDesign ( + new Func (s => { - miAction = s; + miAction = s as string; return true; - } - ); + }) + ); + menu.Key = KeyCode.F9; menu.MenuOpening += (s, e) => mbiCurrent = e.CurrentMenu; menu.MenuOpened += (s, e) => { miCurrent = e.MenuItem; }; @@ -1301,14 +1303,16 @@ public void KeyBindings_Shortcut_Commands (string expectedAction, params KeyCode MenuItem mbiCurrent = null; MenuItem miCurrent = null; - MenuBar menu = MenuBarScenario.CreateTestMenu ( - s => + MenuBar menu = new MenuBar (); + menu.EnableForDesign ( + new Func (s => { - miAction = s; + miAction = s as string; return true; - } - ); + }) + ); + menu.Key = KeyCode.F9; menu.MenuOpening += (s, e) => mbiCurrent = e.CurrentMenu; menu.MenuOpened += (s, e) => { miCurrent = e.MenuItem; };