diff --git a/Examples/UICatalog/Scenarios/DimAutoDemo.cs b/Examples/UICatalog/Scenarios/DimAutoDemo.cs index ab97889d0..8fdef585e 100644 --- a/Examples/UICatalog/Scenarios/DimAutoDemo.cs +++ b/Examples/UICatalog/Scenarios/DimAutoDemo.cs @@ -179,20 +179,20 @@ public class DimAutoDemo : Scenario { var sliderFrameView = new FrameView { - Title = "Slider - Example of a DimAuto View", + Title = "LinearRange - Example of a DimAuto View", }; List options = new () { "One", "Two", "Three", "Four" }; - Slider slider = new (options) + LinearRange linearRange = new (options) { X = 0, Y = 0, - Type = SliderType.Multiple, + Type = LinearRangeType.Multiple, AllowEmpty = false, BorderStyle = LineStyle.Double, - Title = "_Slider" + Title = "_LinearRange" }; - sliderFrameView.Add (slider); + sliderFrameView.Add (linearRange); return sliderFrameView; } diff --git a/Examples/UICatalog/Scenarios/LinearRanges.cs b/Examples/UICatalog/Scenarios/LinearRanges.cs new file mode 100644 index 000000000..eb3c9ddb5 --- /dev/null +++ b/Examples/UICatalog/Scenarios/LinearRanges.cs @@ -0,0 +1,615 @@ +using System.Collections.ObjectModel; +using System.Text; + +namespace UICatalog.Scenarios; + +[ScenarioMetadata ("LinearRanges", "Demonstrates the LinearRange view.")] +[ScenarioCategory ("Controls")] +public class LinearRanges : Scenario +{ + public override void Main () + { + Application.Init (); + using IApplication app = Application.Instance; + + using Window mainWindow = new (); + mainWindow.Title = GetQuitKeyAndName (); + + MakeSliders ( + mainWindow, + [ + 500, + 1000, + 1500, + 2000, + 2500, + 3000, + 3500, + 4000, + 4500, + 5000 + ] + ); + + FrameView configView = new () + { + Title = "Confi_guration", + X = Pos.Percent (50), + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill (), + SchemeName = "Dialog" + }; + + mainWindow.Add (configView); + + #region Config LinearRange + + LinearRange optionsSlider = new () + { + Title = "Options", + X = 0, + Y = 0, + Width = Dim.Fill (), + Type = LinearRangeType.Multiple, + AllowEmpty = true, + BorderStyle = LineStyle.Single + }; + + optionsSlider.Style.SetChar = optionsSlider.Style.SetChar with { Attribute = new Attribute (Color.BrightGreen, Color.Black) }; + optionsSlider.Style.LegendAttributes.SetAttribute = new Attribute (Color.Green, Color.Black); + + optionsSlider.Options = + [ + new () { Legend = "Legends" }, + new () { Legend = "RangeAllowSingle" }, + new () { Legend = "EndSpacing" }, + new () { Legend = "DimAuto" } + ]; + + configView.Add (optionsSlider); + + optionsSlider.OptionsChanged += OnOptionsSliderOnOptionsChanged; + optionsSlider.SetOption (0); // Legends + optionsSlider.SetOption (1); // RangeAllowSingle + optionsSlider.SetOption (3); // DimAuto + + CheckBox dimAutoUsesMin = new () + { + Text = "Use minimum size (vs. ideal)", + X = 0, + Y = Pos.Bottom (optionsSlider) + }; + + dimAutoUsesMin.CheckedStateChanging += (_, _) => + { + foreach (LinearRange s in mainWindow.SubViews.OfType ()) + { + s.UseMinimumSize = !s.UseMinimumSize; + } + }; + configView.Add (dimAutoUsesMin); + + #region LinearRange Orientation LinearRange + + LinearRange orientationSlider = new (new () { "Horizontal", "Vertical" }) + { + Title = "LinearRange Orientation", + X = 0, + Y = Pos.Bottom (dimAutoUsesMin) + 1, + BorderStyle = LineStyle.Single + }; + + orientationSlider.SetOption (0); + + configView.Add (orientationSlider); + + orientationSlider.OptionsChanged += OnOrientationSliderOnOptionsChanged; + + #endregion LinearRange Orientation LinearRange + + #region Legends Orientation LinearRange + + LinearRange legendsOrientationSlider = new (["Horizontal", "Vertical"]) + { + Title = "Legends Orientation", + X = 0, + Y = Pos.Bottom (orientationSlider) + 1, + BorderStyle = LineStyle.Single + }; + + legendsOrientationSlider.SetOption (0); + + configView.Add (legendsOrientationSlider); + + legendsOrientationSlider.OptionsChanged += OnLegendsOrientationSliderOnOptionsChanged; + + #endregion Legends Orientation LinearRange + + #region Spacing Options + + FrameView spacingOptions = new () + { + Title = "Spacing Options", + X = Pos.Right (orientationSlider), + Y = Pos.Top (orientationSlider), + Width = Dim.Fill (), + Height = Dim.Auto (), + BorderStyle = LineStyle.Single + }; + + Label label = new () + { + Text = "Min _Inner Spacing:" + }; + + NumericUpDown innerSpacingUpDown = new () + { + X = Pos.Right (label) + 1 + }; + + innerSpacingUpDown.Value = mainWindow.SubViews.OfType ().First ().MinimumInnerSpacing; + + innerSpacingUpDown.ValueChanging += (_, e) => + { + if (e.NewValue < 0) + { + e.Cancel = true; + + return; + } + + foreach (LinearRange s in mainWindow.SubViews.OfType ()) + { + s.MinimumInnerSpacing = e.NewValue; + } + }; + + spacingOptions.Add (label, innerSpacingUpDown); + configView.Add (spacingOptions); + + #endregion + + #region Color LinearRange + + foreach (LinearRange s in mainWindow.SubViews.OfType ()) + { + s.Style.OptionChar = s.Style.OptionChar with { Attribute = mainWindow.GetAttributeForRole (VisualRole.Normal) }; + s.Style.SetChar = s.Style.SetChar with { Attribute = mainWindow.GetAttributeForRole (VisualRole.Normal) }; + s.Style.LegendAttributes.SetAttribute = mainWindow.GetAttributeForRole (VisualRole.Normal); + s.Style.RangeChar = s.Style.RangeChar with { Attribute = mainWindow.GetAttributeForRole (VisualRole.Normal) }; + } + + LinearRange<(Color, Color)> sliderFgColor = new () + { + Title = "FG Color", + X = 0, + Y = Pos.Bottom (legendsOrientationSlider) + + 1, + Type = LinearRangeType.Single, + BorderStyle = LineStyle.Single, + AllowEmpty = false, + Orientation = Orientation.Vertical, + LegendsOrientation = Orientation.Horizontal, + MinimumInnerSpacing = 0, + UseMinimumSize = true + }; + + sliderFgColor.Style.SetChar = sliderFgColor.Style.SetChar with { Attribute = new Attribute (Color.BrightGreen, Color.Black) }; + sliderFgColor.Style.LegendAttributes.SetAttribute = new Attribute (Color.Green, Color.Blue); + + List> colorOptions = []; + + colorOptions.AddRange ( + from colorIndex in Enum.GetValues () + let colorName = colorIndex.ToString () + select new LinearRangeOption<(Color, Color)> + { Data = (new (colorIndex), new (colorIndex)), Legend = colorName, LegendAbbr = (Rune)colorName [0] }); + + sliderFgColor.Options = colorOptions; + + configView.Add (sliderFgColor); + + sliderFgColor.OptionsChanged += OnSliderFgColorOnOptionsChanged; + + LinearRange<(Color, Color)> sliderBgColor = new () + { + Title = "BG Color", + X = Pos.Right (sliderFgColor), + Y = Pos.Top (sliderFgColor), + Type = LinearRangeType.Single, + BorderStyle = LineStyle.Single, + AllowEmpty = false, + Orientation = Orientation.Vertical, + LegendsOrientation = Orientation.Horizontal, + MinimumInnerSpacing = 0, + UseMinimumSize = true + }; + + sliderBgColor.Style.SetChar = sliderBgColor.Style.SetChar with { Attribute = new Attribute (Color.BrightGreen, Color.Black) }; + sliderBgColor.Style.LegendAttributes.SetAttribute = new Attribute (Color.Green, Color.Blue); + + sliderBgColor.Options = colorOptions; + + configView.Add (sliderBgColor); + + sliderBgColor.OptionsChanged += (_, e) => + { + if (e.Options.Count == 0) + { + return; + } + + (Color, Color) data = e.Options.First ().Value.Data; + + foreach (LinearRange s in mainWindow.SubViews.OfType ()) + { + s.SetScheme ( + new (s.GetScheme ()) + { + Normal = new ( + s.GetAttributeForRole (VisualRole.Normal).Foreground, + data.Item2 + ) + }); + } + }; + + #endregion Color LinearRange + + #endregion Config LinearRange + + ObservableCollection eventSource = []; + + ListView eventLog = new () + { + X = Pos.Right (sliderBgColor), + Y = Pos.Bottom (spacingOptions), + Width = Dim.Fill (), + Height = Dim.Fill (), + SchemeName = "Runnable", + Source = new ListWrapper (eventSource) + }; + configView.Add (eventLog); + + foreach (View view in mainWindow.SubViews.Where (v => v is LinearRange)!) + { + var slider = (LinearRange)view; + + slider.Accepting += (_, args) => + { + eventSource.Add ($"Accept: {string.Join (",", slider.GetSetOptions ())}"); + eventLog.MoveDown (); + args.Handled = true; + }; + + slider.OptionsChanged += (_, args) => + { + eventSource.Add ($"OptionsChanged: {string.Join (",", slider.GetSetOptions ())}"); + eventLog.MoveDown (); + args.Cancel = true; + }; + } + + mainWindow.FocusDeepest (NavigationDirection.Forward, null); + + app.Run (mainWindow); + + return; + + void OnSliderFgColorOnOptionsChanged (object _, LinearRangeEventArgs<(Color, Color)> e) + { + if (e.Options.Count == 0) + { + return; + } + + (Color, Color) data = e.Options.First ().Value.Data; + + foreach (LinearRange s in mainWindow.SubViews.OfType ()) + { + s.SetScheme ( + new (s.GetScheme ()) + { + Normal = new ( + data.Item2, + s.GetAttributeForRole (VisualRole.Normal).Background, + s.GetAttributeForRole (VisualRole.Normal).Style) + }); + + s.Style.OptionChar = s.Style.OptionChar with + { + Attribute = new Attribute ( + data.Item1, + s.GetAttributeForRole (VisualRole.Normal).Background, + s.GetAttributeForRole (VisualRole.Normal).Style) + }; + + s.Style.SetChar = s.Style.SetChar with + { + Attribute = new Attribute ( + data.Item1, + s.Style.SetChar.Attribute?.Background + ?? s.GetAttributeForRole (VisualRole.Normal).Background, + s.Style.SetChar.Attribute?.Style + ?? s.GetAttributeForRole (VisualRole.Normal).Style) + }; + + s.Style.LegendAttributes.SetAttribute = new Attribute ( + data.Item1, + s.GetAttributeForRole (VisualRole.Normal).Background, + s.GetAttributeForRole (VisualRole.Normal).Style); + + s.Style.RangeChar = s.Style.RangeChar with + { + Attribute = new Attribute ( + data.Item1, + s.GetAttributeForRole (VisualRole.Normal).Background, + s.GetAttributeForRole (VisualRole.Normal).Style) + }; + + s.Style.SpaceChar = s.Style.SpaceChar with + { + Attribute = new Attribute ( + data.Item1, + s.GetAttributeForRole (VisualRole.Normal).Background, + s.GetAttributeForRole (VisualRole.Normal).Style) + }; + + s.Style.LegendAttributes.NormalAttribute = new Attribute ( + data.Item1, + s.GetAttributeForRole (VisualRole.Normal).Background, + s.GetAttributeForRole (VisualRole.Normal).Style); + } + } + + void OnLegendsOrientationSliderOnOptionsChanged (object _, LinearRangeEventArgs e) + { + foreach (LinearRange s in mainWindow.SubViews.OfType ()) + { + if (e.Options.ContainsKey (0)) + { + s.LegendsOrientation = Orientation.Horizontal; + } + else if (e.Options.ContainsKey (1)) + { + s.LegendsOrientation = Orientation.Vertical; + } + + if (optionsSlider.GetSetOptions ().Contains (3)) + { + s.Width = Dim.Auto (DimAutoStyle.Content); + s.Height = Dim.Auto (DimAutoStyle.Content); + } + else + { + if (s.Orientation == Orientation.Horizontal) + { + s.Width = Dim.Percent (50); + + int h = s.ShowLegends && s.LegendsOrientation == Orientation.Vertical + ? s.Options.Max (o => o.Legend!.Length) + 3 + : 4; + s.Height = h; + } + else + { + int w = s.ShowLegends ? s.Options.Max (o => o.Legend!.Length) + 3 : 3; + s.Width = w; + s.Height = Dim.Fill (); + } + } + } + } + + void OnOrientationSliderOnOptionsChanged (object _, LinearRangeEventArgs e) + { + View prev = null; + + foreach (LinearRange s in mainWindow.SubViews.OfType ()) + { + if (e.Options.ContainsKey (0)) + { + s.Orientation = Orientation.Horizontal; + + s.Style.SpaceChar = new () { Grapheme = Glyphs.HLine.ToString () }; + + if (prev == null) + { + s.Y = 0; + } + else + { + s.Y = Pos.Bottom (prev) + 1; + } + + s.X = 0; + prev = s; + } + else if (e.Options.ContainsKey (1)) + { + s.Orientation = Orientation.Vertical; + + s.Style.SpaceChar = new () { Grapheme = Glyphs.VLine.ToString () }; + + if (prev == null) + { + s.X = 0; + } + else + { + s.X = Pos.Right (prev) + 2; + } + + s.Y = 0; + prev = s; + } + + if (optionsSlider.GetSetOptions ().Contains (3)) + { + s.Width = Dim.Auto (DimAutoStyle.Content); + s.Height = Dim.Auto (DimAutoStyle.Content); + } + else + { + if (s.Orientation == Orientation.Horizontal) + { + s.Width = Dim.Percent (50); + + int h = s.ShowLegends && s.LegendsOrientation == Orientation.Vertical + ? s.Options.Max (o => o.Legend!.Length) + 3 + : 4; + s.Height = h; + } + else + { + int w = s.ShowLegends ? s.Options.Max (o => o.Legend!.Length) + 3 : 3; + s.Width = w; + s.Height = Dim.Fill (); + } + } + } + } + + void OnOptionsSliderOnOptionsChanged (object _, LinearRangeEventArgs e) + { + foreach (LinearRange s in mainWindow.SubViews.OfType ()) + { + s.ShowLegends = e.Options.ContainsKey (0); + s.RangeAllowSingle = e.Options.ContainsKey (1); + s.ShowEndSpacing = e.Options.ContainsKey (2); + + if (e.Options.ContainsKey (3)) + { + s.Width = Dim.Auto (DimAutoStyle.Content); + s.Height = Dim.Auto (DimAutoStyle.Content); + } + else + { + if (s.Orientation == Orientation.Horizontal) + { + s.Width = Dim.Percent (50); + + int h = s.ShowLegends && s.LegendsOrientation == Orientation.Vertical + ? s.Options.Max (o => o.Legend!.Length) + 3 + : 4; + s.Height = h; + } + else + { + int w = s.ShowLegends ? s.Options.Max (o => o.Legend!.Length) + 3 : 3; + s.Width = w; + s.Height = Dim.Fill (); + } + } + } + } + } + + private void MakeSliders (Window window, List options) + { + List types = Enum.GetValues (typeof (LinearRangeType)).Cast ().ToList (); + LinearRange prev = null; + + foreach (LinearRange view in types.Select (type => new LinearRange (options) + { + Title = type.ToString (), + X = 0, + Y = prev == null ? 0 : Pos.Bottom (prev), + BorderStyle = LineStyle.Single, + Type = type, + AllowEmpty = true + })) + { + //view.Padding.Thickness = new (0,1,0,0); + window.Add (view); + prev = view; + } + + List singleOptions = + [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39 + ]; + + LinearRange single = new (singleOptions) + { + Title = "_Continuous", + X = 0, + Y = prev == null ? 0 : Pos.Bottom (prev), + Type = LinearRangeType.Single, + BorderStyle = LineStyle.Single, + AllowEmpty = false + }; + + single.SubViewLayout += (_, _) => + { + if (single.Orientation == Orientation.Horizontal) + { + single.Style.SpaceChar = new () { Grapheme = Glyphs.HLine.ToString () }; + single.Style.OptionChar = new () { Grapheme = Glyphs.HLine.ToString () }; + } + else + { + single.Style.SpaceChar = new () { Grapheme = Glyphs.VLine.ToString () }; + single.Style.OptionChar = new () { Grapheme = Glyphs.VLine.ToString () }; + } + }; + single.Style.SetChar = new () { Grapheme = Glyphs.ContinuousMeterSegment.ToString () }; + single.Style.DragChar = new () { Grapheme = Glyphs.ContinuousMeterSegment.ToString () }; + + window.Add (single); + + single.OptionsChanged += (_, e) => { single.Title = $"_Continuous {e.Options.FirstOrDefault ().Key}"; }; + + List oneOption = new () { "The Only Option" }; + + LinearRange one = new (oneOption) + { + Title = "_One Option", + X = 0, + Y = prev == null ? 0 : Pos.Bottom (single), + Type = LinearRangeType.Single, + BorderStyle = LineStyle.Single, + AllowEmpty = false + }; + window.Add (one); + } +} diff --git a/Examples/UICatalog/Scenarios/Shortcuts.cs b/Examples/UICatalog/Scenarios/Shortcuts.cs index 5a76edd12..1fd133a71 100644 --- a/Examples/UICatalog/Scenarios/Shortcuts.cs +++ b/Examples/UICatalog/Scenarios/Shortcuts.cs @@ -229,8 +229,8 @@ public class Shortcuts : Scenario X = 0, Y = Pos.Bottom (optionSelectorShortcut), Width = Dim.Fill ()! - Dim.Width (eventLog), - HelpText = "Sliders work!", - CommandView = new Slider + HelpText = "LinearRanges work!", + CommandView = new LinearRange { Orientation = Orientation.Horizontal, AllowEmpty = true @@ -238,13 +238,13 @@ public class Shortcuts : Scenario Key = Key.F5 }; - ((Slider)sliderShortcut.CommandView).Options = [new () { Legend = "A" }, new () { Legend = "B" }, new () { Legend = "C" }]; - ((Slider)sliderShortcut.CommandView).SetOption (0); + ((LinearRange)sliderShortcut.CommandView).Options = [new () { Legend = "A" }, new () { Legend = "B" }, new () { Legend = "C" }]; + ((LinearRange)sliderShortcut.CommandView).SetOption (0); - ((Slider)sliderShortcut.CommandView).OptionsChanged += (o, args) => + ((LinearRange)sliderShortcut.CommandView).OptionsChanged += (o, args) => { eventSource.Add ( - $"OptionsChanged: {o?.GetType ().Name} - {string.Join (",", ((Slider)o!)!.GetSetOptions ())}"); + $"OptionsChanged: {o?.GetType ().Name} - {string.Join (",", ((LinearRange)o!)!.GetSetOptions ())}"); eventLog.MoveDown (); }; diff --git a/Examples/UICatalog/Scenarios/Sliders.cs b/Examples/UICatalog/Scenarios/Sliders.cs deleted file mode 100644 index d23eae48d..000000000 --- a/Examples/UICatalog/Scenarios/Sliders.cs +++ /dev/null @@ -1,621 +0,0 @@ -using System.Collections.ObjectModel; -using System.Text; - -namespace UICatalog.Scenarios; - -[ScenarioMetadata ("Sliders", "Demonstrates the Slider view.")] -[ScenarioCategory ("Controls")] -public class Sliders : Scenario -{ - public void MakeSliders (View v, List options) - { - List types = Enum.GetValues (typeof (SliderType)).Cast ().ToList (); - Slider prev = null; - - foreach (SliderType type in types) - { - var view = new Slider (options) - { - Title = type.ToString (), - X = 0, - Y = prev == null ? 0 : Pos.Bottom (prev), - BorderStyle = LineStyle.Single, - Type = type, - AllowEmpty = true - }; - - //view.Padding.Thickness = new (0,1,0,0); - v.Add (view); - prev = view; - } - - List singleOptions = new () - { - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39 - }; - - var single = new Slider (singleOptions) - { - Title = "_Continuous", - X = 0, - Y = prev == null ? 0 : Pos.Bottom (prev), - Type = SliderType.Single, - BorderStyle = LineStyle.Single, - AllowEmpty = false - }; - - single.SubViewLayout += (s, e) => - { - if (single.Orientation == Orientation.Horizontal) - { - single.Style.SpaceChar = new () { Grapheme = Glyphs.HLine.ToString () }; - single.Style.OptionChar = new () { Grapheme = Glyphs.HLine.ToString () }; - } - else - { - single.Style.SpaceChar = new () { Grapheme = Glyphs.VLine.ToString () }; - single.Style.OptionChar = new () { Grapheme = Glyphs.VLine.ToString () }; - } - }; - single.Style.SetChar = new () { Grapheme = Glyphs.ContinuousMeterSegment.ToString () }; - single.Style.DragChar = new () { Grapheme = Glyphs.ContinuousMeterSegment.ToString () }; - - v.Add (single); - - single.OptionsChanged += (s, e) => { single.Title = $"_Continuous {e.Options.FirstOrDefault ().Key}"; }; - - List oneOption = new () { "The Only Option" }; - - var one = new Slider (oneOption) - { - Title = "_One Option", - X = 0, - Y = prev == null ? 0 : Pos.Bottom (single), - Type = SliderType.Single, - BorderStyle = LineStyle.Single, - AllowEmpty = false - }; - v.Add (one); - } - - public override void Main () - { - Application.Init (); - - Window app = new () - { - Title = GetQuitKeyAndName () - }; - - MakeSliders ( - app, - new () - { - 500, - 1000, - 1500, - 2000, - 2500, - 3000, - 3500, - 4000, - 4500, - 5000 - } - ); - - var configView = new FrameView - { - Title = "Confi_guration", - X = Pos.Percent (50), - Y = 0, - Width = Dim.Fill (), - Height = Dim.Fill (), - SchemeName = "Dialog" - }; - - app.Add (configView); - - #region Config Slider - - Slider optionsSlider = new () - { - Title = "Options", - X = 0, - Y = 0, - Width = Dim.Fill (), - Type = SliderType.Multiple, - AllowEmpty = true, - BorderStyle = LineStyle.Single - }; - - optionsSlider.Style.SetChar = optionsSlider.Style.SetChar with { Attribute = new Attribute (Color.BrightGreen, Color.Black) }; - optionsSlider.Style.LegendAttributes.SetAttribute = new Attribute (Color.Green, Color.Black); - - optionsSlider.Options = new () - { - new () { Legend = "Legends" }, - new () { Legend = "RangeAllowSingle" }, - new () { Legend = "EndSpacing" }, - new () { Legend = "DimAuto" } - }; - - configView.Add (optionsSlider); - - optionsSlider.OptionsChanged += (sender, e) => - { - foreach (Slider s in app.SubViews.OfType ()) - { - s.ShowLegends = e.Options.ContainsKey (0); - s.RangeAllowSingle = e.Options.ContainsKey (1); - s.ShowEndSpacing = e.Options.ContainsKey (2); - - if (e.Options.ContainsKey (3)) - { - s.Width = Dim.Auto (DimAutoStyle.Content); - s.Height = Dim.Auto (DimAutoStyle.Content); - } - else - { - if (s.Orientation == Orientation.Horizontal) - { - s.Width = Dim.Percent (50); - - int h = s.ShowLegends && s.LegendsOrientation == Orientation.Vertical - ? s.Options.Max (o => o.Legend.Length) + 3 - : 4; - s.Height = h; - } - else - { - int w = s.ShowLegends ? s.Options.Max (o => o.Legend.Length) + 3 : 3; - s.Width = w; - s.Height = Dim.Fill (); - } - } - } - }; - optionsSlider.SetOption (0); // Legends - optionsSlider.SetOption (1); // RangeAllowSingle - optionsSlider.SetOption (3); // DimAuto - - CheckBox dimAutoUsesMin = new () - { - Text = "Use minimum size (vs. ideal)", - X = 0, - Y = Pos.Bottom (optionsSlider) - }; - - dimAutoUsesMin.CheckedStateChanging += (sender, e) => - { - foreach (Slider s in app.SubViews.OfType ()) - { - s.UseMinimumSize = !s.UseMinimumSize; - } - }; - configView.Add (dimAutoUsesMin); - - #region Slider Orientation Slider - - Slider orientationSlider = new (new () { "Horizontal", "Vertical" }) - { - Title = "Slider Orientation", - X = 0, - Y = Pos.Bottom (dimAutoUsesMin) + 1, - BorderStyle = LineStyle.Single - }; - - orientationSlider.SetOption (0); - - configView.Add (orientationSlider); - - orientationSlider.OptionsChanged += (sender, e) => - { - View prev = null; - - foreach (Slider s in app.SubViews.OfType ()) - { - if (e.Options.ContainsKey (0)) - { - s.Orientation = Orientation.Horizontal; - - s.Style.SpaceChar = new () { Grapheme = Glyphs.HLine.ToString () }; - - if (prev == null) - { - s.Y = 0; - } - else - { - s.Y = Pos.Bottom (prev) + 1; - } - - s.X = 0; - prev = s; - } - else if (e.Options.ContainsKey (1)) - { - s.Orientation = Orientation.Vertical; - - s.Style.SpaceChar = new () { Grapheme = Glyphs.VLine.ToString () }; - - if (prev == null) - { - s.X = 0; - } - else - { - s.X = Pos.Right (prev) + 2; - } - - s.Y = 0; - prev = s; - } - - if (optionsSlider.GetSetOptions ().Contains (3)) - { - s.Width = Dim.Auto (DimAutoStyle.Content); - s.Height = Dim.Auto (DimAutoStyle.Content); - } - else - { - if (s.Orientation == Orientation.Horizontal) - { - s.Width = Dim.Percent (50); - - int h = s.ShowLegends && s.LegendsOrientation == Orientation.Vertical - ? s.Options.Max (o => o.Legend.Length) + 3 - : 4; - s.Height = h; - } - else - { - int w = s.ShowLegends ? s.Options.Max (o => o.Legend.Length) + 3 : 3; - s.Width = w; - s.Height = Dim.Fill (); - } - } - } - }; - - #endregion Slider Orientation Slider - - #region Legends Orientation Slider - - Slider legendsOrientationSlider = new (new () { "Horizontal", "Vertical" }) - { - Title = "Legends Orientation", - X = 0, - Y = Pos.Bottom (orientationSlider) + 1, - BorderStyle = LineStyle.Single - }; - - legendsOrientationSlider.SetOption (0); - - configView.Add (legendsOrientationSlider); - - legendsOrientationSlider.OptionsChanged += (sender, e) => - { - foreach (Slider s in app.SubViews.OfType ()) - { - if (e.Options.ContainsKey (0)) - { - s.LegendsOrientation = Orientation.Horizontal; - } - else if (e.Options.ContainsKey (1)) - { - s.LegendsOrientation = Orientation.Vertical; - } - - if (optionsSlider.GetSetOptions ().Contains (3)) - { - s.Width = Dim.Auto (DimAutoStyle.Content); - s.Height = Dim.Auto (DimAutoStyle.Content); - } - else - { - if (s.Orientation == Orientation.Horizontal) - { - s.Width = Dim.Percent (50); - - int h = s.ShowLegends && s.LegendsOrientation == Orientation.Vertical - ? s.Options.Max (o => o.Legend.Length) + 3 - : 4; - s.Height = h; - } - else - { - int w = s.ShowLegends ? s.Options.Max (o => o.Legend.Length) + 3 : 3; - s.Width = w; - s.Height = Dim.Fill (); - } - } - } - }; - - #endregion Legends Orientation Slider - - #region Spacing Options - - FrameView spacingOptions = new () - { - Title = "Spacing Options", - X = Pos.Right (orientationSlider), - Y = Pos.Top (orientationSlider), - Width = Dim.Fill (), - Height = Dim.Auto (), - BorderStyle = LineStyle.Single - }; - - Label label = new () - { - Text = "Min _Inner Spacing:" - }; - - NumericUpDown innerSpacingUpDown = new () - { - X = Pos.Right (label) + 1 - }; - - innerSpacingUpDown.Value = app.SubViews.OfType ().First ().MinimumInnerSpacing; - - innerSpacingUpDown.ValueChanging += (sender, e) => - { - if (e.NewValue < 0) - { - e.Cancel = true; - - return; - } - - foreach (Slider s in app.SubViews.OfType ()) - { - s.MinimumInnerSpacing = e.NewValue; - } - }; - - spacingOptions.Add (label, innerSpacingUpDown); - configView.Add (spacingOptions); - - #endregion - - #region Color Slider - - foreach (Slider s in app.SubViews.OfType ()) - { - s.Style.OptionChar = s.Style.OptionChar with { Attribute = app.GetAttributeForRole (VisualRole.Normal) }; - s.Style.SetChar = s.Style.SetChar with { Attribute = app.GetAttributeForRole (VisualRole.Normal) }; - s.Style.LegendAttributes.SetAttribute = app.GetAttributeForRole (VisualRole.Normal); - s.Style.RangeChar = s.Style.RangeChar with { Attribute = app.GetAttributeForRole (VisualRole.Normal) }; - } - - Slider<(Color, Color)> sliderFGColor = new () - { - Title = "FG Color", - X = 0, - Y = Pos.Bottom ( - legendsOrientationSlider - ) - + 1, - Type = SliderType.Single, - BorderStyle = LineStyle.Single, - AllowEmpty = false, - Orientation = Orientation.Vertical, - LegendsOrientation = Orientation.Horizontal, - MinimumInnerSpacing = 0, - UseMinimumSize = true - }; - - sliderFGColor.Style.SetChar = sliderFGColor.Style.SetChar with { Attribute = new Attribute (Color.BrightGreen, Color.Black) }; - sliderFGColor.Style.LegendAttributes.SetAttribute = new Attribute (Color.Green, Color.Blue); - - List> colorOptions = new (); - - foreach (ColorName16 colorIndex in Enum.GetValues ()) - { - var colorName = colorIndex.ToString (); - - colorOptions.Add ( - new () - { - Data = (new (colorIndex), - new (colorIndex)), - Legend = colorName, - LegendAbbr = (Rune)colorName [0] - } - ); - } - - sliderFGColor.Options = colorOptions; - - configView.Add (sliderFGColor); - - sliderFGColor.OptionsChanged += (sender, e) => - { - if (e.Options.Count != 0) - { - (Color, Color) data = e.Options.First ().Value.Data; - - foreach (Slider s in app.SubViews.OfType ()) - { - s.SetScheme ( - new (s.GetScheme ()) - { - Normal = new ( - data.Item2, - s.GetAttributeForRole (VisualRole.Normal).Background, - s.GetAttributeForRole (VisualRole.Normal).Style - ) - }); - - s.Style.OptionChar = s.Style.OptionChar with - { - Attribute = new Attribute ( - data.Item1, - s.GetAttributeForRole (VisualRole.Normal).Background, - s.GetAttributeForRole (VisualRole.Normal).Style - ) - }; - - s.Style.SetChar = s.Style.SetChar with - { - Attribute = new Attribute ( - data.Item1, - s.Style.SetChar.Attribute?.Background - ?? s.GetAttributeForRole (VisualRole.Normal).Background, - s.Style.SetChar.Attribute?.Style - ?? s.GetAttributeForRole (VisualRole.Normal).Style - ) - }; - - s.Style.LegendAttributes.SetAttribute = - new Attribute ( - data.Item1, - s.GetAttributeForRole (VisualRole.Normal).Background, - s.GetAttributeForRole (VisualRole.Normal).Style); - - s.Style.RangeChar = s.Style.RangeChar with - { - Attribute = new Attribute ( - data.Item1, - s.GetAttributeForRole (VisualRole.Normal).Background, - s.GetAttributeForRole (VisualRole.Normal).Style) - }; - - s.Style.SpaceChar = s.Style.SpaceChar with - { - Attribute = new Attribute ( - data.Item1, - s.GetAttributeForRole (VisualRole.Normal).Background, - s.GetAttributeForRole (VisualRole.Normal).Style) - }; - - s.Style.LegendAttributes.NormalAttribute = - new Attribute ( - data.Item1, - s.GetAttributeForRole (VisualRole.Normal).Background, - s.GetAttributeForRole (VisualRole.Normal).Style); - } - } - }; - - Slider<(Color, Color)> sliderBGColor = new () - { - Title = "BG Color", - X = Pos.Right (sliderFGColor), - Y = Pos.Top (sliderFGColor), - Type = SliderType.Single, - BorderStyle = LineStyle.Single, - AllowEmpty = false, - Orientation = Orientation.Vertical, - LegendsOrientation = Orientation.Horizontal, - MinimumInnerSpacing = 0, - UseMinimumSize = true - }; - - sliderBGColor.Style.SetChar = sliderBGColor.Style.SetChar with { Attribute = new Attribute (Color.BrightGreen, Color.Black) }; - sliderBGColor.Style.LegendAttributes.SetAttribute = new Attribute (Color.Green, Color.Blue); - - sliderBGColor.Options = colorOptions; - - configView.Add (sliderBGColor); - - sliderBGColor.OptionsChanged += (sender, e) => - { - if (e.Options.Count != 0) - { - (Color, Color) data = e.Options.First ().Value.Data; - - foreach (Slider s in app.SubViews.OfType ()) - { - s.SetScheme ( - new (s.GetScheme ()) - { - Normal = new ( - s.GetAttributeForRole (VisualRole.Normal).Foreground, - data.Item2 - ) - }); - } - } - }; - - #endregion Color Slider - - #endregion Config Slider - - ObservableCollection eventSource = new (); - - var eventLog = new ListView - { - X = Pos.Right (sliderBGColor), - Y = Pos.Bottom (spacingOptions), - Width = Dim.Fill (), - Height = Dim.Fill (), - SchemeName = "Runnable", - Source = new ListWrapper (eventSource) - }; - configView.Add (eventLog); - - foreach (Slider slider in app.SubViews.Where (v => v is Slider)!) - { - slider.Accepting += (o, args) => - { - eventSource.Add ($"Accept: {string.Join (",", slider.GetSetOptions ())}"); - eventLog.MoveDown (); - args.Handled = true; - }; - - slider.OptionsChanged += (o, args) => - { - eventSource.Add ($"OptionsChanged: {string.Join (",", slider.GetSetOptions ())}"); - eventLog.MoveDown (); - args.Cancel = true; - }; - } - - app.FocusDeepest (NavigationDirection.Forward, null); - - Application.Run (app); - app.Dispose (); - Application.Shutdown (); - } -} diff --git a/Examples/UICatalog/Scenarios/ViewportSettings.cs b/Examples/UICatalog/Scenarios/ViewportSettings.cs index 4edd90d0c..8c93b6b07 100644 --- a/Examples/UICatalog/Scenarios/ViewportSettings.cs +++ b/Examples/UICatalog/Scenarios/ViewportSettings.cs @@ -201,17 +201,17 @@ public class ViewportSettings : Scenario List options = new () { "Option 1", "Option 2", "Option 3" }; - Slider slider = new (options) + LinearRange linearRange = new (options) { X = 0, Y = Pos.Bottom (textField) + 1, Orientation = Orientation.Vertical, - Type = SliderType.Multiple, + Type = LinearRangeType.Multiple, AllowEmpty = false, BorderStyle = LineStyle.Double, - Title = "_Slider" + Title = "_LinearRange" }; - view.Add (slider); + view.Add (linearRange); adornmentsEditor.Initialized += (s, e) => { diff --git a/Terminal.Gui/Views/Slider/Slider.cs b/Terminal.Gui/Views/LinearRange/LinearRange.cs similarity index 63% rename from Terminal.Gui/Views/Slider/Slider.cs rename to Terminal.Gui/Views/LinearRange/LinearRange.cs index 5c7a9f347..3b5c7b74c 100644 --- a/Terminal.Gui/Views/Slider/Slider.cs +++ b/Terminal.Gui/Views/LinearRange/LinearRange.cs @@ -1,46 +1,44 @@ -#nullable disable -using System.Transactions; - namespace Terminal.Gui.Views; /// -/// Provides a slider control letting the user navigate from a set of typed options in a linear manner using the +/// Provides a linear range control letting the user navigate from a set of typed options in a linear manner using the /// keyboard or mouse. /// -public class Slider : Slider +public class LinearRange : LinearRange { - /// Initializes a new instance of the class. - public Slider () { } + /// Initializes a new instance of the class. + public LinearRange () { } - /// Initializes a new instance of the class. - /// Initial slider options. - /// Initial slider options. - public Slider (List options, Orientation orientation = Orientation.Horizontal) : + /// Initializes a new instance of the class. + /// Initial options. + /// Initial orientation. + public LinearRange (List options, Orientation orientation = Orientation.Horizontal) : base (options, orientation) { } } /// -/// Provides a tpe-safe slider control letting the user navigate from a set of typed options in a linear manner using the +/// Provides a type-safe linear range control letting the user navigate from a set of typed options in a linear manner +/// using the /// keyboard or mouse. /// /// -public class Slider : View, IOrientation +public class LinearRange : View, IOrientation { - private readonly SliderConfiguration _config = new (); + private readonly LinearRangeConfiguration _config = new (); // List of the current set options. - private readonly List _setOptions = new (); + private readonly List _setOptions = []; // Options - private List> _options; + private List>? _options; - private OrientationHelper _orientationHelper; + private OrientationHelper? _orientationHelper; #region Initialize private void SetInitialProperties ( - List> options, + List> options, Orientation orientation = Orientation.Horizontal ) { @@ -49,10 +47,10 @@ public class Slider : View, IOrientation CanFocus = true; CursorVisibility = CursorVisibility.Default; - _options = options ?? new List> (); + _options = options; _orientationHelper = new (this); // Do not use object initializer! - _orientationHelper.Orientation = _config._sliderOrientation = orientation; + _orientationHelper.Orientation = _config._linearRangeOrientation = orientation; _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e); _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e); @@ -60,9 +58,6 @@ public class Slider : View, IOrientation SetCommands (); SetContentSize (); - // BUGBUG: This should not be needed - Need to ensure SetRelativeLayout gets called during EndInit - Initialized += (s, e) => { SetContentSize (); }; - SubViewLayout += (s, e) => { SetContentSize (); }; } @@ -71,7 +66,7 @@ public class Slider : View, IOrientation { _config._showLegends = true; - switch (_config._sliderOrientation) + switch (_config._linearRangeOrientation) { case Orientation.Horizontal: Style.SpaceChar = new () { Grapheme = Glyphs.HLine.ToString () }; // '─' @@ -85,63 +80,44 @@ public class Slider : View, IOrientation break; } - // TODO(jmperricone) Wide Vertical ??? - /* - │ - │ - ┼─ 40 - │ - │ - ███ 30 - ▒▒▒ - ▒▒▒ - ▒▒▒ 20 - ▒▒▒ - ▒▒▒ - ███ 10 - │ - │ - ─●─ 0 - */ - - _config._legendsOrientation = _config._sliderOrientation; + _config._legendsOrientation = _config._linearRangeOrientation; Style.EmptyChar = new () { Grapheme = " " }; Style.SetChar = new () { Grapheme = Glyphs.ContinuousMeterSegment.ToString () }; // ■ Style.RangeChar = new () { Grapheme = Glyphs.Stipple.ToString () }; // ░ ▒ ▓ // Medium shade not blinking on curses. Style.StartRangeChar = new () { Grapheme = Glyphs.ContinuousMeterSegment.ToString () }; Style.EndRangeChar = new () { Grapheme = Glyphs.ContinuousMeterSegment.ToString () }; Style.DragChar = new () { Grapheme = Glyphs.Diamond.ToString () }; - - // TODO: Support left & right (top/bottom) - // First = '├', - // Last = '┤', } #endregion #region Constructors - /// Initializes a new instance of the class. - public Slider () : this (new ()) { } + /// Initializes a new instance of the class. + public LinearRange () : this (new ()) { } - /// Initializes a new instance of the class. - /// Initial slider options. - /// Initial slider orientation. - public Slider (List options, Orientation orientation = Orientation.Horizontal) + /// Initializes a new instance of the class. + /// Initial options. + /// Initial orientation. + public LinearRange (List? options, Orientation orientation = Orientation.Horizontal) { if (options is null) { - SetInitialProperties (null, orientation); + return; + } + + if (options is { Count: 0 }) + { + SetInitialProperties ([], orientation); } else { SetInitialProperties ( - options.Select ( - e => + options.Select (e => { - var legend = e.ToString (); + string? legend = e?.ToString (); - return new SliderOption + return new LinearRangeOption { Data = e, Legend = legend, @@ -161,20 +137,13 @@ public class Slider : View, IOrientation #region Properties /// - /// Setting the Text of a slider is a shortcut to setting options. The text is a CSV string of the options. + /// Setting the Text of a linear range is a shortcut to setting options. The text is a CSV string of the options. /// public override string Text { - get - { - if (_options.Count == 0) - { - return string.Empty; - } - - // Return labels as a CSV string - return string.Join (",", _options); - } + // Return labels as a CSV string + get => _options is null or { Count: 0 } ? string.Empty : + string.Join (",", _options); set { if (string.IsNullOrEmpty (value)) @@ -184,7 +153,7 @@ public class Slider : View, IOrientation else { IEnumerable list = value.Split (',').Select (x => x.Trim ()); - Options = list.Select (x => new SliderOption { Legend = x }).ToList (); + Options = list.Select (x => new LinearRangeOption { Legend = x }).ToList (); } } } @@ -197,7 +166,7 @@ public class Slider : View, IOrientation { _config._allowEmpty = value; - if (!value && _options.Count > 0 && _setOptions.Count == 0) + if (!value && _options!.Count > 0 && _setOptions.Count == 0) { SetOption (0); } @@ -210,50 +179,102 @@ public class Slider : View, IOrientation get => _config._minInnerSpacing; set { - _config._minInnerSpacing = value; + int current = _config._minInnerSpacing; - SetContentSize (); + CWPPropertyHelper.ChangeProperty ( + this, + ref current, + value, + OnMinimumInnerSpacingChanging, + MinimumInnerSpacingChanging, + newValue => + { + _config._minInnerSpacing = newValue; + SetContentSize (); + }, + OnMinimumInnerSpacingChanged, + MinimumInnerSpacingChanged, + out int _ + ); } } - /// Slider Type. - public SliderType Type + /// Event raised before the property changes. Can be cancelled. + public event EventHandler>? MinimumInnerSpacingChanging; + + /// Event raised after the property has changed. + public event EventHandler>? MinimumInnerSpacingChanged; + + /// Called before changes. Return true to cancel the change. + protected virtual bool OnMinimumInnerSpacingChanging (ValueChangingEventArgs args) => false; + + /// Called after has changed. + protected virtual void OnMinimumInnerSpacingChanged (ValueChangedEventArgs args) { } + + /// LinearRange Type. + public LinearRangeType Type { get => _config._type; set { - _config._type = value; + LinearRangeType current = _config._type; - // Todo: Custom logic to preserve options. - _setOptions.Clear (); - SetNeedsDraw (); + CWPPropertyHelper.ChangeProperty ( + this, + ref current, + value, + OnTypeChanging, + TypeChanging, + newValue => + { + _config._type = newValue; + + // Todo: Custom logic to preserve options. + _setOptions.Clear (); + SetNeedsDraw (); + }, + OnTypeChanged, + TypeChanged, + out LinearRangeType _ + ); } } + /// Event raised before the property changes. Can be cancelled. + public event EventHandler>? TypeChanging; + + /// Event raised after the property has changed. + public event EventHandler>? TypeChanged; + + /// Called before changes. Return true to cancel the change. + protected virtual bool OnTypeChanging (ValueChangingEventArgs args) => false; + + /// Called after has changed. + protected virtual void OnTypeChanged (ValueChangedEventArgs args) { } /// /// Gets or sets the . The default is . /// public Orientation Orientation { - get => _orientationHelper.Orientation; - set => _orientationHelper.Orientation = value; + get => _orientationHelper!.Orientation; + set => _orientationHelper!.Orientation = value; } #region IOrientation members - /// - public event EventHandler> OrientationChanging; + /// + public event EventHandler>? OrientationChanging; - /// - public event EventHandler> OrientationChanged; + /// + public event EventHandler>? OrientationChanged; - /// + /// public void OnOrientationChanged (Orientation newOrientation) { - _config._sliderOrientation = newOrientation; + _config._linearRangeOrientation = newOrientation; - switch (_config._sliderOrientation) + switch (_config._linearRangeOrientation) { case Orientation.Horizontal: Style.SpaceChar = new () { Grapheme = Glyphs.HLine.ToString () }; // '─' @@ -268,6 +289,7 @@ public class Slider : View, IOrientation SetKeyBindings (); SetContentSize (); } + #endregion /// Legends Orientation. @@ -276,20 +298,45 @@ public class Slider : View, IOrientation get => _config._legendsOrientation; set { - _config._legendsOrientation = value; + Orientation current = _config._legendsOrientation; - SetContentSize (); + CWPPropertyHelper.ChangeProperty ( + this, + ref current, + value, + OnLegendsOrientationChanging, + LegendsOrientationChanging, + newValue => + { + _config._legendsOrientation = newValue; + SetContentSize (); + }, + OnLegendsOrientationChanged, + LegendsOrientationChanged, + out Orientation _ + ); } } - /// Slider styles. - public SliderStyle Style { get; set; } = new (); + /// Event raised before the property changes. Can be cancelled. + public event EventHandler>? LegendsOrientationChanging; - /// Set the slider options. - public List> Options + /// Event raised after the property has changed. + public event EventHandler>? LegendsOrientationChanged; + + /// Called before changes. Return true to cancel the change. + protected virtual bool OnLegendsOrientationChanging (ValueChangingEventArgs args) => false; + + /// Called after has changed. + protected virtual void OnLegendsOrientationChanged (ValueChangedEventArgs args) { } + + /// LinearRange styles. + public LinearRangeStyle Style { get; set; } = new (); + + /// Set the linear range options. + public List> Options { - get => - _options; + get => _options ?? []; set { // _options should never be null @@ -317,43 +364,127 @@ public class Slider : View, IOrientation get => _config._showEndSpacing; set { - _config._showEndSpacing = value; - SetContentSize (); + bool current = _config._showEndSpacing; + + CWPPropertyHelper.ChangeProperty ( + this, + ref current, + value, + OnShowEndSpacingChanging, + ShowEndSpacingChanging, + newValue => + { + _config._showEndSpacing = newValue; + SetContentSize (); + }, + OnShowEndSpacingChanged, + ShowEndSpacingChanged, + out bool _ + ); } } + /// Event raised before the property changes. Can be cancelled. + public event EventHandler>? ShowEndSpacingChanging; + + /// Event raised after the property has changed. + public event EventHandler>? ShowEndSpacingChanged; + + /// Called before changes. Return true to cancel the change. + protected virtual bool OnShowEndSpacingChanging (ValueChangingEventArgs args) => false; + + /// Called after has changed. + protected virtual void OnShowEndSpacingChanged (ValueChangedEventArgs args) { } + /// Show/Hide the options legends. public bool ShowLegends { get => _config._showLegends; set { - _config._showLegends = value; - SetContentSize (); + bool current = _config._showLegends; + + CWPPropertyHelper.ChangeProperty ( + this, + ref current, + value, + OnShowLegendsChanging, + ShowLegendsChanging, + newValue => + { + _config._showLegends = newValue; + SetContentSize (); + }, + OnShowLegendsChanged, + ShowLegendsChanged, + out bool _ + ); } } + /// Event raised before the property changes. Can be cancelled. + public event EventHandler>? ShowLegendsChanging; + + /// Event raised after the property has changed. + public event EventHandler>? ShowLegendsChanged; + + /// Called before changes. Return true to cancel the change. + protected virtual bool OnShowLegendsChanging (ValueChangingEventArgs args) => false; + + /// Called after has changed. + protected virtual void OnShowLegendsChanged (ValueChangedEventArgs args) { } + /// - /// Gets or sets whether the minimum or ideal size will be used when calculating the size of the slider. + /// Gets or sets whether the minimum or ideal size will be used when calculating the size of the linear range. /// public bool UseMinimumSize { get => _config._useMinimumSize; set { - _config._useMinimumSize = value; - SetContentSize (); + bool current = _config._useMinimumSize; + + CWPPropertyHelper.ChangeProperty ( + this, + ref current, + value, + OnUseMinimumSizeChanging, + UseMinimumSizeChanging, + newValue => + { + _config._useMinimumSize = newValue; + SetContentSize (); + }, + OnUseMinimumSizeChanged, + UseMinimumSizeChanged, + out bool _ + ); } } + /// Event raised before the property changes. Can be cancelled. + public event EventHandler>? UseMinimumSizeChanging; + + /// Event raised after the property has changed. + public event EventHandler>? UseMinimumSizeChanged; + + /// Called before changes. Return true to cancel the change. + protected virtual bool OnUseMinimumSizeChanging (ValueChangingEventArgs args) => false; + + /// Called after has changed. + protected virtual void OnUseMinimumSizeChanged (ValueChangedEventArgs args) { } + #endregion #region Events - /// Event raised when the slider option/s changed. The dictionary contains: key = option index, value = T - public event EventHandler> OptionsChanged; + /// Event raised when the linear range option/s changed. The dictionary contains: key = option index, value = T + public event EventHandler>? OptionsChanged; - /// Overridable method called when the slider options have changed. Raises the event. + /// + /// Overridable method called when the linear range options have changed. Raises the + /// event. + /// public virtual void OnOptionsChanged () { OptionsChanged?.Invoke (this, new (GetSetOptionDictionary ())); @@ -361,7 +492,7 @@ public class Slider : View, IOrientation } /// Event raised When the option is hovered with the keys or the mouse. - public event EventHandler> OptionFocused; + public event EventHandler>? OptionFocused; private int _lastFocusedOption; // for Range type; the most recently focused option. Used to determine shrink direction @@ -370,9 +501,9 @@ public class Slider : View, IOrientation /// /// if the focus change was cancelled. /// - public virtual bool OnOptionFocused (int newFocusedOption, SliderEventArgs args) + public virtual bool OnOptionFocused (int newFocusedOption, LinearRangeEventArgs args) { - if (newFocusedOption > _options.Count - 1 || newFocusedOption < 0) + if (newFocusedOption > _options!.Count - 1 || newFocusedOption < 0) { return true; } @@ -383,8 +514,6 @@ public class Slider : View, IOrientation { _lastFocusedOption = FocusedOption; FocusedOption = newFocusedOption; - - //PositionCursor (); } return args.Cancel; @@ -403,7 +532,7 @@ public class Slider : View, IOrientation // TODO: Handle range type. // Note: Maybe return false only when optionIndex doesn't exist, otherwise true. - if (!_setOptions.Contains (optionIndex) && optionIndex >= 0 && optionIndex < _options.Count) + if (!_setOptions.Contains (optionIndex) && optionIndex >= 0 && optionIndex < _options!.Count) { FocusedOption = optionIndex; SetFocusedOption (); @@ -451,15 +580,15 @@ public class Slider : View, IOrientation AddStr (str); } - /// Sets the dimensions of the Slider to the ideal values. + /// Sets the dimensions of the LinearRange to the ideal values. private void SetContentSize () { - if (_options.Count == 0) + if (_options is { Count: 0 }) { return; } - bool horizontal = _config._sliderOrientation == Orientation.Horizontal; + bool horizontal = _config._linearRangeOrientation == Orientation.Horizontal; if (UseMinimumSize) { @@ -482,29 +611,29 @@ public class Slider : View, IOrientation int maxLegend; // Because the legends are centered, the longest one determines inner spacing - if (_config._sliderOrientation == _config._legendsOrientation) + if (_config._linearRangeOrientation == _config._legendsOrientation) { - maxLegend = int.Max (_options.Max (s => s.Legend?.GetColumns () ?? 1), 1); + maxLegend = int.Max (_options!.Max (s => s.Legend?.GetColumns () ?? 1), 1); } else { maxLegend = 1; } - int minSizeThatFitsLegends = _options.Count == 1 ? maxLegend : _options.Sum (o => o.Legend.GetColumns ()); + int minSizeThatFitsLegends = _options!.Count == 1 ? maxLegend : _options.Sum (o => o.Legend!.GetColumns ()); - string first; - string last; + string? first; + string? last; + + _config._showLegendsAbbr = false; if (minSizeThatFitsLegends > size) { - _config._showLegendsAbbr = false; - - if (_config._sliderOrientation == _config._legendsOrientation) + if (_config._linearRangeOrientation == _config._legendsOrientation) { _config._showLegendsAbbr = true; - foreach (SliderOption o in _options.Where (op => op.LegendAbbr == default (Rune))) + foreach (LinearRangeOption o in _options.Where (op => op.LegendAbbr == default (Rune))) { o.LegendAbbr = (Rune)(o.Legend?.GetColumns () > 0 ? o.Legend [0] : ' '); } @@ -515,7 +644,6 @@ public class Slider : View, IOrientation } else { - _config._showLegendsAbbr = false; first = _options.First ().Legend; last = _options.Last ().Legend; } @@ -524,10 +652,10 @@ public class Slider : View, IOrientation // Hello // Left = He // Right = lo - int firstLeft = (first.Length - 1) / 2; // Chars count of the first option to the left. - int lastRight = last.Length / 2; // Chars count of the last option to the right. + int firstLeft = (first!.Length - 1) / 2; // Chars count of the first option to the left. + int lastRight = last!.Length / 2; // Chars count of the last option to the right. - if (_config._sliderOrientation != _config._legendsOrientation) + if (_config._linearRangeOrientation != _config._legendsOrientation) { firstLeft = 0; lastRight = 0; @@ -557,21 +685,22 @@ public class Slider : View, IOrientation /// private int CalcMinLength () { - if (_options.Count == 0) + if (_options is { Count: 0 }) { return 0; } var length = 0; length += _config._startSpacing + _config._endSpacing; - length += _options.Count; + length += _options!.Count; length += (_options.Count - 1) * _config._minInnerSpacing; return length; } /// - /// Gets the ideal width of the slider. The ideal width is the minimum width required to display all options and inner + /// Gets the ideal width of the linear range. The ideal width is the minimum width required to display all options and + /// inner /// spacing. /// /// @@ -586,7 +715,8 @@ public class Slider : View, IOrientation } /// - /// Gets the ideal height of the slider. The ideal height is the minimum height required to display all options and + /// Gets the ideal height of the linear range. The ideal height is the minimum height required to display all options + /// and /// inner spacing. /// /// @@ -607,58 +737,53 @@ public class Slider : View, IOrientation /// private int CalcIdealLength () { - if (_options.Count == 0) + if (_options is { Count: 0 }) { return 0; } - bool isVertical = Orientation == Orientation.Vertical; var length = 0; - if (_config._showLegends) + if (!_config._showLegends) { - if (_config._legendsOrientation == _config._sliderOrientation && _options.Count > 0) - { - // Each legend should be centered in a space the width of the longest legend, with one space between. - // Calculate the total length required for all legends. - //if (!isVertical) - { - int maxLegend = int.Max (_options.Max (s => s.Legend?.GetColumns () ?? 1), 1); - length = maxLegend * _options.Count + (_options.Count - 1); - } + return Math.Max (length, CalcMinLength ()); + } - // - //{ - // length = CalcMinLength (); - //} - } - else - { - length = CalcMinLength (); - } + if (_config._legendsOrientation == _config._linearRangeOrientation && _options!.Count > 0) + { + // Each legend should be centered in a space the width of the longest legend, with one space between. + // Calculate the total length required for all legends. + int maxLegend = int.Max (_options.Max (s => s.Legend?.GetColumns () ?? 1), 1); + length = maxLegend * _options.Count + (_options.Count - 1); + } + else + { + length = CalcMinLength (); } return Math.Max (length, CalcMinLength ()); } /// - /// Calculates the minimum dimension required for the slider and legends. + /// Calculates the minimum dimension required for the linear range and legends. /// /// private int CalcIdealThickness () { - var thickness = 1; // Always show the slider. + var thickness = 1; // Always show the linear range. - if (_config._showLegends) + if (!_config._showLegends) { - if (_config._legendsOrientation != _config._sliderOrientation && _options.Count > 0) - { - thickness += _options.Max (s => s.Legend?.GetColumns () ?? 0); - } - else - { - thickness += 1; - } + return thickness; + } + + if (_config._legendsOrientation != _config._linearRangeOrientation && _options!.Count > 0) + { + thickness += _options.Max (s => s.Legend?.GetColumns () ?? 0); + } + else + { + thickness += 1; } return thickness; @@ -672,7 +797,7 @@ public class Slider : View, IOrientation { position = (-1, -1); - if (option < 0 || option >= _options.Count ()) + if (option < 0 || option >= _options!.Count ()) { return false; } @@ -681,7 +806,7 @@ public class Slider : View, IOrientation offset += _config._startSpacing; offset += option * (_config._cachedInnerSpacing + 1); - if (_config._sliderOrientation == Orientation.Vertical) + if (_config._linearRangeOrientation == Orientation.Vertical) { position = (0, offset); } @@ -701,7 +826,6 @@ public class Slider : View, IOrientation /// internal bool TryGetOptionByPosition (int x, int y, int threshold, out int optionIdx) { - // Fix(jmperricone): Not working. optionIdx = -1; if (Orientation == Orientation.Horizontal) @@ -719,7 +843,7 @@ public class Slider : View, IOrientation int option = cx / (_config._cachedInnerSpacing + 1); bool valid = cx % (_config._cachedInnerSpacing + 1) == 0; - if (!valid || option < 0 || option > _options.Count - 1) + if (!valid || option < 0 || option > _options!.Count - 1) { continue; } @@ -744,7 +868,7 @@ public class Slider : View, IOrientation int option = cy / (_config._cachedInnerSpacing + 1); bool valid = cy % (_config._cachedInnerSpacing + 1) == 0; - if (!valid || option < 0 || option > _options.Count - 1) + if (!valid || option < 0 || option > _options!.Count - 1) { continue; } @@ -761,17 +885,17 @@ public class Slider : View, IOrientation /// public override Point? PositionCursor () { - if (TryGetPositionByOption (FocusedOption, out (int x, int y) position)) + if (!TryGetPositionByOption (FocusedOption, out (int x, int y) position) + || !IsInitialized + || !Viewport.Contains (position.x, position.y)) { - if (IsInitialized && Viewport.Contains (position.x, position.y)) - { - Move (position.x, position.y); - - return new (position.x, position.y); - } + return base.PositionCursor (); } - return base.PositionCursor (); + Move (position.x, position.y); + + return new (position.x, position.y); + } #endregion Cursor and Position @@ -779,7 +903,7 @@ public class Slider : View, IOrientation #region Drawing /// - protected override bool OnDrawingContent (DrawContext context) + protected override bool OnDrawingContent (DrawContext? context) { // TODO: make this more surgical to reduce repaint @@ -788,8 +912,8 @@ public class Slider : View, IOrientation return true; } - // Draw Slider - DrawSlider (); + // Draw LinearRange + DrawLinearRange (); // Draw Legends. if (_config._showLegends) @@ -805,9 +929,9 @@ public class Slider : View, IOrientation return true; } - private string AlignText (string text, int width, Alignment alignment) + private static string AlignText (string? text, int width, Alignment alignment) { - if (text is null) + if (string.IsNullOrEmpty (text)) { return ""; } @@ -842,13 +966,12 @@ public class Slider : View, IOrientation } } - private void DrawSlider () + private void DrawLinearRange () { // TODO: be more surgical on clear - ClearViewport (null); + ClearViewport (); // Attributes - var normalAttr = new Attribute (Color.White, Color.Black); var setAttr = new Attribute (Color.Black, Color.White); @@ -858,9 +981,7 @@ public class Slider : View, IOrientation setAttr = Style.SetChar.Attribute ?? GetAttributeForRole (VisualRole.HotNormal); } - bool isVertical = _config._sliderOrientation == Orientation.Vertical; - bool isLegendsVertical = _config._legendsOrientation == Orientation.Vertical; - bool isReverse = _config._sliderOrientation != _config._legendsOrientation; + bool isVertical = _config._linearRangeOrientation == Orientation.Vertical; var x = 0; var y = 0; @@ -868,14 +989,14 @@ public class Slider : View, IOrientation bool isSet = _setOptions.Count > 0; // Left Spacing - if (_config._showEndSpacing && _config._startSpacing > 0) + if (_config is { _showEndSpacing: true, _startSpacing: > 0 }) { SetAttribute ( - isSet && _config._type == SliderType.LeftRange - ? Style.RangeChar.Attribute ?? normalAttr - : Style.SpaceChar.Attribute ?? normalAttr - ); - string text = isSet && _config._type == SliderType.LeftRange ? Style.RangeChar.Grapheme : Style.SpaceChar.Grapheme; + isSet && _config._type == LinearRangeType.LeftRange + ? Style.RangeChar.Attribute ?? normalAttr + : Style.SpaceChar.Attribute ?? normalAttr + ); + string text = isSet && _config._type == LinearRangeType.LeftRange ? Style.RangeChar.Grapheme : Style.SpaceChar.Grapheme; for (var i = 0; i < _config._startSpacing; i++) { @@ -911,7 +1032,7 @@ public class Slider : View, IOrientation } // Slider - if (_options.Count > 0) + if (_options!.Count > 0) { for (var i = 0; i < _options.Count; i++) { @@ -921,19 +1042,19 @@ public class Slider : View, IOrientation { switch (_config._type) { - case SliderType.LeftRange when i <= _setOptions [0]: + case LinearRangeType.LeftRange when i <= _setOptions [0]: drawRange = i < _setOptions [0]; break; - case SliderType.RightRange when i >= _setOptions [0]: + case LinearRangeType.RightRange when i >= _setOptions [0]: drawRange = i >= _setOptions [0]; break; - case SliderType.Range when _setOptions.Count == 1: + case LinearRangeType.Range when _setOptions.Count == 1: drawRange = false; break; - case SliderType.Range when _setOptions.Count == 2: + case LinearRangeType.Range when _setOptions.Count == 2: if ((i >= _setOptions [0] && i <= _setOptions [1]) || (i >= _setOptions [1] && i <= _setOptions [0])) { @@ -947,9 +1068,9 @@ public class Slider : View, IOrientation // Draw Option SetAttribute ( - isSet && _setOptions.Contains (i) ? Style.SetChar.Attribute ?? setAttr : - drawRange ? Style.RangeChar.Attribute ?? setAttr : Style.OptionChar.Attribute ?? normalAttr - ); + isSet && _setOptions.Contains (i) ? Style.SetChar.Attribute ?? setAttr : + drawRange ? Style.RangeChar.Attribute ?? setAttr : Style.OptionChar.Attribute ?? normalAttr + ); string text = drawRange ? Style.RangeChar.Grapheme : Style.OptionChar.Grapheme; @@ -981,27 +1102,29 @@ public class Slider : View, IOrientation } // Draw Spacing - if (_config._showEndSpacing || i < _options.Count - 1) + if (!_config._showEndSpacing && i >= _options.Count - 1) { - // Skip if is the Last Spacing. - SetAttribute ( - drawRange && isSet - ? Style.RangeChar.Attribute ?? setAttr - : Style.SpaceChar.Attribute ?? normalAttr - ); + continue; + } - for (var s = 0; s < _config._cachedInnerSpacing; s++) + // Skip if is the Last Spacing. + SetAttribute ( + drawRange && isSet + ? Style.RangeChar.Attribute ?? setAttr + : Style.SpaceChar.Attribute ?? normalAttr + ); + + for (var s = 0; s < _config._cachedInnerSpacing; s++) + { + MoveAndAdd (x, y, drawRange && isSet ? Style.RangeChar.Grapheme : Style.SpaceChar.Grapheme); + + if (isVertical) { - MoveAndAdd (x, y, drawRange && isSet ? Style.RangeChar.Grapheme : Style.SpaceChar.Grapheme); - - if (isVertical) - { - y++; - } - else - { - x++; - } + y++; + } + else + { + x++; } } } @@ -1013,11 +1136,11 @@ public class Slider : View, IOrientation if (_config._showEndSpacing) { SetAttribute ( - isSet && _config._type == SliderType.RightRange - ? Style.RangeChar.Attribute ?? normalAttr - : Style.SpaceChar.Attribute ?? normalAttr - ); - string text = isSet && _config._type == SliderType.RightRange ? Style.RangeChar.Grapheme : Style.SpaceChar.Grapheme; + isSet && _config._type == LinearRangeType.RightRange + ? Style.RangeChar.Attribute ?? normalAttr + : Style.SpaceChar.Attribute ?? normalAttr + ); + string text = isSet && _config._type == LinearRangeType.RightRange ? Style.RangeChar.Grapheme : Style.SpaceChar.Grapheme; for (var i = 0; i < remaining; i++) { @@ -1075,19 +1198,21 @@ public class Slider : View, IOrientation Move (x, y); - if (_config._sliderOrientation == Orientation.Horizontal - && _config._legendsOrientation == Orientation.Vertical) + switch (_config._linearRangeOrientation) { - x += _config._startSpacing; + case Orientation.Horizontal + when _config._legendsOrientation == Orientation.Vertical: + x += _config._startSpacing; + + break; + case Orientation.Vertical + when _config._legendsOrientation == Orientation.Horizontal: + y += _config._startSpacing; + + break; } - if (_config._sliderOrientation == Orientation.Vertical - && _config._legendsOrientation == Orientation.Horizontal) - { - y += _config._startSpacing; - } - - if (_config._sliderOrientation == Orientation.Horizontal) + if (_config._linearRangeOrientation == Orientation.Horizontal) { y += 1; } @@ -1097,43 +1222,43 @@ public class Slider : View, IOrientation x += 1; } - for (var i = 0; i < _options.Count; i++) + for (var i = 0; i < _options!.Count; i++) { var isOptionSet = false; // Check if the Option is Set. switch (_config._type) { - case SliderType.Single: - case SliderType.Multiple: + case LinearRangeType.Single: + case LinearRangeType.Multiple: if (isSet && _setOptions.Contains (i)) { isOptionSet = true; } break; - case SliderType.LeftRange: + case LinearRangeType.LeftRange: if (isSet && i <= _setOptions [0]) { isOptionSet = true; } break; - case SliderType.RightRange: + case LinearRangeType.RightRange: if (isSet && i >= _setOptions [0]) { isOptionSet = true; } break; - case SliderType.Range when _setOptions.Count == 1: + case LinearRangeType.Range when _setOptions.Count == 1: if (isSet && i == _setOptions [0]) { isOptionSet = true; } break; - case SliderType.Range: + case LinearRangeType.Range: if (isSet && ((i >= _setOptions [0] && i <= _setOptions [1]) || (i >= _setOptions [1] && i <= _setOptions [0]))) @@ -1142,21 +1267,17 @@ public class Slider : View, IOrientation } break; + default: + throw new ArgumentOutOfRangeException (); } // Text || Abbreviation - var text = string.Empty; - if (_config._showLegendsAbbr) - { - text = _options [i].LegendAbbr.ToString () ?? new Rune (_options [i].Legend.First ()).ToString (); - } - else - { - text = _options [i].Legend; - } + string text = (_config._showLegendsAbbr ? + _options [i].LegendAbbr.ToString () : + _options [i].Legend)!; - switch (_config._sliderOrientation) + switch (_config._linearRangeOrientation) { case Orientation.Horizontal: switch (_config._legendsOrientation) @@ -1193,21 +1314,19 @@ public class Slider : View, IOrientation int legendRightSpacesCount = text.Reverse ().TakeWhile (e => e == ' ').Count (); text = text.Trim (); - // TODO(jmperricone): Improve the Orientation check. - // Calculate Start Spacing - if (_config._sliderOrientation == _config._legendsOrientation) + if (_config._linearRangeOrientation == _config._legendsOrientation) { if (i == 0) { - // The spacing for the slider use the StartSpacing but... + // The spacing for the linear range use the StartSpacing but... // The spacing for the legends is the StartSpacing MINUS the total chars to the left of the first options. // ●────●────● // Hello Bye World // // chars_left is 2 for Hello => (5 - 1) / 2 // - // then the spacing is 2 for the slider but 0 for the legends. + // then the spacing is 2 for the linear range but 0 for the legends. int charsLeft = (text.Length - 1) / 2; legendLeftSpacesCount = _config._startSpacing - charsLeft; @@ -1222,8 +1341,6 @@ public class Slider : View, IOrientation { x += legendLeftSpacesCount; } - - //Move (x, y); } // Legend @@ -1233,7 +1350,6 @@ public class Slider : View, IOrientation { MoveAndAdd (x, y, c); - //Driver.AddRune (c); if (isTextVertical) { y += 1; @@ -1245,7 +1361,7 @@ public class Slider : View, IOrientation } // Calculate End Spacing - if (i == _options.Count () - 1) + if (i == _options.Count - 1) { // See Start Spacing explanation. int charsRight = text.Length / 2; @@ -1264,15 +1380,18 @@ public class Slider : View, IOrientation x += legendRightSpacesCount; } - if (_config._sliderOrientation == Orientation.Horizontal - && _config._legendsOrientation == Orientation.Vertical) + switch (_config._linearRangeOrientation) { - x += _config._cachedInnerSpacing + 1; - } - else if (_config._sliderOrientation == Orientation.Vertical - && _config._legendsOrientation == Orientation.Horizontal) - { - y += _config._cachedInnerSpacing + 1; + case Orientation.Horizontal + when _config._legendsOrientation == Orientation.Vertical: + x += _config._cachedInnerSpacing + 1; + + break; + case Orientation.Vertical + when _config._legendsOrientation == Orientation.Horizontal: + y += _config._cachedInnerSpacing + 1; + + break; } } } @@ -1290,12 +1409,6 @@ public class Slider : View, IOrientation /// protected override bool OnMouseEvent (MouseEventArgs mouseEvent) { - // Note(jmperricone): Maybe we click to focus the cursor, and on next click we set the option. - // That will make OptionFocused Event more relevant. - // (tig: I don't think so. Maybe an option if someone really wants it, but for now that - // adds too much friction to UI. - // TODO(jmperricone): Make Range Type work with mouse. - if (!(mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) || mouseEvent.Flags.HasFlag (MouseFlags.ReportMousePosition) @@ -1320,6 +1433,9 @@ public class Slider : View, IOrientation return true; } + bool success; + int option; + if (_dragPosition.HasValue && mouseEvent.Flags.HasFlag (MouseFlags.ReportMousePosition) && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)) @@ -1328,9 +1444,6 @@ public class Slider : View, IOrientation _dragPosition = mouseEvent.Position; _moveRenderPosition = ClampMovePosition ((Point)_dragPosition); - var success = false; - var option = 0; - // how far has user dragged from original location? if (Orientation == Orientation.Horizontal) { @@ -1354,51 +1467,49 @@ public class Slider : View, IOrientation return true; } - if ((_dragPosition.HasValue && mouseEvent.Flags.HasFlag (MouseFlags.Button1Released)) - || mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) + if ((!_dragPosition.HasValue || !mouseEvent.Flags.HasFlag (MouseFlags.Button1Released)) + && !mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) { - // End Drag - App?.Mouse.UngrabMouse (); - _dragPosition = null; - _moveRenderPosition = null; - - // TODO: Add func to calc distance between options to use as the MouseClickXOptionThreshold - var success = false; - var option = 0; - - if (Orientation == Orientation.Horizontal) - { - success = TryGetOptionByPosition (mouseEvent.Position.X, 0, Math.Max (0, _config._cachedInnerSpacing / 2), out option); - } - else - { - success = TryGetOptionByPosition (0, mouseEvent.Position.Y, Math.Max (0, _config._cachedInnerSpacing / 2), out option); - } - - if (success) - { - if (!OnOptionFocused (option, new (GetSetOptionDictionary (), FocusedOption))) - { - SetFocusedOption (); - } - } - - SetNeedsDraw (); - - mouseEvent.Handled = true; - + return mouseEvent.Handled; } + // End Drag + App?.Mouse.UngrabMouse (); + _dragPosition = null; + _moveRenderPosition = null; + + switch (Orientation) + { + case Orientation.Horizontal: + success = TryGetOptionByPosition (mouseEvent.Position.X, 0, Math.Max (0, _config._cachedInnerSpacing / 2), out option); + + break; + default: + success = TryGetOptionByPosition (0, mouseEvent.Position.Y, Math.Max (0, _config._cachedInnerSpacing / 2), out option); + + break; + } + + if (success) + { + if (!OnOptionFocused (option, new (GetSetOptionDictionary (), FocusedOption))) + { + SetFocusedOption (); + } + } + + SetNeedsDraw (); + + mouseEvent.Handled = true; + return mouseEvent.Handled; Point ClampMovePosition (Point position) { - int Clamp (int value, int min, int max) { return Math.Max (min, Math.Min (max, value)); } - if (Orientation == Orientation.Horizontal) { int left = _config._startSpacing; - int width = _options.Count + (_options.Count - 1) * _config._cachedInnerSpacing; + int width = _options!.Count + (_options.Count - 1) * _config._cachedInnerSpacing; int right = left + width - 1; int clampedX = Clamp (position.X, left, right); position = new (clampedX, 0); @@ -1406,13 +1517,15 @@ public class Slider : View, IOrientation else { int top = _config._startSpacing; - int height = _options.Count + (_options.Count - 1) * _config._cachedInnerSpacing; + int height = _options!.Count + (_options.Count - 1) * _config._cachedInnerSpacing; int bottom = top + height - 1; int clampedY = Clamp (position.Y, top, bottom); position = new (0, clampedY); } return position; + + static int Clamp (int value, int min, int max) { return Math.Max (min, Math.Min (max, value)); } } } @@ -1427,7 +1540,7 @@ public class Slider : View, IOrientation AddCommand (Command.RightExtend, () => ExtendPlus ()); AddCommand (Command.LeftExtend, () => ExtendMinus ()); AddCommand (Command.Activate, () => Select ()); - AddCommand (Command.Accept, (ctx) => Accept (ctx)); + AddCommand (Command.Accept, ctx => Accept (ctx)); SetKeyBindings (); } @@ -1435,7 +1548,7 @@ public class Slider : View, IOrientation // This is called during initialization and anytime orientation changes private void SetKeyBindings () { - if (_config._sliderOrientation == Orientation.Horizontal) + if (_config._linearRangeOrientation == Orientation.Horizontal) { KeyBindings.Add (Key.CursorRight, Command.Right); KeyBindings.Remove (Key.CursorDown); @@ -1470,10 +1583,13 @@ public class Slider : View, IOrientation KeyBindings.Add (Key.Space, Command.Activate); } - private Dictionary> GetSetOptionDictionary () { return _setOptions.ToDictionary (e => e, e => _options [e]); } + private Dictionary> GetSetOptionDictionary () + { + return _setOptions.ToDictionary (e => e, e => _options! [e]); + } /// - /// Sets or unsets based on . + /// Sets or unsets based on . /// /// The option to change. /// If , sets the option. Unsets it otherwise. @@ -1484,7 +1600,8 @@ public class Slider : View, IOrientation if (!_setOptions.Contains (optionIndex)) { _setOptions.Add (optionIndex); - _options [optionIndex].OnSet (); + + _options? [optionIndex].OnSet (); } } else @@ -1492,7 +1609,8 @@ public class Slider : View, IOrientation if (_setOptions.Contains (optionIndex)) { _setOptions.Remove (optionIndex); - _options [optionIndex].OnUnSet (); + + _options? [optionIndex].OnUnSet (); } } @@ -1502,16 +1620,18 @@ public class Slider : View, IOrientation private bool SetFocusedOption () { - if (_options.Count == 0) + if (_options is null or { Count: 0 }) { return false; } - bool changed = false; + + var changed = false; + switch (_config._type) { - case SliderType.Single: - case SliderType.LeftRange: - case SliderType.RightRange: + case LinearRangeType.Single: + case LinearRangeType.LeftRange: + case LinearRangeType.RightRange: if (_setOptions.Count == 1) { @@ -1542,7 +1662,7 @@ public class Slider : View, IOrientation changed = true; break; - case SliderType.Multiple: + case LinearRangeType.Multiple: if (_setOptions.Contains (FocusedOption)) { if (!_config._allowEmpty && _setOptions.Count () == 1) @@ -1564,7 +1684,7 @@ public class Slider : View, IOrientation break; - case SliderType.Range: + case LinearRangeType.Range: if (_config._rangeAllowSingle) { if (_setOptions.Count == 1) @@ -1692,7 +1812,7 @@ public class Slider : View, IOrientation } } - // Raise Slider Option Changed Event. + // Raise LinearRange Option Changed Event. OnOptionsChanged (); changed = true; @@ -1706,7 +1826,7 @@ public class Slider : View, IOrientation internal bool ExtendPlus () { - int next = FocusedOption < _options.Count - 1 ? FocusedOption + 1 : FocusedOption; + int next = _options is { } && FocusedOption < _options.Count - 1 ? FocusedOption + 1 : FocusedOption; if (next != FocusedOption && !OnOptionFocused ( @@ -1721,48 +1841,6 @@ public class Slider : View, IOrientation } return true; - - //// TODO: Support RangeMultiple - //if (_setOptions.Contains (FocusedOption)) { - // var next = FocusedOption < _options.Count - 1 ? FocusedOption + 1 : FocusedOption; - // if (!_setOptions.Contains (next)) { - // if (_config._type == SliderType.Range) { - // if (_setOptions.Count == 1) { - // if (!OnOptionFocused (next, new SliderEventArgs (GetSetOptionDictionary (), FocusedOption))) { - // _setOptions.Add (FocusedOption); - // _setOptions.Sort (); // Range Type - // OnOptionsChanged (); - // } - // } else if (_setOptions.Count == 2) { - // if (!OnOptionFocused (next, new SliderEventArgs (GetSetOptionDictionary (), FocusedOption))) { - // _setOptions [1] = FocusedOption; - // _setOptions.Sort (); // Range Type - // OnOptionsChanged (); - // } - // } - // } else { - // _setOptions.Remove (FocusedOption); - // // Note(jmperricone): We are setting the option here, do we send the OptionFocused Event too ? - - // if (!OnOptionFocused (next, new SliderEventArgs (GetSetOptionDictionary (), FocusedOption))) { - // _setOptions.Add (FocusedOption); - // _setOptions.Sort (); // Range Type - // OnOptionsChanged (); - // } - // } - // } else { - // if (_config._type == SliderType.Range) { - // if (!OnOptionFocused (next, new SliderEventArgs (GetSetOptionDictionary (), FocusedOption))) { - // _setOptions.Clear(); - // _setOptions.Add (FocusedOption); - // OnOptionsChanged (); - // } - // } else if (/*_settingRange == true ||*/ !AllowEmpty) { - // SetFocusedOption (); - // } - // } - //} - //return true; } internal bool ExtendMinus () @@ -1784,12 +1862,9 @@ public class Slider : View, IOrientation return true; } - internal bool Select () - { - return SetFocusedOption (); - } + internal bool Select () => SetFocusedOption (); - internal bool Accept (ICommandContext commandContext) + internal bool Accept (ICommandContext? commandContext) { SetFocusedOption (); @@ -1853,7 +1928,7 @@ public class Slider : View, IOrientation internal bool MoveEnd () { - if (OnOptionFocused (_options.Count - 1, new (GetSetOptionDictionary (), FocusedOption))) + if (OnOptionFocused (_options!.Count - 1, new (GetSetOptionDictionary (), FocusedOption))) { return false; } diff --git a/Terminal.Gui/Views/Slider/SliderAttributes.cs b/Terminal.Gui/Views/LinearRange/LinearRangeAttributes.cs similarity index 80% rename from Terminal.Gui/Views/Slider/SliderAttributes.cs rename to Terminal.Gui/Views/LinearRange/LinearRangeAttributes.cs index c57c8ab5a..e39261241 100644 --- a/Terminal.Gui/Views/Slider/SliderAttributes.cs +++ b/Terminal.Gui/Views/LinearRange/LinearRangeAttributes.cs @@ -1,7 +1,7 @@ namespace Terminal.Gui.Views; -/// Legend Style -public class SliderAttributes +/// Legend Style +public class LinearRangeAttributes { /// Attribute for the Legends Container. public Attribute? EmptyAttribute { get; set; } diff --git a/Terminal.Gui/Views/Slider/SliderConfiguration.cs b/Terminal.Gui/Views/LinearRange/LinearRangeConfiguration.cs similarity index 63% rename from Terminal.Gui/Views/Slider/SliderConfiguration.cs rename to Terminal.Gui/Views/LinearRange/LinearRangeConfiguration.cs index 1b9354a48..f0a94b62d 100644 --- a/Terminal.Gui/Views/Slider/SliderConfiguration.cs +++ b/Terminal.Gui/Views/LinearRange/LinearRangeConfiguration.cs @@ -1,8 +1,7 @@ - namespace Terminal.Gui.Views; -/// All configuration are grouped in this class. -internal class SliderConfiguration +/// All configuration are grouped in this class. +internal class LinearRangeConfiguration { internal bool _allowEmpty; internal int _endSpacing; @@ -13,8 +12,8 @@ internal class SliderConfiguration internal bool _showEndSpacing; internal bool _showLegends; internal bool _showLegendsAbbr; - internal Orientation _sliderOrientation = Orientation.Horizontal; + internal Orientation _linearRangeOrientation = Orientation.Horizontal; internal int _startSpacing; - internal SliderType _type = SliderType.Single; + internal LinearRangeType _type = LinearRangeType.Single; internal bool _useMinimumSize; } diff --git a/Terminal.Gui/Views/Slider/SliderEventArgs.cs b/Terminal.Gui/Views/LinearRange/LinearRangeEventArgs.cs similarity index 60% rename from Terminal.Gui/Views/Slider/SliderEventArgs.cs rename to Terminal.Gui/Views/LinearRange/LinearRangeEventArgs.cs index 0b55b4012..36442c8cb 100644 --- a/Terminal.Gui/Views/Slider/SliderEventArgs.cs +++ b/Terminal.Gui/Views/LinearRange/LinearRangeEventArgs.cs @@ -1,13 +1,12 @@ -#nullable disable namespace Terminal.Gui.Views; -/// for events. -public class SliderEventArgs : EventArgs +/// for events. +public class LinearRangeEventArgs : EventArgs { - /// Initializes a new instance of + /// Initializes a new instance of /// The current options. /// Index of the option that is focused. -1 if no option has the focus. - public SliderEventArgs (Dictionary> options, int focused = -1) + public LinearRangeEventArgs (Dictionary> options, int focused = -1) { Options = options; Focused = focused; @@ -21,5 +20,5 @@ public class SliderEventArgs : EventArgs public int Focused { get; set; } /// Gets/sets whether the option is set or not. - public Dictionary> Options { get; set; } + public Dictionary> Options { get; set; } } diff --git a/Terminal.Gui/Views/LinearRange/LinearRangeOption.cs b/Terminal.Gui/Views/LinearRange/LinearRangeOption.cs new file mode 100644 index 000000000..47fc52580 --- /dev/null +++ b/Terminal.Gui/Views/LinearRange/LinearRangeOption.cs @@ -0,0 +1,50 @@ +namespace Terminal.Gui.Views; + + /// Represents an option in a . + /// Data type of the option. + public class LinearRangeOption +{ + /// Creates a new empty instance of the class. + public LinearRangeOption () { } + + /// Creates a new instance of the class with values for each property. + public LinearRangeOption (string legend, Rune legendAbbr, T data) + { + Legend = legend; + LegendAbbr = legendAbbr; + Data = data; + } + + /// Event fired when an option has changed. + public event EventHandler? Changed; + + /// Custom data of the option. + public T? Data { get; set; } + + /// Legend of the option. + public string? Legend { get; set; } + + /// + /// Abbreviation of the Legend. When the too small to fit + /// . + /// + public Rune LegendAbbr { get; set; } + + /// Event Raised when this option is set. + public event EventHandler? Set; + + /// Creates a human-readable string that represents this . + public override string ToString () => "{Legend=" + Legend + ", LegendAbbr=" + LegendAbbr + ", Data=" + Data + "}"; + + /// Event Raised when this option is unset. + public event EventHandler? UnSet; + + /// To Raise the event from the LinearRange. + internal void OnChanged (bool isSet) { Changed?.Invoke (this, new (isSet)); } + + /// To Raise the event from the LinearRange. + internal void OnSet () { Set?.Invoke (this, new (true)); } + + /// To Raise the event from the LinearRange. + internal void OnUnSet () { UnSet?.Invoke (this, new (false)); } +} diff --git a/Terminal.Gui/Views/LinearRange/LinearRangeOptionEventArgs.cs b/Terminal.Gui/Views/LinearRange/LinearRangeOptionEventArgs.cs new file mode 100644 index 000000000..77a408878 --- /dev/null +++ b/Terminal.Gui/Views/LinearRange/LinearRangeOptionEventArgs.cs @@ -0,0 +1,12 @@ +namespace Terminal.Gui.Views; + +/// for events. +public class LinearRangeOptionEventArgs : EventArgs +{ + /// Initializes a new instance of + /// indicates whether the option is set + public LinearRangeOptionEventArgs (bool isSet) { IsSet = isSet; } + + /// Gets whether the option is set or not. + public bool IsSet { get; } +} diff --git a/Terminal.Gui/Views/Slider/SliderStyle.cs b/Terminal.Gui/Views/LinearRange/LinearRangeStyle.cs similarity index 64% rename from Terminal.Gui/Views/Slider/SliderStyle.cs rename to Terminal.Gui/Views/LinearRange/LinearRangeStyle.cs index aa5557116..aef55dbe7 100644 --- a/Terminal.Gui/Views/Slider/SliderStyle.cs +++ b/Terminal.Gui/Views/LinearRange/LinearRangeStyle.cs @@ -1,36 +1,35 @@ - -namespace Terminal.Gui.Views; +namespace Terminal.Gui.Views; -/// Style -public class SliderStyle +/// Style +public class LinearRangeStyle { /// Constructs a new instance. - public SliderStyle () { LegendAttributes = new (); } + public LinearRangeStyle () { LegendAttributes = new (); } /// The glyph and the attribute to indicate mouse dragging. public Cell DragChar { get; set; } - /// The glyph and the attribute used for empty spaces on the slider. + /// The glyph and the attribute used for empty spaces on the linear range. public Cell EmptyChar { get; set; } - /// The glyph and the attribute used for the end of ranges on the slider. + /// The glyph and the attribute used for the end of ranges on the linear range. public Cell EndRangeChar { get; set; } /// Legend attributes - public SliderAttributes LegendAttributes { get; set; } + public LinearRangeAttributes LegendAttributes { get; set; } - /// The glyph and the attribute used for each option (tick) on the slider. + /// The glyph and the attribute used for each option (tick) on the linear range. public Cell OptionChar { get; set; } - /// The glyph and the attribute used for filling in ranges on the slider. + /// The glyph and the attribute used for filling in ranges on the linear range. public Cell RangeChar { get; set; } - /// The glyph and the attribute used for options (ticks) that are set on the slider. + /// The glyph and the attribute used for options (ticks) that are set on the linear range. public Cell SetChar { get; set; } - /// The glyph and the attribute used for spaces between options (ticks) on the slider. + /// The glyph and the attribute used for spaces between options (ticks) on the linear range. public Cell SpaceChar { get; set; } - /// The glyph and the attribute used for the start of ranges on the slider. + /// The glyph and the attribute used for the start of ranges on the linear range. public Cell StartRangeChar { get; set; } } diff --git a/Terminal.Gui/Views/Slider/SliderType.cs b/Terminal.Gui/Views/LinearRange/LinearRangeType.cs similarity index 91% rename from Terminal.Gui/Views/Slider/SliderType.cs rename to Terminal.Gui/Views/LinearRange/LinearRangeType.cs index 7715aa3c3..0633a416e 100644 --- a/Terminal.Gui/Views/Slider/SliderType.cs +++ b/Terminal.Gui/Views/LinearRange/LinearRangeType.cs @@ -1,7 +1,7 @@ namespace Terminal.Gui.Views; -/// Types -public enum SliderType +/// Types +public enum LinearRangeType { /// /// diff --git a/Terminal.Gui/Views/Slider/SliderOption.cs b/Terminal.Gui/Views/Slider/SliderOption.cs deleted file mode 100644 index d5207c301..000000000 --- a/Terminal.Gui/Views/Slider/SliderOption.cs +++ /dev/null @@ -1,51 +0,0 @@ -#nullable disable -namespace Terminal.Gui.Views; - -/// Represents an option in a . -/// Data type of the option. -public class SliderOption -{ - /// Creates a new empty instance of the class. - public SliderOption () { } - - /// Creates a new instance of the class with values for each property. - public SliderOption (string legend, Rune legendAbbr, T data) - { - Legend = legend; - LegendAbbr = legendAbbr; - Data = data; - } - - /// Event fired when an option has changed. - public event EventHandler Changed; - - /// Custom data of the option. - public T Data { get; set; } - - /// Legend of the option. - public string Legend { get; set; } - - /// - /// Abbreviation of the Legend. When the too small to fit - /// . - /// - public Rune LegendAbbr { get; set; } - - /// Event Raised when this option is set. - public event EventHandler Set; - - /// Creates a human-readable string that represents this . - public override string ToString () { return "{Legend=" + Legend + ", LegendAbbr=" + LegendAbbr + ", Data=" + Data + "}"; } - - /// Event Raised when this option is unset. - public event EventHandler UnSet; - - /// To Raise the event from the Slider. - internal void OnChanged (bool isSet) { Changed?.Invoke (this, new (isSet)); } - - /// To Raise the event from the Slider. - internal void OnSet () { Set?.Invoke (this, new (true)); } - - /// To Raise the event from the Slider. - internal void OnUnSet () { UnSet?.Invoke (this, new (false)); } -} diff --git a/Terminal.Gui/Views/Slider/SliderOptionEventArgs.cs b/Terminal.Gui/Views/Slider/SliderOptionEventArgs.cs deleted file mode 100644 index 5176f9601..000000000 --- a/Terminal.Gui/Views/Slider/SliderOptionEventArgs.cs +++ /dev/null @@ -1,13 +0,0 @@ -#nullable disable -namespace Terminal.Gui.Views; - -/// for events. -public class SliderOptionEventArgs : EventArgs -{ - /// Initializes a new instance of - /// indicates whether the option is set - public SliderOptionEventArgs (bool isSet) { IsSet = isSet; } - - /// Gets whether the option is set or not. - public bool IsSet { get; } -} diff --git a/Tests/IntegrationTests/FluentTests/LinearRangeFluentTests.cs b/Tests/IntegrationTests/FluentTests/LinearRangeFluentTests.cs new file mode 100644 index 000000000..6d526c7e7 --- /dev/null +++ b/Tests/IntegrationTests/FluentTests/LinearRangeFluentTests.cs @@ -0,0 +1,144 @@ +using TerminalGuiFluentTesting; +using Xunit.Abstractions; + +namespace IntegrationTests.FluentTests; + +public class LinearRangeFluentTests (ITestOutputHelper outputHelper) +{ + private readonly TextWriter _out = new TestOutputWriter (outputHelper); + + [Theory] + [ClassData (typeof (TestDrivers))] + public void LinearRange_CanCreateAndRender (TestDriver d) + { + using GuiTestContext c = With.A (80, 25, d, _out) + .Add ( + new LinearRange (new() { 0, 10, 20, 30, 40, 50 }) + { + X = 2, + Y = 2, + Type = LinearRangeType.Single + }) + .Focus> () + .WaitIteration () + .ScreenShot ("LinearRange initial render", _out) + .Stop (); + } + + [Theory] + [ClassData (typeof (TestDrivers))] + public void LinearRange_CanNavigateWithArrowKeys (TestDriver d) + { + using GuiTestContext c = With.A (80, 25, d, _out) + .Add ( + new LinearRange (new() { 0, 10, 20, 30 }) + { + X = 2, + Y = 2, + Type = LinearRangeType.Single, + AllowEmpty = false + }) + .Focus> () + .WaitIteration () + .ScreenShot ("Initial state", _out) + .EnqueueKeyEvent (Key.CursorRight) + .WaitIteration () + .ScreenShot ("After right arrow", _out) + .EnqueueKeyEvent (Key.CursorRight) + .WaitIteration () + .ScreenShot ("After second right arrow", _out) + .Stop (); + } + + [Theory] + [ClassData (typeof (TestDrivers))] + public void LinearRange_TypeChange_TriggersEvents (TestDriver d) + { + LinearRange linearRange = new (new() { 0, 10, 20, 30 }) + { + X = 2, + Y = 2, + Type = LinearRangeType.Single + }; + + var changingEventRaised = false; + var changedEventRaised = false; + + linearRange.TypeChanging += (_, args) => + { + changingEventRaised = true; + Assert.Equal (LinearRangeType.Single, args.CurrentValue); + Assert.Equal (LinearRangeType.Range, args.NewValue); + }; + + linearRange.TypeChanged += (_, args) => + { + changedEventRaised = true; + Assert.Equal (LinearRangeType.Single, args.OldValue); + Assert.Equal (LinearRangeType.Range, args.NewValue); + }; + + // Change the type before adding to window + linearRange.Type = LinearRangeType.Range; + + using GuiTestContext c = With.A (80, 25, d, _out) + .Add (linearRange) + .Focus> () + .WaitIteration () + .ScreenShot ("After type change to Range", _out) + .Stop (); + + Assert.True (changingEventRaised); + Assert.True (changedEventRaised); + Assert.Equal (LinearRangeType.Range, linearRange.Type); + } + + [Theory] + [ClassData (typeof (TestDrivers))] + public void LinearRange_RangeType_CanSelectRange (TestDriver d) + { + using GuiTestContext c = With.A (80, 25, d, _out) + .Add ( + new LinearRange (new() { 0, 10, 20, 30, 40 }) + { + X = 2, + Y = 2, + Type = LinearRangeType.Range, + AllowEmpty = false + }) + .Focus> () + .WaitIteration () + .ScreenShot ("Range type initial", _out) + .EnqueueKeyEvent (Key.Space) + .WaitIteration () + .ScreenShot ("After first selection", _out) + .EnqueueKeyEvent (Key.CursorRight.WithCtrl) + .WaitIteration () + .EnqueueKeyEvent (Key.CursorRight.WithCtrl) + .WaitIteration () + .ScreenShot ("After extending range", _out) + .Stop (); + } + + [Theory] + [ClassData (typeof (TestDrivers))] + public void LinearRange_VerticalOrientation_Renders (TestDriver d) + { + using GuiTestContext c = With.A (80, 25, d, _out) + .Add ( + new LinearRange (new() { 0, 10, 20, 30 }) + { + X = 2, + Y = 2, + Orientation = Orientation.Vertical, + Type = LinearRangeType.Single + }) + .Focus> () + .WaitIteration () + .ScreenShot ("Vertical orientation", _out) + .EnqueueKeyEvent (Key.CursorDown) + .WaitIteration () + .ScreenShot ("After down arrow", _out) + .Stop (); + } +} diff --git a/Tests/UnitTestsParallelizable/ViewBase/Adornment/PaddingTests.cs b/Tests/UnitTestsParallelizable/ViewBase/Adornment/PaddingTests.cs index c81194394..7e211d16e 100644 --- a/Tests/UnitTestsParallelizable/ViewBase/Adornment/PaddingTests.cs +++ b/Tests/UnitTestsParallelizable/ViewBase/Adornment/PaddingTests.cs @@ -1,10 +1,6 @@ -#nullable enable -using UnitTests; -using Xunit.Abstractions; +namespace ViewBaseTests.Adornments; -namespace ViewBaseTests.Adornments; - -public class PaddingTests (ITestOutputHelper output) +public class PaddingTests { [Fact] public void Constructor_Defaults () @@ -21,5 +17,4 @@ public class PaddingTests (ITestOutputHelper output) View view = new () { Height = 3, Width = 3 }; Assert.Equal (Thickness.Empty, view.Padding!.Thickness); } - } diff --git a/Tests/UnitTestsParallelizable/Views/SliderTests.cs b/Tests/UnitTestsParallelizable/Views/LinearRangeTests.cs similarity index 50% rename from Tests/UnitTestsParallelizable/Views/SliderTests.cs rename to Tests/UnitTestsParallelizable/Views/LinearRangeTests.cs index 9aa71097d..b39432f05 100644 --- a/Tests/UnitTestsParallelizable/Views/SliderTests.cs +++ b/Tests/UnitTestsParallelizable/Views/LinearRangeTests.cs @@ -3,13 +3,13 @@ using UnitTests; namespace ViewsTests; -public class SliderOptionTests : FakeDriverBase +public class LinearRangeOptionTests : FakeDriverBase { [Fact] public void OnChanged_Should_Raise_ChangedEvent () { // Arrange - SliderOption sliderOption = new (); + LinearRangeOption sliderOption = new (); var eventRaised = false; sliderOption.Changed += (sender, args) => eventRaised = true; @@ -24,7 +24,7 @@ public class SliderOptionTests : FakeDriverBase public void OnSet_Should_Raise_SetEvent () { // Arrange - SliderOption sliderOption = new (); + LinearRangeOption sliderOption = new (); var eventRaised = false; sliderOption.Set += (sender, args) => eventRaised = true; @@ -39,7 +39,7 @@ public class SliderOptionTests : FakeDriverBase public void OnUnSet_Should_Raise_UnSetEvent () { // Arrange - SliderOption sliderOption = new (); + LinearRangeOption sliderOption = new (); var eventRaised = false; sliderOption.UnSet += (sender, args) => eventRaised = true; @@ -51,42 +51,42 @@ public class SliderOptionTests : FakeDriverBase } [Fact] - public void Slider_Option_Default_Constructor () + public void LinearRange_Option_Default_Constructor () { - SliderOption o = new (); + LinearRangeOption o = new (); Assert.Null (o.Legend); Assert.Equal (default (Rune), o.LegendAbbr); Assert.Equal (default (int), o.Data); } [Fact] - public void Slider_Option_Values_Constructor () + public void LinearRange_Option_Values_Constructor () { - SliderOption o = new ("1 thousand", new ('y'), 1000); + LinearRangeOption o = new ("1 thousand", new ('y'), 1000); Assert.Equal ("1 thousand", o.Legend); Assert.Equal (new ('y'), o.LegendAbbr); Assert.Equal (1000, o.Data); } [Fact] - public void SliderOption_ToString_WhenEmpty () + public void LinearRangeOption_ToString_WhenEmpty () { - SliderOption sliderOption = new (); + LinearRangeOption sliderOption = new (); Assert.Equal ("{Legend=, LegendAbbr=\0, Data=}", sliderOption.ToString ()); } [Fact] - public void SliderOption_ToString_WhenPopulated_WithInt () + public void LinearRangeOption_ToString_WhenPopulated_WithInt () { - SliderOption sliderOption = new () { Legend = "Lord flibble", LegendAbbr = new ('l'), Data = 1 }; + LinearRangeOption sliderOption = new () { Legend = "Lord flibble", LegendAbbr = new ('l'), Data = 1 }; Assert.Equal ("{Legend=Lord flibble, LegendAbbr=l, Data=1}", sliderOption.ToString ()); } [Fact] - public void SliderOption_ToString_WhenPopulated_WithSizeF () + public void LinearRangeOption_ToString_WhenPopulated_WithSizeF () { - SliderOption sliderOption = new () + LinearRangeOption sliderOption = new () { Legend = "Lord flibble", LegendAbbr = new ('l'), Data = new (32, 11) }; @@ -95,17 +95,17 @@ public class SliderOptionTests : FakeDriverBase } } -public class SliderEventArgsTests : FakeDriverBase +public class LinearRangeEventArgsTests : FakeDriverBase { [Fact] public void Constructor_Sets_Cancel_Default_To_False () { // Arrange - Dictionary> options = new (); + Dictionary> options = new (); var focused = 42; // Act - SliderEventArgs sliderEventArgs = new (options, focused); + LinearRangeEventArgs sliderEventArgs = new (options, focused); // Assert Assert.False (sliderEventArgs.Cancel); @@ -115,11 +115,11 @@ public class SliderEventArgsTests : FakeDriverBase public void Constructor_Sets_Focused () { // Arrange - Dictionary> options = new (); + Dictionary> options = new (); var focused = 42; // Act - SliderEventArgs sliderEventArgs = new (options, focused); + LinearRangeEventArgs sliderEventArgs = new (options, focused); // Assert Assert.Equal (focused, sliderEventArgs.Focused); @@ -129,23 +129,23 @@ public class SliderEventArgsTests : FakeDriverBase public void Constructor_Sets_Options () { // Arrange - Dictionary> options = new (); + Dictionary> options = new (); // Act - SliderEventArgs sliderEventArgs = new (options); + LinearRangeEventArgs sliderEventArgs = new (options); // Assert Assert.Equal (options, sliderEventArgs.Options); } } -public class SliderTests : FakeDriverBase +public class LinearRangeTests : FakeDriverBase { [Fact] public void Constructor_Default () { // Arrange & Act - Slider slider = new (); + LinearRange slider = new (); // Assert Assert.NotNull (slider); @@ -155,7 +155,7 @@ public class SliderTests : FakeDriverBase Assert.False (slider.AllowEmpty); Assert.True (slider.ShowLegends); Assert.False (slider.ShowEndSpacing); - Assert.Equal (SliderType.Single, slider.Type); + Assert.Equal (LinearRangeType.Single, slider.Type); Assert.Equal (1, slider.MinimumInnerSpacing); Assert.True (slider.Width is DimAuto); Assert.True (slider.Height is DimAuto); @@ -169,15 +169,15 @@ public class SliderTests : FakeDriverBase List options = new () { 1, 2, 3 }; // Act - Slider slider = new (options); + LinearRange slider = new (options); slider.SetRelativeLayout (new (100, 100)); // Assert // 0123456789 // 1 2 3 Assert.Equal (1, slider.MinimumInnerSpacing); - Assert.Equal (new Size (5, 2), slider.GetContentSize ()); - Assert.Equal (new Size (5, 2), slider.Frame.Size); + Assert.Equal (new (5, 2), slider.GetContentSize ()); + Assert.Equal (new (5, 2), slider.Frame.Size); Assert.NotNull (slider); Assert.NotNull (slider.Options); Assert.Equal (options.Count, slider.Options.Count); @@ -187,7 +187,7 @@ public class SliderTests : FakeDriverBase public void MovePlus_Should_MoveFocusRight_When_OptionIsAvailable () { // Arrange - Slider slider = new (new () { 1, 2, 3, 4 }); + LinearRange slider = new (new () { 1, 2, 3, 4 }); // Act bool result = slider.MovePlus (); @@ -201,7 +201,7 @@ public class SliderTests : FakeDriverBase public void MovePlus_Should_NotMoveFocusRight_When_AtEnd () { // Arrange - Slider slider = new (new () { 1, 2, 3, 4 }); + LinearRange slider = new (new () { 1, 2, 3, 4 }); slider.FocusedOption = 3; @@ -217,7 +217,7 @@ public class SliderTests : FakeDriverBase public void OnOptionFocused_Event_Cancelled () { // Arrange - Slider slider = new (new () { 1, 2, 3 }); + LinearRange slider = new (new () { 1, 2, 3 }); var eventRaised = false; var cancel = false; slider.OptionFocused += (sender, args) => eventRaised = true; @@ -226,7 +226,7 @@ public class SliderTests : FakeDriverBase // Create args with cancel set to false cancel = false; - SliderEventArgs args = + LinearRangeEventArgs args = new (new (), newFocusedOption) { Cancel = cancel }; Assert.Equal (0, slider.FocusedOption); @@ -257,11 +257,11 @@ public class SliderTests : FakeDriverBase public void OnOptionFocused_Event_Raised () { // Arrange - Slider slider = new (new () { 1, 2, 3 }); + LinearRange slider = new (new () { 1, 2, 3 }); var eventRaised = false; slider.OptionFocused += (sender, args) => eventRaised = true; var newFocusedOption = 1; - SliderEventArgs args = new (new (), newFocusedOption); + LinearRangeEventArgs args = new (new (), newFocusedOption); // Act slider.OnOptionFocused (newFocusedOption, args); @@ -274,7 +274,7 @@ public class SliderTests : FakeDriverBase public void OnOptionsChanged_Event_Raised () { // Arrange - Slider slider = new (); + LinearRange slider = new (); var eventRaised = false; slider.OptionsChanged += (sender, args) => eventRaised = true; @@ -289,7 +289,7 @@ public class SliderTests : FakeDriverBase public void Set_Should_Not_UnSetFocusedOption_When_EmptyNotAllowed () { // Arrange - Slider slider = new (new () { 1, 2, 3, 4 }) { AllowEmpty = false }; + LinearRange slider = new (new () { 1, 2, 3, 4 }) { AllowEmpty = false }; Assert.NotEmpty (slider.GetSetOptions ()); @@ -307,7 +307,7 @@ public class SliderTests : FakeDriverBase public void Set_Should_SetFocusedOption () { // Arrange - Slider slider = new (new () { 1, 2, 3, 4 }); + LinearRange slider = new (new () { 1, 2, 3, 4 }); // Act slider.FocusedOption = 2; @@ -323,7 +323,7 @@ public class SliderTests : FakeDriverBase public void TryGetOptionByPosition_InvalidPosition_Failure () { // Arrange - Slider slider = new (new () { 1, 2, 3 }); + LinearRange slider = new (new () { 1, 2, 3 }); var x = 10; var y = 10; var threshold = 2; @@ -347,7 +347,8 @@ public class SliderTests : FakeDriverBase public void TryGetOptionByPosition_ValidPositionHorizontal_Success (int x, int y, int threshold, int expectedData) { // Arrange - Slider slider = new (new () { 1, 2, 3, 4 }); + LinearRange slider = new (new () { 1, 2, 3, 4 }); + // 0123456789 // 1234 @@ -376,7 +377,7 @@ public class SliderTests : FakeDriverBase public void TryGetOptionByPosition_ValidPositionVertical_Success (int x, int y, int threshold, int expectedData) { // Arrange - Slider slider = new (new () { 1, 2, 3, 4 }); + LinearRange slider = new (new () { 1, 2, 3, 4 }); slider.Orientation = Orientation.Vertical; // Set auto size to true to enable testing @@ -405,7 +406,7 @@ public class SliderTests : FakeDriverBase public void TryGetPositionByOption_InvalidOption_Failure () { // Arrange - Slider slider = new (new () { 1, 2, 3 }); + LinearRange slider = new (new () { 1, 2, 3 }); int option = -1; (int, int) expectedPosition = (-1, -1); @@ -424,7 +425,7 @@ public class SliderTests : FakeDriverBase public void TryGetPositionByOption_ValidOptionHorizontal_Success (int option, int expectedX, int expectedY) { // Arrange - Slider slider = new (new () { 1, 2, 3, 4 }); + LinearRange slider = new (new () { 1, 2, 3, 4 }); // Set auto size to true to enable testing slider.MinimumInnerSpacing = 2; @@ -448,7 +449,7 @@ public class SliderTests : FakeDriverBase public void TryGetPositionByOption_ValidOptionVertical_Success (int option, int expectedX, int expectedY) { // Arrange - Slider slider = new (new () { 1, 2, 3, 4 }); + LinearRange slider = new (new () { 1, 2, 3, 4 }); slider.Orientation = Orientation.Vertical; // Set auto size to true to enable testing @@ -468,32 +469,12 @@ public class SliderTests : FakeDriverBase private void One_Option_Does_Not_Throw () { // Arrange - Slider slider = new (); + LinearRange slider = new (); slider.BeginInit (); slider.EndInit (); // Act/Assert - slider.Options = new () { new () }; - } - - [Fact] - private void Set_Options_No_Legend_Throws () - { - // Arrange - Slider slider = new (); - - // Act/Assert - Assert.Throws (() => slider.Options = null); - } - - [Fact] - private void Set_Options_Throws_If_Null () - { - // Arrange - Slider slider = new (); - - // Act/Assert - Assert.Throws (() => slider.Options = null); + slider.Options = [new ()]; } [Fact] @@ -505,12 +486,12 @@ public class SliderTests : FakeDriverBase Height = Dim.Fill () }; - List options = new () { "01234", "01234" }; + List options = ["01234", "01234"]; - Slider slider = new (options) + LinearRange slider = new (options) { Orientation = Orientation.Vertical, - Type = SliderType.Multiple, + Type = LinearRangeType.Multiple }; view.Add (slider); view.BeginInit (); @@ -539,10 +520,10 @@ public class SliderTests : FakeDriverBase List options = new () { "01234", "01234" }; - Slider slider = new (options) + LinearRange slider = new (options) { Orientation = Orientation.Vertical, - Type = SliderType.Multiple, + Type = LinearRangeType.Multiple, Height = 10 }; view.Add (slider); @@ -572,11 +553,11 @@ public class SliderTests : FakeDriverBase List options = new () { "01234", "01234" }; - Slider slider = new (options) + LinearRange slider = new (options) { Orientation = Orientation.Vertical, - Type = SliderType.Multiple, - Width = 10, + Type = LinearRangeType.Multiple, + Width = 10 }; view.Add (slider); view.BeginInit (); @@ -596,3 +577,254 @@ public class SliderTests : FakeDriverBase // Add more tests for different scenarios and edge cases. } +public class LinearRangeCWPTests : FakeDriverBase +{ + [Fact] + public void Type_PropertyChange_RaisesChangingAndChangedEvents () + { + // Arrange + LinearRange linearRange = new (); + var changingRaised = false; + var changedRaised = false; + var oldValue = LinearRangeType.Single; + var newValue = LinearRangeType.Range; + + linearRange.TypeChanging += (sender, args) => + { + changingRaised = true; + Assert.Equal (oldValue, args.CurrentValue); + Assert.Equal (newValue, args.NewValue); + }; + + linearRange.TypeChanged += (sender, args) => + { + changedRaised = true; + Assert.Equal (oldValue, args.OldValue); + Assert.Equal (newValue, args.NewValue); + }; + + // Act + linearRange.Type = newValue; + + // Assert + Assert.True (changingRaised); + Assert.True (changedRaised); + Assert.Equal (newValue, linearRange.Type); + } + + [Fact] + public void Type_PropertyChange_CanBeCancelled () + { + // Arrange + LinearRange linearRange = new (); + LinearRangeType oldValue = linearRange.Type; + + linearRange.TypeChanging += (sender, args) => { args.Handled = true; }; + + // Act + linearRange.Type = LinearRangeType.Range; + + // Assert + Assert.Equal (oldValue, linearRange.Type); + } + + [Fact] + public void LegendsOrientation_PropertyChange_RaisesChangingAndChangedEvents () + { + // Arrange + LinearRange linearRange = new (); + var changingRaised = false; + var changedRaised = false; + var oldValue = Orientation.Horizontal; + var newValue = Orientation.Vertical; + + linearRange.LegendsOrientationChanging += (sender, args) => + { + changingRaised = true; + Assert.Equal (oldValue, args.CurrentValue); + Assert.Equal (newValue, args.NewValue); + }; + + linearRange.LegendsOrientationChanged += (sender, args) => + { + changedRaised = true; + Assert.Equal (oldValue, args.OldValue); + Assert.Equal (newValue, args.NewValue); + }; + + // Act + linearRange.LegendsOrientation = newValue; + + // Assert + Assert.True (changingRaised); + Assert.True (changedRaised); + Assert.Equal (newValue, linearRange.LegendsOrientation); + } + + [Fact] + public void MinimumInnerSpacing_PropertyChange_RaisesChangingAndChangedEvents () + { + // Arrange + LinearRange linearRange = new (); + var changingRaised = false; + var changedRaised = false; + var oldValue = 1; + var newValue = 5; + + linearRange.MinimumInnerSpacingChanging += (sender, args) => + { + changingRaised = true; + Assert.Equal (oldValue, args.CurrentValue); + Assert.Equal (newValue, args.NewValue); + }; + + linearRange.MinimumInnerSpacingChanged += (sender, args) => + { + changedRaised = true; + Assert.Equal (oldValue, args.OldValue); + Assert.Equal (newValue, args.NewValue); + }; + + // Act + linearRange.MinimumInnerSpacing = newValue; + + // Assert + Assert.True (changingRaised); + Assert.True (changedRaised); + Assert.Equal (newValue, linearRange.MinimumInnerSpacing); + } + + [Fact] + public void ShowLegends_PropertyChange_RaisesChangingAndChangedEvents () + { + // Arrange + LinearRange linearRange = new (); + var changingRaised = false; + var changedRaised = false; + var oldValue = true; + var newValue = false; + + linearRange.ShowLegendsChanging += (sender, args) => + { + changingRaised = true; + Assert.Equal (oldValue, args.CurrentValue); + Assert.Equal (newValue, args.NewValue); + }; + + linearRange.ShowLegendsChanged += (sender, args) => + { + changedRaised = true; + Assert.Equal (oldValue, args.OldValue); + Assert.Equal (newValue, args.NewValue); + }; + + // Act + linearRange.ShowLegends = newValue; + + // Assert + Assert.True (changingRaised); + Assert.True (changedRaised); + Assert.Equal (newValue, linearRange.ShowLegends); + } + + [Fact] + public void ShowEndSpacing_PropertyChange_RaisesChangingAndChangedEvents () + { + // Arrange + LinearRange linearRange = new (); + var changingRaised = false; + var changedRaised = false; + var oldValue = false; + var newValue = true; + + linearRange.ShowEndSpacingChanging += (sender, args) => + { + changingRaised = true; + Assert.Equal (oldValue, args.CurrentValue); + Assert.Equal (newValue, args.NewValue); + }; + + linearRange.ShowEndSpacingChanged += (sender, args) => + { + changedRaised = true; + Assert.Equal (oldValue, args.OldValue); + Assert.Equal (newValue, args.NewValue); + }; + + // Act + linearRange.ShowEndSpacing = newValue; + + // Assert + Assert.True (changingRaised); + Assert.True (changedRaised); + Assert.Equal (newValue, linearRange.ShowEndSpacing); + } + + [Fact] + public void UseMinimumSize_PropertyChange_RaisesChangingAndChangedEvents () + { + // Arrange + LinearRange linearRange = new (); + var changingRaised = false; + var changedRaised = false; + var oldValue = false; + var newValue = true; + + linearRange.UseMinimumSizeChanging += (sender, args) => + { + changingRaised = true; + Assert.Equal (oldValue, args.CurrentValue); + Assert.Equal (newValue, args.NewValue); + }; + + linearRange.UseMinimumSizeChanged += (sender, args) => + { + changedRaised = true; + Assert.Equal (oldValue, args.OldValue); + Assert.Equal (newValue, args.NewValue); + }; + + // Act + linearRange.UseMinimumSize = newValue; + + // Assert + Assert.True (changingRaised); + Assert.True (changedRaised); + Assert.Equal (newValue, linearRange.UseMinimumSize); + } + + [Fact] + public void Type_PropertyChange_NoEventsWhenValueUnchanged () + { + // Arrange + LinearRange linearRange = new (); + var changingRaised = false; + var changedRaised = false; + + linearRange.TypeChanging += (sender, args) => changingRaised = true; + linearRange.TypeChanged += (sender, args) => changedRaised = true; + + // Act + linearRange.Type = linearRange.Type; + + // Assert + Assert.False (changingRaised); + Assert.False (changedRaised); + } + + [Fact] + public void Type_PropertyChange_ChangingEventCanModifyNewValue () + { + // Arrange + LinearRange linearRange = new (); + var modifiedValue = LinearRangeType.Multiple; + + linearRange.TypeChanging += (sender, args) => { args.NewValue = modifiedValue; }; + + // Act + linearRange.Type = LinearRangeType.Range; + + // Assert + Assert.Equal (modifiedValue, linearRange.Type); + } +}