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