Files
Terminal.Gui/Examples/UICatalog/Scenarios/Sliders.cs
BDisp cd75a20c60 Fixes #4387. Runes should not be used on a cell, but rather should use a single grapheme rendering 1 or 2 columns (#4388)
* Fixes #4382. StringExtensions.GetColumns method should only return the total text width and not the sum of all runes width

* Trying to fix unit test error

* Update StringExtensions.cs

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

* Resolving merge conflicts

* Prevents Runes throwing if Grapheme is null

* Add unit test to prove that null and empty string doesn't not throws anything.

* Fix unit test failure

* Fix IsValidLocation for wide graphemes

* Add more combining

* Prevent set invalid graphemes

* Fix unit tests

* Grapheme doesn't support invalid code points like lone surrogates

* Fixes more unit tests

* Fix unit test

* Seems all test are fixed now

* Adjust CharMap scenario with graphemes

* Upgrade Wcwidth to version 4.0.0

* Reformat

* Trying fix CheckDefaultState assertion

* Revert "Trying fix CheckDefaultState assertion"

This reverts commit c9b46b796a.

* Forgot to include driver.End in the test

* Reapply "Trying fix CheckDefaultState assertion"

This reverts commit 1060ac9b63.

* Remove ToString

* Fix merge errors

* Change to conditional expression

* Assertion to prove that no exception throws during cell initialization.

* Remove unnecessary assignment

* Remove assignment to end

* Replace string concatenation with 'StringBuilder'.

* Replace more string concatenation with 'StringBuilder'

* Remove redundant call to 'ToString' because Rune cast to a String object.

* Replace foreach loop with Sum linq

---------

Co-authored-by: Tig <tig@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-20 13:45:13 -05:00

622 lines
28 KiB
C#

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 = "TopLevel",
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 ();
}
}