Fixes #3829. Rename Slider to LinearRange and implement CWP (#4509)

* Initial plan

* Rename Slider to LinearRange - files and code updated

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Fix variable name mismatches in scenario files

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Fix typos in comments identified by code review

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Update HelpText for consistency with LinearRange rename

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Refactor LinearRange properties to use CWP with CWPPropertyHelper

- Added CWP events (Changing/Changed) for Type, LegendsOrientation, MinimumInnerSpacing, ShowLegends, ShowEndSpacing, and UseMinimumSize properties
- Used CWPPropertyHelper for consistent property change workflow
- Added virtual OnChanging/OnChanged methods for each property
- All existing tests pass

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Fix CWP property implementations - remove duplicate field updates in doWork

- CWPPropertyHelper updates the ref parameter, so doWork should only contain side effects
- Fixed Type, MinimumInnerSpacing, LegendsOrientation, ShowLegends, ShowEndSpacing, and UseMinimumSize
- Added comprehensive CWP tests for property changes (9 new tests, all passing)
- All 45 pre-existing LinearRange tests still pass (10 were already failing before changes)

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Add fluent integration tests for LinearRange

- Created LinearRangeFluentTests with 5 test scenarios
- Tests cover rendering, navigation, type changes with CWP events, range selection, and vertical orientation
- All tests build successfully

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Fix unit tests and address review comments

- Fixed review comments: Updated Title properties from "_Slider" to "_LinearRange" in DimAutoDemo and ViewportSettings
- Fixed review comment: Renamed filterSlider to filterLinearRange in Mouse.cs
- Fixed CWP implementation: Use local variable instead of backing field as ref parameter to CWPPropertyHelper
- This ensures doWork can update the backing field BEFORE SetContentSize() reads it
- All 55 LinearRange tests now passing (was 45/55, now 55/55)

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Update DimAutoDemo.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update ViewportSettings.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* code cleanup

* Refactor LinearRanges scenario for clarity and modularity

Refactored the LinearRanges scenario to separate UI setup, configuration, and event logic. The Main() method now handles all UI layout, configuration controls, and event wiring, while MakeSliders() is responsible only for adding LinearRange controls to the main window. Updated event handlers to use discard parameters for unused arguments and modernized code with collection initializers and LINQ. This results in a clearer separation of concerns and improved maintainability.

* un did change

* cleanup

* cleanup

* cleanup

* fixed unit test
code cleanup

---------

Co-authored-by: Tig <tig@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tig <585482+tig@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Copilot
2025-12-21 10:40:09 -07:00
committed by GitHub
parent 4145b984ba
commit fb4043afbc
18 changed files with 1620 additions and 1185 deletions

View File

@@ -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<object> 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;
}

View File

@@ -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<string> 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<LinearRange> ())
{
s.UseMinimumSize = !s.UseMinimumSize;
}
};
configView.Add (dimAutoUsesMin);
#region LinearRange Orientation LinearRange
LinearRange<string> 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<string> 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<int> innerSpacingUpDown = new ()
{
X = Pos.Right (label) + 1
};
innerSpacingUpDown.Value = mainWindow.SubViews.OfType<LinearRange> ().First ().MinimumInnerSpacing;
innerSpacingUpDown.ValueChanging += (_, e) =>
{
if (e.NewValue < 0)
{
e.Cancel = true;
return;
}
foreach (LinearRange s in mainWindow.SubViews.OfType<LinearRange> ())
{
s.MinimumInnerSpacing = e.NewValue;
}
};
spacingOptions.Add (label, innerSpacingUpDown);
configView.Add (spacingOptions);
#endregion
#region Color LinearRange
foreach (LinearRange s in mainWindow.SubViews.OfType<LinearRange> ())
{
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<LinearRangeOption<(Color, Color)>> colorOptions = [];
colorOptions.AddRange (
from colorIndex in Enum.GetValues<ColorName16> ()
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<LinearRange> ())
{
s.SetScheme (
new (s.GetScheme ())
{
Normal = new (
s.GetAttributeForRole (VisualRole.Normal).Foreground,
data.Item2
)
});
}
};
#endregion Color LinearRange
#endregion Config LinearRange
ObservableCollection<string> eventSource = [];
ListView eventLog = new ()
{
X = Pos.Right (sliderBgColor),
Y = Pos.Bottom (spacingOptions),
Width = Dim.Fill (),
Height = Dim.Fill (),
SchemeName = "Runnable",
Source = new ListWrapper<string> (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<LinearRange> ())
{
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<string> e)
{
foreach (LinearRange s in mainWindow.SubViews.OfType<LinearRange> ())
{
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<string> e)
{
View prev = null;
foreach (LinearRange s in mainWindow.SubViews.OfType<LinearRange> ())
{
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<string> e)
{
foreach (LinearRange s in mainWindow.SubViews.OfType<LinearRange> ())
{
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<object> options)
{
List<LinearRangeType> types = Enum.GetValues (typeof (LinearRangeType)).Cast<LinearRangeType> ().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<object> 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<object> 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);
}
}

View File

@@ -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<string>
HelpText = "LinearRanges work!",
CommandView = new LinearRange<string>
{
Orientation = Orientation.Horizontal,
AllowEmpty = true
@@ -238,13 +238,13 @@ public class Shortcuts : Scenario
Key = Key.F5
};
((Slider<string>)sliderShortcut.CommandView).Options = [new () { Legend = "A" }, new () { Legend = "B" }, new () { Legend = "C" }];
((Slider<string>)sliderShortcut.CommandView).SetOption (0);
((LinearRange<string>)sliderShortcut.CommandView).Options = [new () { Legend = "A" }, new () { Legend = "B" }, new () { Legend = "C" }];
((LinearRange<string>)sliderShortcut.CommandView).SetOption (0);
((Slider<string>)sliderShortcut.CommandView).OptionsChanged += (o, args) =>
((LinearRange<string>)sliderShortcut.CommandView).OptionsChanged += (o, args) =>
{
eventSource.Add (
$"OptionsChanged: {o?.GetType ().Name} - {string.Join (",", ((Slider<string>)o!)!.GetSetOptions ())}");
$"OptionsChanged: {o?.GetType ().Name} - {string.Join (",", ((LinearRange<string>)o!)!.GetSetOptions ())}");
eventLog.MoveDown ();
};

View File

@@ -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<object> options)
{
List<SliderType> types = Enum.GetValues (typeof (SliderType)).Cast<SliderType> ().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<object> 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<object> 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<string> 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<Slider> ())
{
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<Slider> ())
{
s.UseMinimumSize = !s.UseMinimumSize;
}
};
configView.Add (dimAutoUsesMin);
#region Slider Orientation Slider
Slider<string> 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<Slider> ())
{
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<string> 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<Slider> ())
{
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<int> innerSpacingUpDown = new ()
{
X = Pos.Right (label) + 1
};
innerSpacingUpDown.Value = app.SubViews.OfType<Slider> ().First ().MinimumInnerSpacing;
innerSpacingUpDown.ValueChanging += (sender, e) =>
{
if (e.NewValue < 0)
{
e.Cancel = true;
return;
}
foreach (Slider s in app.SubViews.OfType<Slider> ())
{
s.MinimumInnerSpacing = e.NewValue;
}
};
spacingOptions.Add (label, innerSpacingUpDown);
configView.Add (spacingOptions);
#endregion
#region Color Slider
foreach (Slider s in app.SubViews.OfType<Slider> ())
{
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<SliderOption<(Color, Color)>> colorOptions = new ();
foreach (ColorName16 colorIndex in Enum.GetValues<ColorName16> ())
{
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<Slider> ())
{
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<Slider> ())
{
s.SetScheme (
new (s.GetScheme ())
{
Normal = new (
s.GetAttributeForRole (VisualRole.Normal).Foreground,
data.Item2
)
});
}
}
};
#endregion Color Slider
#endregion Config Slider
ObservableCollection<string> 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<string> (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 ();
}
}

View File

@@ -201,17 +201,17 @@ public class ViewportSettings : Scenario
List<object> 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) =>
{

View File

@@ -1,7 +1,7 @@
namespace Terminal.Gui.Views;
/// <summary><see cref="Slider{T}"/> Legend Style</summary>
public class SliderAttributes
/// <summary><see cref="LinearRange{T}"/> Legend Style</summary>
public class LinearRangeAttributes
{
/// <summary>Attribute for the Legends Container.</summary>
public Attribute? EmptyAttribute { get; set; }

View File

@@ -1,8 +1,7 @@
namespace Terminal.Gui.Views;
/// <summary>All <see cref="Slider{T}"/> configuration are grouped in this class.</summary>
internal class SliderConfiguration
/// <summary>All <see cref="LinearRange{T}"/> configuration are grouped in this class.</summary>
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;
}

View File

@@ -1,13 +1,12 @@
#nullable disable
namespace Terminal.Gui.Views;
/// <summary><see cref="EventArgs"/> for <see cref="Slider{T}"/> events.</summary>
public class SliderEventArgs<T> : EventArgs
/// <summary><see cref="EventArgs"/> for <see cref="LinearRange{T}"/> events.</summary>
public class LinearRangeEventArgs<T> : EventArgs
{
/// <summary>Initializes a new instance of <see cref="SliderEventArgs{T}"/></summary>
/// <summary>Initializes a new instance of <see cref="LinearRangeEventArgs{T}"/></summary>
/// <param name="options">The current options.</param>
/// <param name="focused">Index of the option that is focused. -1 if no option has the focus.</param>
public SliderEventArgs (Dictionary<int, SliderOption<T>> options, int focused = -1)
public LinearRangeEventArgs (Dictionary<int, LinearRangeOption<T>> options, int focused = -1)
{
Options = options;
Focused = focused;
@@ -21,5 +20,5 @@ public class SliderEventArgs<T> : EventArgs
public int Focused { get; set; }
/// <summary>Gets/sets whether the option is set or not.</summary>
public Dictionary<int, SliderOption<T>> Options { get; set; }
public Dictionary<int, LinearRangeOption<T>> Options { get; set; }
}

View File

@@ -0,0 +1,50 @@
namespace Terminal.Gui.Views;
/// <summary>Represents an option in a <see cref="LinearRange{T}"/> .</summary>
/// <typeparam name="T">Data type of the option.</typeparam>
public class LinearRangeOption<T>
{
/// <summary>Creates a new empty instance of the <see cref="LinearRangeOption{T}"/> class.</summary>
public LinearRangeOption () { }
/// <summary>Creates a new instance of the <see cref="LinearRangeOption{T}"/> class with values for each property.</summary>
public LinearRangeOption (string legend, Rune legendAbbr, T data)
{
Legend = legend;
LegendAbbr = legendAbbr;
Data = data;
}
/// <summary>Event fired when an option has changed.</summary>
public event EventHandler<LinearRangeOptionEventArgs>? Changed;
/// <summary>Custom data of the option.</summary>
public T? Data { get; set; }
/// <summary>Legend of the option.</summary>
public string? Legend { get; set; }
/// <summary>
/// Abbreviation of the Legend. When the <see cref="LinearRange{T}.MinimumInnerSpacing"/> too small to fit
/// <see cref="Legend"/>.
/// </summary>
public Rune LegendAbbr { get; set; }
/// <summary>Event Raised when this option is set.</summary>
public event EventHandler<LinearRangeOptionEventArgs>? Set;
/// <summary>Creates a human-readable string that represents this <see cref="LinearRangeOption{T}"/>.</summary>
public override string ToString () => "{Legend=" + Legend + ", LegendAbbr=" + LegendAbbr + ", Data=" + Data + "}";
/// <summary>Event Raised when this option is unset.</summary>
public event EventHandler<LinearRangeOptionEventArgs>? UnSet;
/// <summary>To Raise the <see cref="Changed"/> event from the LinearRange.</summary>
internal void OnChanged (bool isSet) { Changed?.Invoke (this, new (isSet)); }
/// <summary>To Raise the <see cref="Set"/> event from the LinearRange.</summary>
internal void OnSet () { Set?.Invoke (this, new (true)); }
/// <summary>To Raise the <see cref="UnSet"/> event from the LinearRange.</summary>
internal void OnUnSet () { UnSet?.Invoke (this, new (false)); }
}

View File

@@ -0,0 +1,12 @@
namespace Terminal.Gui.Views;
/// <summary><see cref="EventArgs"/> for <see cref="LinearRange{T}"/> <see cref="LinearRangeOption{T}"/> events.</summary>
public class LinearRangeOptionEventArgs : EventArgs
{
/// <summary>Initializes a new instance of <see cref="LinearRangeOptionEventArgs"/></summary>
/// <param name="isSet"> indicates whether the option is set</param>
public LinearRangeOptionEventArgs (bool isSet) { IsSet = isSet; }
/// <summary>Gets whether the option is set or not.</summary>
public bool IsSet { get; }
}

View File

@@ -1,36 +1,35 @@

namespace Terminal.Gui.Views;
namespace Terminal.Gui.Views;
/// <summary><see cref="Slider{T}"/> Style</summary>
public class SliderStyle
/// <summary><see cref="LinearRange{T}"/> Style</summary>
public class LinearRangeStyle
{
/// <summary>Constructs a new instance.</summary>
public SliderStyle () { LegendAttributes = new (); }
public LinearRangeStyle () { LegendAttributes = new (); }
/// <summary>The glyph and the attribute to indicate mouse dragging.</summary>
public Cell DragChar { get; set; }
/// <summary>The glyph and the attribute used for empty spaces on the slider.</summary>
/// <summary>The glyph and the attribute used for empty spaces on the linear range.</summary>
public Cell EmptyChar { get; set; }
/// <summary>The glyph and the attribute used for the end of ranges on the slider.</summary>
/// <summary>The glyph and the attribute used for the end of ranges on the linear range.</summary>
public Cell EndRangeChar { get; set; }
/// <summary>Legend attributes</summary>
public SliderAttributes LegendAttributes { get; set; }
public LinearRangeAttributes LegendAttributes { get; set; }
/// <summary>The glyph and the attribute used for each option (tick) on the slider.</summary>
/// <summary>The glyph and the attribute used for each option (tick) on the linear range.</summary>
public Cell OptionChar { get; set; }
/// <summary>The glyph and the attribute used for filling in ranges on the slider.</summary>
/// <summary>The glyph and the attribute used for filling in ranges on the linear range.</summary>
public Cell RangeChar { get; set; }
/// <summary>The glyph and the attribute used for options (ticks) that are set on the slider.</summary>
/// <summary>The glyph and the attribute used for options (ticks) that are set on the linear range.</summary>
public Cell SetChar { get; set; }
/// <summary>The glyph and the attribute used for spaces between options (ticks) on the slider.</summary>
/// <summary>The glyph and the attribute used for spaces between options (ticks) on the linear range.</summary>
public Cell SpaceChar { get; set; }
/// <summary>The glyph and the attribute used for the start of ranges on the slider.</summary>
/// <summary>The glyph and the attribute used for the start of ranges on the linear range.</summary>
public Cell StartRangeChar { get; set; }
}

View File

@@ -1,7 +1,7 @@
namespace Terminal.Gui.Views;
/// <summary><see cref="Slider{T}"/> Types</summary>
public enum SliderType
/// <summary><see cref="LinearRange{T}"/> Types</summary>
public enum LinearRangeType
{
/// <summary>
/// <code>

View File

@@ -1,51 +0,0 @@
#nullable disable
namespace Terminal.Gui.Views;
/// <summary>Represents an option in a <see cref="Slider{T}"/> .</summary>
/// <typeparam name="T">Data type of the option.</typeparam>
public class SliderOption<T>
{
/// <summary>Creates a new empty instance of the <see cref="SliderOption{T}"/> class.</summary>
public SliderOption () { }
/// <summary>Creates a new instance of the <see cref="SliderOption{T}"/> class with values for each property.</summary>
public SliderOption (string legend, Rune legendAbbr, T data)
{
Legend = legend;
LegendAbbr = legendAbbr;
Data = data;
}
/// <summary>Event fired when an option has changed.</summary>
public event EventHandler<SliderOptionEventArgs> Changed;
/// <summary>Custom data of the option.</summary>
public T Data { get; set; }
/// <summary>Legend of the option.</summary>
public string Legend { get; set; }
/// <summary>
/// Abbreviation of the Legend. When the <see cref="Slider{T}.MinimumInnerSpacing"/> too small to fit
/// <see cref="Legend"/>.
/// </summary>
public Rune LegendAbbr { get; set; }
/// <summary>Event Raised when this option is set.</summary>
public event EventHandler<SliderOptionEventArgs> Set;
/// <summary>Creates a human-readable string that represents this <see cref="SliderOption{T}"/>.</summary>
public override string ToString () { return "{Legend=" + Legend + ", LegendAbbr=" + LegendAbbr + ", Data=" + Data + "}"; }
/// <summary>Event Raised when this option is unset.</summary>
public event EventHandler<SliderOptionEventArgs> UnSet;
/// <summary>To Raise the <see cref="Changed"/> event from the Slider.</summary>
internal void OnChanged (bool isSet) { Changed?.Invoke (this, new (isSet)); }
/// <summary>To Raise the <see cref="Set"/> event from the Slider.</summary>
internal void OnSet () { Set?.Invoke (this, new (true)); }
/// <summary>To Raise the <see cref="UnSet"/> event from the Slider.</summary>
internal void OnUnSet () { UnSet?.Invoke (this, new (false)); }
}

View File

@@ -1,13 +0,0 @@
#nullable disable
namespace Terminal.Gui.Views;
/// <summary><see cref="EventArgs"/> for <see cref="Slider{T}"/> <see cref="SliderOption{T}"/> events.</summary>
public class SliderOptionEventArgs : EventArgs
{
/// <summary>Initializes a new instance of <see cref="SliderOptionEventArgs"/></summary>
/// <param name="isSet"> indicates whether the option is set</param>
public SliderOptionEventArgs (bool isSet) { IsSet = isSet; }
/// <summary>Gets whether the option is set or not.</summary>
public bool IsSet { get; }
}

View File

@@ -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<Window> (80, 25, d, _out)
.Add (
new LinearRange<int> (new() { 0, 10, 20, 30, 40, 50 })
{
X = 2,
Y = 2,
Type = LinearRangeType.Single
})
.Focus<LinearRange<int>> ()
.WaitIteration ()
.ScreenShot ("LinearRange initial render", _out)
.Stop ();
}
[Theory]
[ClassData (typeof (TestDrivers))]
public void LinearRange_CanNavigateWithArrowKeys (TestDriver d)
{
using GuiTestContext c = With.A<Window> (80, 25, d, _out)
.Add (
new LinearRange<int> (new() { 0, 10, 20, 30 })
{
X = 2,
Y = 2,
Type = LinearRangeType.Single,
AllowEmpty = false
})
.Focus<LinearRange<int>> ()
.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<int> 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<Window> (80, 25, d, _out)
.Add (linearRange)
.Focus<LinearRange<int>> ()
.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<Window> (80, 25, d, _out)
.Add (
new LinearRange<int> (new() { 0, 10, 20, 30, 40 })
{
X = 2,
Y = 2,
Type = LinearRangeType.Range,
AllowEmpty = false
})
.Focus<LinearRange<int>> ()
.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<Window> (80, 25, d, _out)
.Add (
new LinearRange<int> (new() { 0, 10, 20, 30 })
{
X = 2,
Y = 2,
Orientation = Orientation.Vertical,
Type = LinearRangeType.Single
})
.Focus<LinearRange<int>> ()
.WaitIteration ()
.ScreenShot ("Vertical orientation", _out)
.EnqueueKeyEvent (Key.CursorDown)
.WaitIteration ()
.ScreenShot ("After down arrow", _out)
.Stop ();
}
}

View File

@@ -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);
}
}

View File

@@ -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<int> sliderOption = new ();
LinearRangeOption<int> 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<int> sliderOption = new ();
LinearRangeOption<int> 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<int> sliderOption = new ();
LinearRangeOption<int> 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<int> o = new ();
LinearRangeOption<int> 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<int> o = new ("1 thousand", new ('y'), 1000);
LinearRangeOption<int> 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<object> sliderOption = new ();
LinearRangeOption<object> sliderOption = new ();
Assert.Equal ("{Legend=, LegendAbbr=\0, Data=}", sliderOption.ToString ());
}
[Fact]
public void SliderOption_ToString_WhenPopulated_WithInt ()
public void LinearRangeOption_ToString_WhenPopulated_WithInt ()
{
SliderOption<int> sliderOption = new () { Legend = "Lord flibble", LegendAbbr = new ('l'), Data = 1 };
LinearRangeOption<int> 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<SizeF> sliderOption = new ()
LinearRangeOption<SizeF> 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<int, SliderOption<int>> options = new ();
Dictionary<int, LinearRangeOption<int>> options = new ();
var focused = 42;
// Act
SliderEventArgs<int> sliderEventArgs = new (options, focused);
LinearRangeEventArgs<int> sliderEventArgs = new (options, focused);
// Assert
Assert.False (sliderEventArgs.Cancel);
@@ -115,11 +115,11 @@ public class SliderEventArgsTests : FakeDriverBase
public void Constructor_Sets_Focused ()
{
// Arrange
Dictionary<int, SliderOption<int>> options = new ();
Dictionary<int, LinearRangeOption<int>> options = new ();
var focused = 42;
// Act
SliderEventArgs<int> sliderEventArgs = new (options, focused);
LinearRangeEventArgs<int> 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<int, SliderOption<int>> options = new ();
Dictionary<int, LinearRangeOption<int>> options = new ();
// Act
SliderEventArgs<int> sliderEventArgs = new (options);
LinearRangeEventArgs<int> 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<int> slider = new ();
LinearRange<int> 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<int> options = new () { 1, 2, 3 };
// Act
Slider<int> slider = new (options);
LinearRange<int> 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<int> slider = new (new () { 1, 2, 3, 4 });
LinearRange<int> 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<int> slider = new (new () { 1, 2, 3, 4 });
LinearRange<int> 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<int> slider = new (new () { 1, 2, 3 });
LinearRange<int> 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<int> args =
LinearRangeEventArgs<int> 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<int> slider = new (new () { 1, 2, 3 });
LinearRange<int> slider = new (new () { 1, 2, 3 });
var eventRaised = false;
slider.OptionFocused += (sender, args) => eventRaised = true;
var newFocusedOption = 1;
SliderEventArgs<int> args = new (new (), newFocusedOption);
LinearRangeEventArgs<int> args = new (new (), newFocusedOption);
// Act
slider.OnOptionFocused (newFocusedOption, args);
@@ -274,7 +274,7 @@ public class SliderTests : FakeDriverBase
public void OnOptionsChanged_Event_Raised ()
{
// Arrange
Slider<int> slider = new ();
LinearRange<int> 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<int> slider = new (new () { 1, 2, 3, 4 }) { AllowEmpty = false };
LinearRange<int> 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<int> slider = new (new () { 1, 2, 3, 4 });
LinearRange<int> 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<int> slider = new (new () { 1, 2, 3 });
LinearRange<int> 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<int> slider = new (new () { 1, 2, 3, 4 });
LinearRange<int> 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<int> slider = new (new () { 1, 2, 3, 4 });
LinearRange<int> 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<int> slider = new (new () { 1, 2, 3 });
LinearRange<int> 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<int> slider = new (new () { 1, 2, 3, 4 });
LinearRange<int> 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<int> slider = new (new () { 1, 2, 3, 4 });
LinearRange<int> 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<int> slider = new ();
LinearRange<int> slider = new ();
slider.BeginInit ();
slider.EndInit ();
// Act/Assert
slider.Options = new () { new () };
}
[Fact]
private void Set_Options_No_Legend_Throws ()
{
// Arrange
Slider<int> slider = new ();
// Act/Assert
Assert.Throws<ArgumentNullException> (() => slider.Options = null);
}
[Fact]
private void Set_Options_Throws_If_Null ()
{
// Arrange
Slider<int> slider = new ();
// Act/Assert
Assert.Throws<ArgumentNullException> (() => slider.Options = null);
slider.Options = [new ()];
}
[Fact]
@@ -505,12 +486,12 @@ public class SliderTests : FakeDriverBase
Height = Dim.Fill ()
};
List<object> options = new () { "01234", "01234" };
List<object> 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<object> 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<object> 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> linearRange = new ();
var modifiedValue = LinearRangeType.Multiple;
linearRange.TypeChanging += (sender, args) => { args.NewValue = modifiedValue; };
// Act
linearRange.Type = LinearRangeType.Range;
// Assert
Assert.Equal (modifiedValue, linearRange.Type);
}
}