diff --git a/.editorconfig b/.editorconfig index c8b73b2d84..ed21eef370 100644 --- a/.editorconfig +++ b/.editorconfig @@ -27,14 +27,24 @@ csharp_style_var_elsewhere = true:none # ReSharper properties resharper_align_linq_query = true +resharper_align_multiline_binary_patterns = true resharper_align_multiline_calls_chain = true resharper_align_multiline_extends_list = true resharper_align_multiline_parameter = true resharper_blank_lines_around_region = 1 resharper_braces_redundant = true +resharper_csharp_alignment_tab_fill_style = optimal_fill +resharper_csharp_max_line_length = 200 resharper_csharp_stick_comment = false +resharper_csharp_wrap_parameters_style = chop_if_long resharper_force_attribute_style = separate resharper_indent_type_constraints = true +#resharper_int_align_binary_expressions = true +resharper_int_align_comments = true +resharper_int_align_invocations = true +resharper_int_align_nested_ternary = true +resharper_int_align_switch_expressions = true +resharper_int_align_switch_sections = true resharper_local_function_body = expression_body resharper_remove_blank_lines_near_braces_in_declarations = true resharper_use_roslyn_logic_for_evident_types = true @@ -82,7 +92,19 @@ csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion csharp_style_pattern_matching_over_as_with_null_check = true:suggestion csharp_style_prefer_not_pattern = true:suggestion csharp_style_prefer_extended_property_pattern = true:suggestion -csharp_style_var_for_built_in_types = false:silent +csharp_style_var_for_built_in_types = true:none +resharper_wrap_before_linq_expression = true +resharper_wrap_chained_binary_expressions = chop_if_long +resharper_wrap_chained_binary_patterns = chop_if_long +resharper_xmldoc_indent_size = 2 +resharper_xmldoc_indent_style = space +resharper_xmldoc_indent_text = DoNotTouch +resharper_xmldoc_linebreaks_inside_tags_for_elements_longer_than = 120 +resharper_xmldoc_max_blank_lines_between_tags = 1 +resharper_xmldoc_max_line_length = 100 +resharper_xmldoc_space_before_self_closing = false +resharper_xmldoc_tab_width = 2 +resharper_xmldoc_use_indent_from_vs = true [*.{cs,vb}] dotnet_style_operator_placement_when_wrapping = beginning_of_line diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/ViewDrawing.cs index f622d80e37..94d8cad846 100644 --- a/Terminal.Gui/View/ViewDrawing.cs +++ b/Terminal.Gui/View/ViewDrawing.cs @@ -291,12 +291,12 @@ public void DrawHotString (string text, bool focused, ColorScheme scheme) /// the row to move to, in view-relative coordinates. public void Move (int col, int row) { - if (Driver.Rows == 0) { + if (Driver == null || Driver?.Rows == 0) { return; } BoundsToScreen (col, row, out int rCol, out int rRow, false); - Driver.Move (rCol, rRow); + Driver?.Move (rCol, rRow); } /// diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs index 0280f5b307..21fb8a2695 100644 --- a/Terminal.Gui/Views/Slider.cs +++ b/Terminal.Gui/Views/Slider.cs @@ -1,10 +1,7 @@ -using Microsoft.VisualBasic.FileIO; -using System; +using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Text; -using Unix.Terminal; namespace Terminal.Gui; @@ -13,48 +10,26 @@ namespace Terminal.Gui; /// public class SliderOptionEventArgs : EventArgs { /// - /// Gets whether the option is set or not. + /// Initializes a new instance of /// - public bool IsSet { get; } - + /// indicates whether the option is set + public SliderOptionEventArgs (bool isSet) => IsSet = isSet; /// - /// Initializes a new instance of + /// Gets whether the option is set or not. /// - /// indicates whether the option is set - public SliderOptionEventArgs (bool isSet) - { - IsSet = isSet; - } + public bool IsSet { get; } } /// /// Represents an option in a . /// -/// Datatype of the option. +/// Data type of the option. public class SliderOption { - /// - /// Legend of the option. - /// - public string Legend { get; set; } - - /// - /// Abbreviation of the Legend. When the too small to fit . - /// - public Rune LegendAbbr { get; set; } - - /// - /// Custom data of the option. - /// - public T Data { get; set; } - /// /// Creates a new empty instance of the class. /// - public SliderOption () - { - - } + public SliderOption () { } /// /// Creates a new instance of the class with values for @@ -67,29 +42,36 @@ public SliderOption (string legend, Rune legendAbbr, T data) Data = data; } + /// + /// Legend of the option. + /// + public string Legend { get; set; } + + /// + /// Abbreviation of the Legend. When the too small to fit + /// . + /// + public Rune LegendAbbr { get; set; } + + /// + /// Custom data of the option. + /// + public T Data { get; set; } + /// /// To Raise the event from the Slider. /// - internal void OnSet () - { - Set?.Invoke (this, new SliderOptionEventArgs (true)); - } + internal void OnSet () => Set?.Invoke (this, new SliderOptionEventArgs (true)); /// /// To Raise the event from the Slider. /// - internal void OnUnSet () - { - UnSet?.Invoke (this, new SliderOptionEventArgs (false)); - } + internal void OnUnSet () => UnSet?.Invoke (this, new SliderOptionEventArgs (false)); /// /// To Raise the event from the Slider. /// - internal void OnChanged (bool isSet) - { - Changed?.Invoke (this, new SliderOptionEventArgs (isSet)); - } + internal void OnChanged (bool isSet) => Changed?.Invoke (this, new SliderOptionEventArgs (isSet)); /// /// Event Raised when this option is set. @@ -109,8 +91,7 @@ internal void OnChanged (bool isSet) /// /// Creates a human-readable string that represents this . /// - public override string ToString () => "{Legend=" + Legend + ", LegendAbbr=" + LegendAbbr.ToString () + ", Data=" + Data?.ToString () + "}"; - + public override string ToString () => "{Legend=" + Legend + ", LegendAbbr=" + LegendAbbr + ", Data=" + Data + "}"; } /// @@ -118,31 +99,35 @@ internal void OnChanged (bool isSet) /// public enum SliderType { /// - /// + /// /// ├─┼─┼─┼─┼─█─┼─┼─┼─┼─┼─┼─┤ /// /// Single, + /// - /// + /// /// ├─┼─█─┼─┼─█─┼─┼─┼─┼─█─┼─┤ /// /// Multiple, + /// - /// + /// /// ├▒▒▒▒▒▒▒▒▒█─┼─┼─┼─┼─┼─┼─┤ /// /// LeftRange, + /// - /// + /// /// ├─┼─┼─┼─┼─█▒▒▒▒▒▒▒▒▒▒▒▒▒┤ /// /// RightRange, + /// - /// + /// /// ├─┼─┼─┼─┼─█▒▒▒▒▒▒▒█─┼─┼─┤ /// /// @@ -157,10 +142,12 @@ public class SliderAttributes { /// Attribute for when the respective Option is NOT Set. /// public Attribute? NormalAttribute { get; set; } + /// /// Attribute for when the respective Option is Set. /// public Attribute? SetAttribute { get; set; } + /// /// Attribute for the Legends Container. /// @@ -171,80 +158,95 @@ public class SliderAttributes { /// Style /// public class SliderStyle { + /// + /// Constructs a new instance. + /// + public SliderStyle () => LegendAttributes = new SliderAttributes (); + /// /// Legend attributes /// public SliderAttributes LegendAttributes { get; set; } + /// /// The glyph and the attribute used for empty spaces on the slider. /// public Cell EmptyChar { get; set; } + /// /// The glyph and the attribute used for each option (tick) on the slider. /// public Cell OptionChar { get; set; } + /// /// The glyph and the attribute used for options (ticks) that are set on the slider. /// public Cell SetChar { get; set; } + /// /// The glyph and the attribute to indicate mouse dragging. /// public Cell DragChar { get; set; } + /// /// The glyph and the attribute used for spaces between options (ticks) on the slider. /// public Cell SpaceChar { get; set; } + /// /// The glyph and the attribute used for filling in ranges on the slider. /// public Cell RangeChar { get; set; } + /// /// The glyph and the attribute used for the start of ranges on the slider. /// public Cell StartRangeChar { get; set; } + /// /// The glyph and the attribute used for the end of ranges on the slider. /// public Cell EndRangeChar { get; set; } - - /// - /// Constructs a new instance. - /// - public SliderStyle () - { - LegendAttributes = new SliderAttributes { }; - } } /// /// All configuration are grouped in this class. /// -internal class SliderConfiguration { - internal bool _rangeAllowSingle; +class SliderConfiguration { internal bool _allowEmpty; - internal int _mouseClickXOptionThreshold; - internal bool _autoSize; - - internal int _startSpacing; internal int _endSpacing; internal int _innerSpacing; + internal Orientation _legendsOrientation = Orientation.Horizontal; + internal bool _rangeAllowSingle; - internal bool _showSpacing; + internal bool _showEndSpacing; internal bool _showLegends; internal bool _showLegendsAbbr; + internal Orientation _sliderOrientation = Orientation.Horizontal; + + internal int _startSpacing; internal SliderType _type = SliderType.Single; - internal Orientation _sliderOrientation = Orientation.Horizontal; - internal Orientation _legendsOrientation = Orientation.Horizontal; } /// /// for events. /// public class SliderEventArgs : EventArgs { + /// + /// 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) + { + Options = options; + Focused = focused; + Cancel = false; + } + /// /// Gets/sets whether the option is set or not. /// @@ -259,24 +261,22 @@ public class SliderEventArgs : EventArgs { /// If set to true, the focus operation will be canceled, if applicable. /// public bool Cancel { get; set; } +} +/// +/// for events. +/// +public class OrientationEventArgs : EventArgs { /// - /// Initializes a new instance of + /// Constructs a new instance. /// - /// The current options. - /// Index of the option that is focused. -1 if no option has the focus. - public SliderEventArgs (Dictionary> options, int focused = -1) + /// the new orientation + public OrientationEventArgs (Orientation orientation) { - Options = options; - Focused = focused; + Orientation = orientation; Cancel = false; } -} -/// -/// for events. -/// -public class OrientationEventArgs : EventArgs { /// /// The new orientation. /// @@ -286,26 +286,16 @@ public class OrientationEventArgs : EventArgs { /// If set to true, the orientation change operation will be canceled, if applicable. /// public bool Cancel { get; set; } - - /// - /// Constructs a new instance. - /// - /// the new orientation - public OrientationEventArgs (Orientation orientation) - { - Orientation = orientation; - Cancel = false; - } } + /// /// Slider control. /// public class Slider : Slider { - /// /// Initializes a new instance of the class. /// - public Slider () : base () { } + public Slider () { } /// /// Initializes a new instance of the class. @@ -316,25 +306,57 @@ public Slider (List options, Orientation orientation = Orientation.Horiz } /// -/// Provides a slider control letting the user navigate from a set of typed options in a linear manner using the keyboard or mouse. +/// Provides a slider control letting the user navigate from a set of typed options in a linear manner +/// using the keyboard or mouse. /// /// public class Slider : View { - SliderConfiguration _config = new SliderConfiguration (); - SliderStyle _style = new SliderStyle (); + readonly SliderConfiguration _config = new (); + + // List of the current set options. + readonly List _setOptions = new (); // Options List> _options; - // List of the current set options. - List _setOptions = new List (); /// - /// The focused option (has the cursor). + /// The focused option (has the cursor). /// public int FocusedOption { get; set; } - #region Events + #region Initialize + void SetInitialProperties (List> options, Orientation orientation = Orientation.Horizontal) + { + CanFocus = true; + + _options = options ?? new List> (); + + _config._sliderOrientation = orientation; + + _config._showLegends = true; + + SetDefaultStyle (); + SetCommands (); + + // When we lose focus of the View(Slider), if we are range selecting we stop it. + Leave += (s, e) => { + //if (_settingRange == true) { + // _settingRange = false; + //} + Driver.SetCursorVisibility (CursorVisibility.Invisible); + }; + + + Enter += (s, e) => { }; + + LayoutComplete += (s, e) => { + CalcSpacingConfig (); + SetBoundsBestFit (); + }; + } + #endregion + #region Events /// /// Event raised when the slider option/s changed. /// The dictionary contains: key = option index, value = T @@ -342,7 +364,8 @@ public class Slider : View { public event EventHandler> OptionsChanged; /// - /// Overridable method called when the slider options have changed. Raises the event. + /// Overridable method called when the slider options have changed. Raises the + /// event. /// public virtual void OnOptionsChanged () { @@ -376,17 +399,13 @@ public virtual bool OnOptionFocused (int newFocusedOption, SliderEventArgs ar } return args.Cancel; } - #endregion #region Constructors - /// /// Initializes a new instance of the class. /// - public Slider () : this (new List ()) - { - } + public Slider () : this (new List ()) { } /// /// Initializes a new instance of the class. @@ -403,50 +422,14 @@ public Slider (List options, Orientation orientation = Orientation.Horizontal return new SliderOption { Data = e, Legend = legend, - LegendAbbr = (Rune)(legend?.Length > 0 ? legend [0] : ' '), + LegendAbbr = (Rune)(legend?.Length > 0 ? legend [0] : ' ') }; }).ToList (), orientation); } } - - #endregion - - #region Initialize - void SetInitialProperties (List> options, Orientation orientation = Orientation.Horizontal) - { - CanFocus = true; - - this._options = options ?? new List> (); - - _config._sliderOrientation = orientation; - - _config._showLegends = true; - - SetDefaultStyle (); - SetCommands (); - - // When we lose focus of the View(Slider), if we are range selecting we stop it. - Leave += (object s, FocusEventArgs e) => { - //if (_settingRange == true) { - // _settingRange = false; - //} - Driver.SetCursorVisibility (CursorVisibility.Invisible); - }; - - - Enter += (object s, FocusEventArgs e) => { - }; - - LayoutComplete += (s, e) => { - CalcSpacingConfig (); - AdjustBestHeight (); - AdjustBestWidth (); - }; - } #endregion #region Properties - /// /// Allow no selection. /// @@ -461,29 +444,38 @@ public bool AllowEmpty { } /// - /// If the slider will be sized to fit the available space (the Bounds of the the SuperView). + /// If the slider will be sized to fit the available space (the Bounds of the + /// the SuperView). /// /// - /// For testing, if there is no SuperView, the slider will be sized based on what is + /// For testing, if there is no SuperView, the slider will be sized based on what + /// is /// set to. /// public override bool AutoSize { get => _config._autoSize; - set => _config._autoSize = value; + set { + _config._autoSize = value; + if (IsInitialized) { + CalcSpacingConfig (); + SetBoundsBestFit (); + + } + } } /// /// Gets or sets the number of rows/columns between /// public int InnerSpacing { - get => _config._innerSpacing; set { _config._innerSpacing = value; - CalcSpacingConfig (); - Adjust (); - SetNeedsDisplay (); - SuperView?.SetNeedsLayout (); + if (IsInitialized) { + CalcSpacingConfig (); + SetBoundsBestFit (); + + } } } @@ -497,7 +489,6 @@ public SliderType Type { // Todo: Custom logic to preserve options. _setOptions.Clear (); - SetNeedsDisplay (); } } @@ -511,7 +502,8 @@ public Orientation Orientation { } /// - /// Fired when the slider orientation has changed. Can be cancelled by setting to true. + /// Fired when the slider orientation has changed. Can be cancelled by setting + /// to true. /// public event EventHandler OrientationChanged; @@ -529,9 +521,8 @@ public virtual bool OnOrientationChanged (Orientation newOrientation) SetKeyBindings (); if (IsInitialized) { CalcSpacingConfig (); - Adjust (); - SetNeedsDisplay (); - SuperView?.SetNeedsLayout (); + SetBoundsBestFit (); + } } return args.Cancel; @@ -544,10 +535,11 @@ public Orientation LegendsOrientation { get => _config._legendsOrientation; set { _config._legendsOrientation = value; - CalcSpacingConfig (); - Adjust (); - SetNeedsDisplay (); - SuperView?.SetNeedsLayout (); + if (IsInitialized) { + CalcSpacingConfig (); + SetBoundsBestFit (); + + } } } @@ -555,38 +547,31 @@ public Orientation LegendsOrientation { /// Slider styles. /// public SliderStyle Style { - get { - // Note(jmperricone): Maybe SliderStyle should be a struct so we return a copy ??? - // Or SetStyle() and ( GetStyle() || Style getter copy ) - return _style; - } - set { - // Note(jmperricone): If the user change a style, he/she must call SetNeedsDisplay(). OK ??? - _style = value; - } - } + // Note(jmperricone): Maybe SliderStyle should be a struct so we return a copy ??? + // Or SetStyle() and ( GetStyle() || Style getter copy ) + get; + set; + } = new (); /// /// Set the slider options. /// public List> Options { - get { + get => // Note(jmperricone): Maybe SliderOption should be a struct so we return a copy ??? // Events will be preserved ? Need a test. // Or SetOptions() and ( GetOptions() || Options getter copy ) - return _options; - } + _options; set { - _options = value; + // _options should never be null + _options = value ?? throw new ArgumentNullException (nameof (value)); - if (_options == null || _options.Count == 0) { + if (!IsInitialized || _options.Count == 0) { return; } CalcSpacingConfig (); - Adjust (); - SetNeedsDisplay (); - SuperView?.SetNeedsLayout (); + SetBoundsBestFit (); } } @@ -595,18 +580,16 @@ public List> Options { /// public bool RangeAllowSingle { get => _config._rangeAllowSingle; - set { - _config._rangeAllowSingle = value; - } + set => _config._rangeAllowSingle = value; } /// /// Show/Hide spacing before and after the first and last option. /// - public bool ShowSpacing { - get => _config._showSpacing; + public bool ShowEndSpacing { + get => _config._showEndSpacing; set { - _config._showSpacing = value; + _config._showEndSpacing = value; SetNeedsDisplay (); } } @@ -618,7 +601,8 @@ public bool ShowLegends { get => _config._showLegends; set { _config._showLegends = value; - Adjust (); + SetBoundsBestFit (); + } } @@ -644,7 +628,7 @@ public bool SetOption (int optionIndex) public bool UnSetOption (int optionIndex) { // TODO: Handle range type. - if ((!AllowEmpty && _setOptions.Count > 2) && _setOptions.Contains (optionIndex)) { + if (!AllowEmpty && _setOptions.Count > 2 && _setOptions.Contains (optionIndex)) { FocusedOption = optionIndex; SetFocusedOption (); return true; @@ -655,12 +639,9 @@ public bool UnSetOption (int optionIndex) /// /// Get the indexes of the set options. /// - public List GetSetOptions () - { + public List GetSetOptions () => // Copy - return _setOptions.OrderBy (e => e).ToList (); - } - + _setOptions.OrderBy (e => e).ToList (); #endregion #region Helpers @@ -681,12 +662,12 @@ void SetDefaultStyle () { switch (_config._sliderOrientation) { case Orientation.Horizontal: - _style.SpaceChar = new Cell () { Rune = CM.Glyphs.HLine }; // '─' - _style.OptionChar = new Cell () { Rune = CM.Glyphs.BlackCircle }; // '┼●🗹□⏹' + Style.SpaceChar = new Cell { Rune = Glyphs.HLine }; // '─' + Style.OptionChar = new Cell { Rune = Glyphs.BlackCircle }; // '┼●🗹□⏹' break; case Orientation.Vertical: - _style.SpaceChar = new Cell () { Rune = CM.Glyphs.VLine }; - _style.OptionChar = new Cell () { Rune = CM.Glyphs.BlackCircle }; + Style.SpaceChar = new Cell { Rune = Glyphs.VLine }; + Style.OptionChar = new Cell { Rune = Glyphs.BlackCircle }; break; } @@ -710,12 +691,12 @@ void SetDefaultStyle () */ _config._legendsOrientation = _config._sliderOrientation; - _style.EmptyChar = new Cell () { Rune = new Rune (' ') }; - _style.SetChar = new Cell () { Rune = CM.Glyphs.ContinuousMeterSegment }; // ■ - _style.RangeChar = new Cell () { Rune = CM.Glyphs.Stipple }; // ░ ▒ ▓ // Medium shade not blinking on curses. - _style.StartRangeChar = new Cell () { Rune = CM.Glyphs.ContinuousMeterSegment }; - _style.EndRangeChar = new Cell () { Rune = CM.Glyphs.ContinuousMeterSegment }; - _style.DragChar = new Cell () { Rune = CM.Glyphs.Diamond }; + Style.EmptyChar = new Cell { Rune = new Rune (' ') }; + Style.SetChar = new Cell { Rune = Glyphs.ContinuousMeterSegment }; // ■ + Style.RangeChar = new Cell { Rune = Glyphs.Stipple }; // ░ ▒ ▓ // Medium shade not blinking on curses. + Style.StartRangeChar = new Cell { Rune = Glyphs.ContinuousMeterSegment }; + Style.EndRangeChar = new Cell { Rune = Glyphs.ContinuousMeterSegment }; + Style.DragChar = new Cell { Rune = Glyphs.Diamond }; // TODO: Support left & right (top/bottom) // First = '├', @@ -723,29 +704,39 @@ void SetDefaultStyle () } /// - /// Calculates the spacing configuration (start, inner, end) as well as turning on/off legend abbreviation - /// if needed. Behaves differently based on and . + /// Calculates the spacing configuration (start, inner, end) as well as turning on/off legend + /// abbreviation if needed. Behaves differently based on and + /// . /// internal void CalcSpacingConfig () { - int size = 0; + var size = 0; - if (_options.Count == 0) { + if (_options.Count == 0 || !IsInitialized) { return; } - if (_config._autoSize || !IsInitialized) { - if (IsInitialized && SuperView != null) { + _config._innerSpacing = 0; + _config._startSpacing = 0; + _config._endSpacing = 0; + + if (AutoSize) { + // Max size is SuperView's Bounds. Min Size is size that will fit. + if (SuperView != null) { // Calculate the size of the slider based on the size of the SuperView's Bounds. - // TODO: + if (_config._sliderOrientation == Orientation.Horizontal) { + size = int.Min (SuperView.Bounds.Width, CalcBestLength ()); + } else { + size = int.Min (SuperView.Bounds.Height, CalcBestLength ()); + } } else { // Use the config values - size = CalcLength (); + size = CalcMinLength (); return; } } else { - // Fit Slider to the actual width and height. + // Fit Slider to the Bounds if (_config._sliderOrientation == Orientation.Horizontal) { size = Bounds.Width; } else { @@ -753,21 +744,24 @@ internal void CalcSpacingConfig () } } - int max_legend; + int max_legend; // Because the legends are centered, the longest one determines inner spacing if (_config._sliderOrientation == _config._legendsOrientation) { - max_legend = _options.Max (e => e.Legend.ToString ().Length); + max_legend = int.Max (_options.Max (s => s.Legend?.Length ?? 1), 1); } else { max_legend = 1; } - var min = (size - max_legend) / (_options.Count - 1); + var min_size_that_fits_legends = _options.Count == 1 ? max_legend : max_legend / (_options.Count - 1); string first; string last; - if (max_legend >= min) { + if (max_legend >= size) { if (_config._sliderOrientation == _config._legendsOrientation) { _config._showLegendsAbbr = true; + foreach (var o in _options.Where (op => op.LegendAbbr == default)) { + o.LegendAbbr = (Rune)(o.Legend?.Length > 0 ? o.Legend [0] : ' '); + } } first = "x"; last = "x"; @@ -782,85 +776,104 @@ internal void CalcSpacingConfig () // Left = He // Right = lo var first_left = (first.Length - 1) / 2; // Chars count of the first option to the left. - var last_right = (last.Length) / 2; // Chars count of the last option to the right. + var last_right = last.Length / 2; // Chars count of the last option to the right. if (_config._sliderOrientation != _config._legendsOrientation) { first_left = 0; last_right = 0; } + // -1 because it's better to have an extra space at right than to clip var width = size - first_left - last_right - 1; _config._startSpacing = first_left; - _config._innerSpacing = Math.Max (0, (int)Math.Floor ((double)width / (_options.Count - 1)) - 1); + if (_options.Count == 1) { + _config._innerSpacing = max_legend; + } else { + _config._innerSpacing = Math.Max (0, (int)Math.Floor ((double)width / (_options.Count - 1)) - 1); + } _config._endSpacing = last_right; } /// - /// Adjust the height of the Slider to the best value. - /// - public void AdjustBestHeight () + /// Adjust the dimensions of the Slider to the best value if is true. + /// + public void SetBoundsBestFit () { + if (!IsInitialized || AutoSize == false) { + return; + } // Hack??? Otherwise we can't go back to Dim.Absolute. LayoutStyle = LayoutStyle.Absolute; - + Width = 0; + Height = 0; if (_config._sliderOrientation == Orientation.Horizontal) { - Bounds = new Rect (Bounds.Location, new Size (Bounds.Width, CalcThickness ())); + Bounds = new Rect (Bounds.Location, + new Size (int.Min (SuperView.Bounds.Width - GetFramesThickness ().Horizontal, CalcBestLength ()), + int.Min (SuperView.Bounds.Height - GetFramesThickness ().Vertical, CalcThickness ()))); } else { - Bounds = new Rect (Bounds.Location, new Size (Bounds.Width, CalcLength ())); + Bounds = new Rect (Bounds.Location, + new Size (int.Min (SuperView.Bounds.Width - GetFramesThickness ().Horizontal, CalcThickness ()), + int.Min (SuperView.Bounds.Height - GetFramesThickness ().Vertical, CalcBestLength ()))); } - LayoutStyle = LayoutStyle.Computed; } /// - /// Adjust the height of the Slider to the best value. (Only works if height is DimAbsolute). - /// - public void AdjustBestWidth () - { - LayoutStyle = LayoutStyle.Absolute; - - if (_config._sliderOrientation == Orientation.Horizontal) { - Bounds = new Rect (Bounds.Location, new Size (CalcLength (), Bounds.Height)); - } else { - Bounds = new Rect (Bounds.Location, new Size (CalcThickness (), Bounds.Height)); - } - - LayoutStyle = LayoutStyle.Computed; - } - - void Adjust () + /// Calculates the min dimension required for all options and inner spacing with abbreviated legends + /// + /// + int CalcMinLength () { - if (Width is Dim.DimAbsolute) { - AdjustBestWidth (); + if (_options.Count == 0) { + return 0; } - if (Height is Dim.DimAbsolute) { - AdjustBestHeight (); - } + var length = 0; + length += _config._startSpacing + _config._endSpacing; + length += _options.Count; + length += (_options.Count - 1) * _config._innerSpacing; + return length; } - internal int CalcLength () + /// + /// Calculates the ideal dimension required for all options, inner spacing, and legends + /// (non-abbreviated). + /// + /// + int CalcBestLength () { if (_options.Count == 0) { return 0; } var length = 0; - length += _config._startSpacing + _config._endSpacing; - length += _options.Count; - length += (_options.Count - 1) * _config._innerSpacing; - return length; + + if (_config._showLegends) { + var max_legend = 1; + if (_config._legendsOrientation == _config._sliderOrientation && _options.Count > 0) { + max_legend = int.Max (_options.Max (s => s.Legend?.Length + 1 ?? 1), 1); + length += max_legend * _options.Count; + //length += (max_legend / 2); + } else { + length += 1; + } + } + return int.Max (length, CalcMinLength ()); } + /// + /// Calculates the min dimension required for the slider and legends + /// + /// int CalcThickness () { var thickness = 1; // Always show the slider. if (_config._showLegends) { if (_config._legendsOrientation != _config._sliderOrientation && _options.Count > 0) { - thickness += _options.Max (s => s.Legend.Length); + thickness += _options.Max (s => s.Legend?.Length ?? 0); } else { thickness += 1; } @@ -907,7 +920,7 @@ internal bool TryGetOptionByPosition (int x, int y, int threshold, out int optio return false; } - for (int xx = (x - threshold); xx < (x + threshold + 1); xx++) { + for (var xx = x - threshold; xx < x + threshold + 1; xx++) { var cx = xx; cx -= _config._startSpacing; @@ -927,7 +940,7 @@ internal bool TryGetOptionByPosition (int x, int y, int threshold, out int optio return false; } - for (int yy = (y - threshold); yy < (y + threshold + 1); yy++) { + for (var yy = y - threshold; yy < y + threshold + 1; yy++) { var cy = yy; cy -= _config._startSpacing; @@ -945,11 +958,9 @@ internal bool TryGetOptionByPosition (int x, int y, int threshold, out int optio return false; } - #endregion #region Cursor and Drawing - /// public override void PositionCursor () { @@ -960,7 +971,7 @@ public override void PositionCursor () } else { Driver?.SetCursorVisibility (CursorVisibility.Invisible); } - if (TryGetPositionByOption (FocusedOption, out (int x, int y) position)) { + if (TryGetPositionByOption (FocusedOption, out var position)) { if (IsInitialized && Bounds.Contains (position.x, position.y)) { Move (position.x, position.y); } @@ -972,17 +983,15 @@ public override void OnDrawContent (Rect contentArea) { // TODO: make this more surgical to reduce repaint - var normalScheme = ColorScheme?.Normal ?? Application.Current.ColorScheme.Normal; - - if (this._options == null && this._options.Count > 0) { + if (_options == null && _options.Count > 0) { return; } // Debug #if (DEBUG) - Driver.SetAttribute (new Attribute (Color.White, Color.Red)); - for (var y = 0; y < Bounds.Height; y++) { - for (var x = 0; x < Bounds.Width; x++) { + Driver?.SetAttribute (new Attribute (Color.White, Color.Red)); + for (var y = 0; y < contentArea.Height; y++) { + for (var x = 0; x < contentArea.Width; x++) { // MoveAndAdd (x, y, '·'); } } @@ -997,7 +1006,7 @@ public override void OnDrawContent (Rect contentArea) } if (_dragPosition.HasValue && _moveRenderPosition.HasValue) { - AddRune (_moveRenderPosition.Value.X, _moveRenderPosition.Value.Y, _style.DragChar.Rune); + AddRune (_moveRenderPosition.Value.X, _moveRenderPosition.Value.Y, Style.DragChar.Rune); } } @@ -1008,26 +1017,24 @@ string AlignText (string text, int width, TextAlignment textAlignment) } if (text.Length > width) { - text = text [0..width]; + text = text [..width]; } var w = width - text.Length; - var s1 = new string (' ', w / 2); - var s2 = new string (' ', w % 2); + string s1 = new (' ', w / 2); + string s2 = new (' ', w % 2); // Note: The formatter doesn't handle all of this ??? switch (textAlignment) { case TextAlignment.Justified: - return TextFormatter.Justify (text, width); case TextAlignment.Left: return text + s1 + s1 + s2; case TextAlignment.Centered: if (text.Length % 2 != 0) { return s1 + text + s1 + s2; - } else { - return s1 + s2 + text + s1; } + return s1 + s2 + text + s1; case TextAlignment.Right: return s1 + s1 + s2 + text; default: @@ -1041,9 +1048,13 @@ void DrawSlider () Clear (); // Attributes - var normalScheme = ColorScheme?.Normal ?? Application.Current.ColorScheme.Normal; - //var normalScheme = style.LegendStyle.NormalAttribute ?? ColorScheme.Disabled; - var setScheme = _style.SetChar.Attribute ?? ColorScheme.HotNormal;// ColorScheme?.Focus ?? Application.Current.ColorScheme.Focus; + + var normalAttr = new Attribute (Color.White, Color.Black); + var setAtrr = new Attribute (Color.Black, Color.White); + if (IsInitialized) { + normalAttr = ColorScheme?.Normal ?? Application.Current.ColorScheme.Normal; + setAtrr = Style.SetChar.Attribute ?? ColorScheme.HotNormal; + } var isVertical = _config._sliderOrientation == Orientation.Vertical; var isLegendsVertical = _config._legendsOrientation == Orientation.Vertical; @@ -1055,21 +1066,28 @@ void DrawSlider () var isSet = _setOptions.Count > 0; // Left Spacing - if (_config._showSpacing && _config._startSpacing > 0) { + if (_config._showEndSpacing && _config._startSpacing > 0) { - Driver.SetAttribute (isSet && _config._type == SliderType.LeftRange ? _style.RangeChar.Attribute ?? normalScheme : _style.SpaceChar.Attribute ?? normalScheme); - var rune = isSet && _config._type == SliderType.LeftRange ? _style.RangeChar.Rune : _style.SpaceChar.Rune; + Driver?.SetAttribute (isSet && _config._type == SliderType.LeftRange ? Style.RangeChar.Attribute ?? normalAttr : Style.SpaceChar.Attribute ?? normalAttr); + var rune = isSet && _config._type == SliderType.LeftRange ? Style.RangeChar.Rune : Style.SpaceChar.Rune; - for (var i = 0; i < this._config._startSpacing; i++) { + for (var i = 0; i < _config._startSpacing; i++) { MoveAndAdd (x, y, rune); - if (isVertical) y++; else x++; + if (isVertical) { + y++; + } else { + x++; + } } } else { - Driver.SetAttribute (_style.EmptyChar.Attribute ?? normalScheme); - // for (int i = 0; i < this.config.StartSpacing + ((this.config.StartSpacing + this.config.EndSpacing) % 2 == 0 ? 1 : 2); i++) { - for (var i = 0; i < this._config._startSpacing; i++) { - MoveAndAdd (x, y, _style.EmptyChar.Rune); - if (isVertical) y++; else x++; + Driver?.SetAttribute (Style.EmptyChar.Attribute ?? normalAttr); + for (var i = 0; i < _config._startSpacing; i++) { + MoveAndAdd (x, y, Style.EmptyChar.Rune); + if (isVertical) { + y++; + } else { + x++; + } } } @@ -1091,19 +1109,17 @@ void DrawSlider () drawRange = false; break; case SliderType.Range when _setOptions.Count == 2: - if ((i >= _setOptions [0] && i <= _setOptions [1]) || (i >= _setOptions [1] && i <= _setOptions [0])) { - drawRange = (i >= _setOptions [0] && i < _setOptions [1]) || (i >= _setOptions [1] && i < _setOptions [0]); + if (i >= _setOptions [0] && i <= _setOptions [1] || i >= _setOptions [1] && i <= _setOptions [0]) { + drawRange = i >= _setOptions [0] && i < _setOptions [1] || i >= _setOptions [1] && i < _setOptions [0]; } break; - default: - // Is Not a Range. - break; } } // Draw Option - Driver.SetAttribute (isSet && _setOptions.Contains (i) ? _style.SetChar.Attribute ?? setScheme : drawRange ? _style.RangeChar.Attribute ?? setScheme : _style.OptionChar.Attribute ?? normalScheme); + Driver?.SetAttribute (isSet && _setOptions.Contains (i) ? Style.SetChar.Attribute ?? setAtrr : drawRange + ? Style.RangeChar.Attribute ?? setAtrr : Style.OptionChar.Attribute ?? normalAttr); // Note(jmperricone): Maybe only for curses, windows inverts actual colors, while curses inverts bg with fg. //if (Application.Driver is CursesDriver) { @@ -1111,25 +1127,34 @@ void DrawSlider () // Driver.SetAttribute (ColorScheme.Focus); // } //} - Rune rune = drawRange ? _style.RangeChar.Rune : _style.OptionChar.Rune; + var rune = drawRange ? Style.RangeChar.Rune : Style.OptionChar.Rune; if (isSet) { if (_setOptions [0] == i) { - rune = _style.StartRangeChar.Rune; + rune = Style.StartRangeChar.Rune; } else if (_setOptions.Count > 1 && _setOptions [1] == i) { - rune = _style.EndRangeChar.Rune; + rune = Style.EndRangeChar.Rune; } else if (_setOptions.Contains (i)) { - rune = _style.SetChar.Rune; + rune = Style.SetChar.Rune; } } MoveAndAdd (x, y, rune); - if (isVertical) y++; else x++; + if (isVertical) { + y++; + } else { + x++; + } // Draw Spacing - if (_config._showSpacing || i < _options.Count - 1) { // Skip if is the Last Spacing. - Driver.SetAttribute (drawRange && isSet ? _style.RangeChar.Attribute ?? setScheme : _style.SpaceChar.Attribute ?? normalScheme); + if (_config._showEndSpacing || i < _options.Count - 1) { + // Skip if is the Last Spacing. + Driver?.SetAttribute (drawRange && isSet ? Style.RangeChar.Attribute ?? setAtrr : Style.SpaceChar.Attribute ?? normalAttr); for (var s = 0; s < _config._innerSpacing; s++) { - MoveAndAdd (x, y, drawRange && isSet ? _style.RangeChar.Rune : _style.SpaceChar.Rune); - if (isVertical) y++; else x++; + MoveAndAdd (x, y, drawRange && isSet ? Style.RangeChar.Rune : Style.SpaceChar.Rune); + if (isVertical) { + y++; + } else { + x++; + } } } } @@ -1137,18 +1162,26 @@ void DrawSlider () var remaining = isVertical ? Bounds.Height - y : Bounds.Width - x; // Right Spacing - if (_config._showSpacing) { - Driver.SetAttribute (isSet && _config._type == SliderType.RightRange ? _style.RangeChar.Attribute ?? normalScheme : _style.SpaceChar.Attribute ?? normalScheme); - var rune = isSet && _config._type == SliderType.RightRange ? _style.RangeChar.Rune : _style.SpaceChar.Rune; + if (_config._showEndSpacing) { + Driver?.SetAttribute (isSet && _config._type == SliderType.RightRange ? Style.RangeChar.Attribute ?? normalAttr : Style.SpaceChar.Attribute ?? normalAttr); + var rune = isSet && _config._type == SliderType.RightRange ? Style.RangeChar.Rune : Style.SpaceChar.Rune; for (var i = 0; i < remaining; i++) { MoveAndAdd (x, y, rune); - if (isVertical) y++; else x++; + if (isVertical) { + y++; + } else { + x++; + } } } else { - Driver.SetAttribute (_style.EmptyChar.Attribute ?? normalScheme); + Driver?.SetAttribute (Style.EmptyChar.Attribute ?? normalAttr); for (var i = 0; i < remaining; i++) { - MoveAndAdd (x, y, _style.EmptyChar.Rune); - if (isVertical) y++; else x++; + MoveAndAdd (x, y, Style.EmptyChar.Rune); + if (isVertical) { + y++; + } else { + x++; + } } } } @@ -1156,9 +1189,14 @@ void DrawSlider () void DrawLegends () { // Attributes - var normalScheme = _style.LegendAttributes.NormalAttribute ?? ColorScheme?.Normal ?? ColorScheme.Disabled; - var setScheme = _style.LegendAttributes.SetAttribute ?? ColorScheme?.HotNormal ?? ColorScheme.Normal; - var spaceScheme = normalScheme;// style.LegendStyle.EmptyAttribute ?? normalScheme; + var normalAttr = new Attribute (Color.White, Color.Black); + var setAttr = new Attribute (Color.Black, Color.White); + var spaceAttr = normalAttr; + if (IsInitialized) { + normalAttr = Style.LegendAttributes.NormalAttribute ?? ColorScheme?.Normal ?? ColorScheme.Disabled; + setAttr = Style.LegendAttributes.SetAttribute ?? ColorScheme?.HotNormal ?? ColorScheme.Normal; + spaceAttr = Style.LegendAttributes.EmptyAttribute ?? normalAttr; + } var isTextVertical = _config._legendsOrientation == Orientation.Vertical; var isSet = _setOptions.Count > 0; @@ -1177,42 +1215,47 @@ void DrawLegends () if (_config._sliderOrientation == Orientation.Horizontal) { y += 1; - } else { // Vertical + } else { + // Vertical x += 1; } - for (int i = 0; i < _options.Count; i++) { + for (var i = 0; i < _options.Count; i++) { - bool isOptionSet = false; + var isOptionSet = false; // Check if the Option is Set. switch (_config._type) { case SliderType.Single: case SliderType.Multiple: - if (isSet && _setOptions.Contains (i)) + if (isSet && _setOptions.Contains (i)) { isOptionSet = true; + } break; case SliderType.LeftRange: - if (isSet && i <= _setOptions [0]) + if (isSet && i <= _setOptions [0]) { isOptionSet = true; + } break; case SliderType.RightRange: - if (isSet && i >= _setOptions [0]) + if (isSet && i >= _setOptions [0]) { isOptionSet = true; + } break; case SliderType.Range when _setOptions.Count == 1: - if (isSet && i == _setOptions [0]) + if (isSet && i == _setOptions [0]) { isOptionSet = true; + } break; case SliderType.Range: - if (isSet && ((i >= _setOptions [0] && i <= _setOptions [1]) || (i >= _setOptions [1] && i <= _setOptions [0]))) { + if (isSet && (i >= _setOptions [0] && i <= _setOptions [1] || i >= _setOptions [1] && i <= _setOptions [0])) { isOptionSet = true; } break; } // Text || Abbreviation - string text = string.Empty; + var text = string.Empty; if (_config._showLegendsAbbr) { text = _options [i].LegendAbbr.ToString () ?? new Rune (_options [i].Legend.First ()).ToString (); } else { @@ -1266,18 +1309,24 @@ void DrawLegends () } // Option Left Spacing - if (isTextVertical) y += legend_left_spaces_count; - else x += legend_left_spaces_count; + if (isTextVertical) { + y += legend_left_spaces_count; + } else { + x += legend_left_spaces_count; + } //Move (x, y); } // Legend - Driver.SetAttribute (isOptionSet ? setScheme : normalScheme); + Driver?.SetAttribute (isOptionSet ? setAttr : normalAttr); foreach (var c in text.EnumerateRunes ()) { MoveAndAdd (x, y, c); //Driver.AddRune (c); - if (isTextVertical) y += 1; - else x += 1; + if (isTextVertical) { + y += 1; + } else { + x += 1; + } } // Calculate End Spacing @@ -1288,9 +1337,12 @@ void DrawLegends () } // Option Right Spacing of Option - Driver.SetAttribute (spaceScheme); - if (isTextVertical) y += legend_right_spaces_count; - else x += legend_right_spaces_count; + Driver?.SetAttribute (spaceAttr); + if (isTextVertical) { + y += legend_right_spaces_count; + } else { + x += legend_right_spaces_count; + } if (_config._sliderOrientation == Orientation.Horizontal && _config._legendsOrientation == Orientation.Vertical) { x += _config._innerSpacing + 1; @@ -1299,13 +1351,12 @@ void DrawLegends () } } } - #endregion #region Keys and Mouse - // Mouse coordinates of current drag Point? _dragPosition; + // Coordinates of where the "move cursor" is drawn (in OnDrawContent) Point? _moveRenderPosition; @@ -1319,9 +1370,9 @@ public override bool MouseEvent (MouseEvent mouseEvent) // TODO(jmperricone): Make Range Type work with mouse. if (!(mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked) || - mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) || - mouseEvent.Flags.HasFlag (MouseFlags.ReportMousePosition) || - mouseEvent.Flags.HasFlag (MouseFlags.Button1Released))) { + mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) || + mouseEvent.Flags.HasFlag (MouseFlags.ReportMousePosition) || + mouseEvent.Flags.HasFlag (MouseFlags.Button1Released))) { return false; } @@ -1332,13 +1383,13 @@ Point ClampMovePosition (Point position) if (Orientation == Orientation.Horizontal) { var left = _config._startSpacing; var width = _options.Count + (_options.Count - 1) * _config._innerSpacing; - var right = (left + width - 1); + var right = left + width - 1; var clampedX = Clamp (position.X, left, right); position = new Point (clampedX, 0); } else { var top = _config._startSpacing; var height = _options.Count + (_options.Count - 1) * _config._innerSpacing; - var bottom = (top + height - 1); + var bottom = top + height - 1; var clampedY = Clamp (position.Y, top, bottom); position = new Point (0, clampedY); } @@ -1347,7 +1398,7 @@ Point ClampMovePosition (Point position) SetFocus (); - if (!_dragPosition.HasValue && (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))) { + if (!_dragPosition.HasValue && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)) { if (mouseEvent.Flags.HasFlag (MouseFlags.ReportMousePosition)) { _dragPosition = new Point (mouseEvent.X, mouseEvent.Y); @@ -1382,7 +1433,7 @@ Point ClampMovePosition (Point position) 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 Application.UngrabMouse (); @@ -1411,15 +1462,15 @@ Point ClampMovePosition (Point position) void SetCommands () { - AddCommand (Command.Right, () => MovePlus ()); - AddCommand (Command.LineDown, () => MovePlus ()); - AddCommand (Command.Left, () => MoveMinus ()); - AddCommand (Command.LineUp, () => MoveMinus ()); - AddCommand (Command.LeftHome, () => MoveStart ()); - AddCommand (Command.RightEnd, () => MoveEnd ()); + AddCommand (Command.Right, () => MovePlus ()); + AddCommand (Command.LineDown, () => MovePlus ()); + AddCommand (Command.Left, () => MoveMinus ()); + AddCommand (Command.LineUp, () => MoveMinus ()); + AddCommand (Command.LeftHome, () => MoveStart ()); + AddCommand (Command.RightEnd, () => MoveEnd ()); AddCommand (Command.RightExtend, () => ExtendPlus ()); - AddCommand (Command.LeftExtend, () => ExtendMinus ()); - AddCommand (Command.Accept, () => Set ()); + AddCommand (Command.LeftExtend, () => ExtendMinus ()); + AddCommand (Command.Accept, () => Set ()); SetKeyBindings (); } @@ -1449,8 +1500,8 @@ void SetKeyBindings () KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.LeftExtend); } - KeyBindings.Add (KeyCode.Home, Command.LeftHome); - KeyBindings.Add (KeyCode.End, Command.RightEnd); + KeyBindings.Add (KeyCode.Home, Command.LeftHome); + KeyBindings.Add (KeyCode.End, Command.RightEnd); KeyBindings.Add (KeyCode.Enter, Command.Accept); KeyBindings.Add (KeyCode.Space, Command.Accept); @@ -1577,7 +1628,7 @@ void SetFocusedOption () // extend right _options [_setOptions [1]].OnUnSet (); _setOptions [1] = FocusedOption; - } else if (FocusedOption >= _setOptions [0] && FocusedOption <= _setOptions [1] && (_setOptions [1] - _setOptions [0] > 1)) { + } else if (FocusedOption >= _setOptions [0] && FocusedOption <= _setOptions [1] && _setOptions [1] - _setOptions [0] > 1) { if (FocusedOption < _lastFocusedOption) { // shrink to the left _options [_setOptions [1]].OnUnSet (); @@ -1721,4 +1772,4 @@ internal bool MoveEnd () return true; } #endregion -} +} \ No newline at end of file diff --git a/Terminal.sln b/Terminal.sln index 4d1712f8e4..fcc42be940 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -14,6 +14,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example", "Example\Example. EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E143FB1F-0B88-48CB-9086-72CDCECFCD22}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig .gitignore = .gitignore .github\workflows\api-docs.yml = .github\workflows\api-docs.yml .github\CODEOWNERS = .github\CODEOWNERS diff --git a/UICatalog/Scenarios/Sliders.cs b/UICatalog/Scenarios/Sliders.cs index 7d4ff03a5c..5da9497115 100644 --- a/UICatalog/Scenarios/Sliders.cs +++ b/UICatalog/Scenarios/Sliders.cs @@ -6,15 +6,12 @@ namespace UICatalog.Scenarios; -[ScenarioMetadata (Name: "Sliders", Description: "Demonstrates the Slider view.")] +[ScenarioMetadata ("Sliders", "Demonstrates the Slider view.")] [ScenarioCategory ("Controls")] public class Sliders : Scenario { public override void Setup () { MakeSliders (Win, new List { 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000 }); - - #region configView - var configView = new FrameView { Title = "Configuration", X = Pos.Percent (50), @@ -27,88 +24,70 @@ public override void Setup () Win.Add (configView); #region Config Slider - - var slider = new Slider () { + var slider = new Slider { Title = "Options", - X = Pos.Center (), + X = 0, Y = 0, Type = SliderType.Multiple, Width = Dim.Fill (), + Height = 4, AllowEmpty = true, BorderStyle = LineStyle.Single }; - slider.Style.SetChar.Attribute = new Terminal.Gui.Attribute (Color.BrightGreen, Color.Black); - slider.Style.LegendAttributes.SetAttribute = new Terminal.Gui.Attribute (Color.Green, Color.Black); + slider.Style.SetChar.Attribute = new Attribute (Color.BrightGreen, Color.Black); + slider.Style.LegendAttributes.SetAttribute = new Attribute (Color.Green, Color.Black); slider.Options = new List> { - new SliderOption{ - Legend="Legends" - }, - new SliderOption{ - Legend="RangeAllowSingle" - }, - new SliderOption{ - Legend="Spacing" - } - }; + new () { + Legend = "Legends" + }, + new () { + Legend = "RangeAllowSingle" + }, + new () { + Legend = "EndSpacing" + }, + new () { + Legend = "AutoSize" + } + }; configView.Add (slider); slider.OptionsChanged += (sender, e) => { foreach (var s in Win.Subviews.OfType ()) { - if (e.Options.ContainsKey (0)) - s.ShowLegends = true; - else - s.ShowLegends = false; - - if (e.Options.ContainsKey (1)) - s.RangeAllowSingle = true; - else - s.RangeAllowSingle = false; - - if (e.Options.ContainsKey (2)) - s.ShowSpacing = true; - else - s.ShowSpacing = false; + s.ShowLegends = e.Options.ContainsKey (0); + s.RangeAllowSingle = e.Options.ContainsKey (1); + s.ShowEndSpacing = e.Options.ContainsKey (2); + s.AutoSize = e.Options.ContainsKey (3); + if (!s.AutoSize) { + if (s.Orientation == Orientation.Horizontal) { + s.Width = Dim.Percent (50); + var h = s.ShowLegends && s.LegendsOrientation == Orientation.Vertical ? s.Options.Max (o => o.Legend.Length) + 3 : 4; + s.Height = h; + } else { + var w = s.ShowLegends ? s.Options.Max (o => o.Legend.Length) + 3 : 3; + s.Width = w; + s.Height = Dim.Fill (); + } + } } if (Win.IsInitialized) { Win.LayoutSubviews (); } }; - slider.SetOption (0); - slider.SetOption (1); - - #endregion - - #region InnerSpacing Input - // var innerspacing_slider = new Slider ("Innerspacing", new List { "Auto", "0", "1", "2", "3", "4", "5" }) { - // X = Pos.Center (), - // Y = Pos.Bottom (slider) + 1 - // }; - - // innerspacing_slider.SetOption (0); - - // configView.Add (innerspacing_slider); - - // innerspacing_slider.OptionsChanged += (options) => { - // foreach (var s in leftView.Subviews.OfType () ()) { - // if (options.ContainsKey (0)) { } - // //s.la = S.SliderLayout.Auto; - // else { - // s.InnerSpacing = options.Keys.First () - 1; - // } - // } - // }; - #endregion + slider.SetOption (0); // Legends + slider.SetOption (1); // RangeAllowSingle + //slider.SetOption (3); // AutoSize #region Slider Orientation Slider - var slider_orientation_slider = new Slider (new List { "Horizontal", "Vertical" }) { Title = "Slider Orientation", X = 0, Y = Pos.Bottom (slider) + 1, Width = Dim.Fill (), + Height = 4, BorderStyle = LineStyle.Single }; @@ -117,17 +96,12 @@ public override void Setup () configView.Add (slider_orientation_slider); slider_orientation_slider.OptionsChanged += (sender, e) => { - View prev = null; foreach (var s in Win.Subviews.OfType ()) { - if (e.Options.ContainsKey (0)) { s.Orientation = Orientation.Horizontal; - s.AdjustBestHeight (); - s.Width = Dim.Percent (50); - - s.Style.SpaceChar = new Cell () { Rune = CM.Glyphs.HLine }; + s.Style.SpaceChar = new Cell { Rune = CM.Glyphs.HLine }; if (prev == null) { s.LayoutStyle = LayoutStyle.Absolute; @@ -142,11 +116,7 @@ public override void Setup () } else if (e.Options.ContainsKey (1)) { s.Orientation = Orientation.Vertical; - s.AdjustBestWidth (); - s.Height = Dim.Fill (); - - s.Style.SpaceChar = new Cell () { Rune = CM.Glyphs.VLine }; - + s.Style.SpaceChar = new Cell { Rune = CM.Glyphs.VLine }; if (prev == null) { s.LayoutStyle = LayoutStyle.Absolute; @@ -158,19 +128,29 @@ public override void Setup () s.Y = 0; prev = s; } + + if (s.Orientation == Orientation.Horizontal) { + s.Width = Dim.Percent (50); + var h = s.ShowLegends && s.LegendsOrientation == Orientation.Vertical ? s.Options.Max (o => o.Legend.Length) + 3 : 4; + s.Height = h; + } else { + var w = s.ShowLegends ? s.Options.Max (o => o.Legend.Length) + 3 : 3; + s.Width = w; + s.Height = Dim.Fill (); + } + } Win.LayoutSubviews (); }; - - #endregion + #endregion Slider Orientation Slider #region Legends Orientation Slider - var legends_orientation_slider = new Slider (new List { "Horizontal", "Vertical" }) { Title = "Legends Orientation", X = Pos.Center (), Y = Pos.Bottom (slider_orientation_slider) + 1, Width = Dim.Fill (), + Height = 4, BorderStyle = LineStyle.Single }; @@ -180,143 +160,170 @@ public override void Setup () legends_orientation_slider.OptionsChanged += (sender, e) => { foreach (var s in Win.Subviews.OfType ()) { - if (e.Options.ContainsKey (0)) + if (e.Options.ContainsKey (0)) { s.LegendsOrientation = Orientation.Horizontal; - else if (e.Options.ContainsKey (1)) + } else if (e.Options.ContainsKey (1)) { s.LegendsOrientation = Orientation.Vertical; + } + if (s.Orientation == Orientation.Horizontal) { + s.Width = Dim.Percent (50); + var h = s.ShowLegends && s.LegendsOrientation == Orientation.Vertical ? s.Options.Max (o => o.Legend.Length) + 3 : 4; + s.Height = h; + } else { + var w = s.ShowLegends ? s.Options.Max (o => o.Legend.Length) + 3 : 3; + s.Width = w; + s.Height = Dim.Fill (); + } } Win.LayoutSubviews (); }; - - #endregion + #endregion Legends Orientation Slider #region Color Slider + foreach (var s in Win.Subviews.OfType ()) { + s.Style.OptionChar.Attribute = Win.GetNormalColor (); + s.Style.SetChar.Attribute = Win.GetNormalColor (); + s.Style.LegendAttributes.SetAttribute = Win.GetNormalColor (); + s.Style.RangeChar.Attribute = Win.GetNormalColor (); + } - var sliderColor = new Slider<(Color, Color)> () { - Title = "Color", - X = Pos.Center (), + var sliderFGColor = new Slider<(Color, Color)> { + Title = "FG Color", + X = 0, Y = Pos.Bottom (legends_orientation_slider) + 1, Type = SliderType.Single, - Width = Dim.Fill (), BorderStyle = LineStyle.Single, - AllowEmpty = false + AllowEmpty = false, + Orientation = Orientation.Vertical, + LegendsOrientation = Orientation.Horizontal, + AutoSize = true }; - sliderColor.Style.SetChar.Attribute = new Terminal.Gui.Attribute (Color.BrightGreen, Color.Black); - sliderColor.Style.LegendAttributes.SetAttribute = new Terminal.Gui.Attribute (Color.Green, Color.Blue); + sliderFGColor.Style.SetChar.Attribute = new Attribute (Color.BrightGreen, Color.Black); + sliderFGColor.Style.LegendAttributes.SetAttribute = new Attribute (Color.Green, Color.Blue); - sliderColor.LegendsOrientation = Orientation.Vertical; var colorOptions = new List> (); foreach (var colorIndex in Enum.GetValues ()) { var colorName = colorIndex.ToString (); colorOptions.Add (new SliderOption<(Color, Color)> { - Data = (new Color((ColorName)colorIndex), Win.GetNormalColor ().Background), + Data = (new Color (colorIndex), new Color (colorIndex)), Legend = colorName, - LegendAbbr = (Rune)colorName [0], + LegendAbbr = (Rune)colorName [0] }); } - sliderColor.Options = colorOptions; + sliderFGColor.Options = colorOptions; - configView.Add (sliderColor); + configView.Add (sliderFGColor); - sliderColor.OptionsChanged += (sender, e) => { + sliderFGColor.OptionsChanged += (sender, e) => { if (e.Options.Count != 0) { var data = e.Options.First ().Value.Data; - foreach (var s in Win.Subviews.OfType ()) { - s.Style.OptionChar.Attribute = new Attribute (data.Item1, data.Item2); - s.Style.SetChar.Attribute = new Attribute (data.Item1, data.Item2); - s.Style.LegendAttributes.SetAttribute = new Attribute (data.Item1, Win.GetNormalColor ().Background); - s.Style.RangeChar.Attribute = new Attribute (data.Item1, Win.GetNormalColor ().Background); - s.Style.SpaceChar.Attribute = new Attribute (data.Item1, Win.GetNormalColor ().Background); - s.Style.LegendAttributes.NormalAttribute = new Attribute (data.Item1, Win.GetNormalColor ().Background); - // Here we can not call SetNeedDisplay(), because the OptionsChanged was triggered by Key Pressing, - // that internaly calls SetNeedDisplay. - } - } else { - foreach (var s in Win.Subviews.OfType ()) { - s.Style.SetChar.Attribute = null; - s.Style.LegendAttributes.SetAttribute = null; - s.Style.RangeChar.Attribute = null; + s.ColorScheme = new ColorScheme (s.ColorScheme); + s.ColorScheme.Normal = new Attribute (data.Item2, s.ColorScheme.Normal.Background); + + s.Style.OptionChar.Attribute = new Attribute (data.Item1, s.ColorScheme.Normal.Background); + s.Style.SetChar.Attribute = new Attribute (data.Item1, s.Style.SetChar.Attribute?.Background ?? s.ColorScheme.Normal.Background); + s.Style.LegendAttributes.SetAttribute = new Attribute (data.Item1, s.ColorScheme.Normal.Background); + s.Style.RangeChar.Attribute = new Attribute (data.Item1, s.ColorScheme.Normal.Background); + s.Style.SpaceChar.Attribute = new Attribute (data.Item1, s.ColorScheme.Normal.Background); + s.Style.LegendAttributes.NormalAttribute = new Attribute (data.Item1, s.ColorScheme.Normal.Background); } } }; - // Set option after Eventhandler def, so it updates the sliders color. - // sliderColor.SetOption (2); + var sliderBGColor = new Slider<(Color, Color)> { + 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, + AutoSize = true + }; + + sliderBGColor.Style.SetChar.Attribute = new Attribute (Color.BrightGreen, Color.Black); + sliderBGColor.Style.LegendAttributes.SetAttribute = new Attribute (Color.Green, Color.Blue); - #endregion + sliderBGColor.Options = colorOptions; - #endregion + configView.Add (sliderBGColor); - Win.FocusFirst (); + sliderBGColor.OptionsChanged += (sender, e) => { + if (e.Options.Count != 0) { + var data = e.Options.First ().Value.Data; + foreach (var s in Win.Subviews.OfType ()) { + s.ColorScheme = new ColorScheme (s.ColorScheme); + s.ColorScheme.Normal = new Attribute (s.ColorScheme.Normal.Foreground, data.Item2); + } + } + }; + #endregion Color Slider + #endregion Config Slider + + Win.FocusFirst (); + Application.Top.Initialized += (s, e) => Application.Top.LayoutSubviews (); } public void MakeSliders (View v, List options) { var types = Enum.GetValues (typeof (SliderType)).Cast ().ToList (); - Slider prev = null; foreach (var type in types) { - var view = new Slider (options, Orientation.Horizontal) { + var view = new Slider (options) { Title = type.ToString (), X = 0, - //X = Pos.Right (view) + 1, Y = prev == null ? 0 : Pos.Bottom (prev), - //Y = Pos.Center (), - Width = Dim.Percent (50), BorderStyle = LineStyle.Single, Type = type, - LegendsOrientation = Orientation.Horizontal, - AllowEmpty = true, + AllowEmpty = true }; v.Add (view); prev = view; - }; - - var singleOptions = new List { 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, Orientation.Horizontal) { - Title = "Actual slider", + var singleOptions = new List + { 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, - //X = Pos.Right (view) + 1, Y = prev == null ? 0 : Pos.Bottom (prev), - //Y = Pos.Center (), Type = SliderType.Single, - //BorderStyle = LineStyle.Single, - LegendsOrientation = Orientation.Horizontal, - Width = Dim.Percent (50), - AllowEmpty = false, - //ShowSpacing = true + BorderStyle = LineStyle.Single, + AllowEmpty = false }; single.LayoutStarted += (s, e) => { if (single.Orientation == Orientation.Horizontal) { - single.Style.SpaceChar = new Cell () { Rune = CM.Glyphs.HLine }; - single.Style.OptionChar = new Cell () { Rune = CM.Glyphs.HLine }; + single.Style.SpaceChar = new Cell { Rune = CM.Glyphs.HLine }; + single.Style.OptionChar = new Cell { Rune = CM.Glyphs.HLine }; } else { - single.Style.SpaceChar = new Cell () { Rune = CM.Glyphs.VLine }; - single.Style.OptionChar = new Cell () { Rune = CM.Glyphs.VLine }; + single.Style.SpaceChar = new Cell { Rune = CM.Glyphs.VLine }; + single.Style.OptionChar = new Cell { Rune = CM.Glyphs.VLine }; } }; - single.Style.SetChar = new Cell () { Rune = CM.Glyphs.ContinuousMeterSegment }; - single.Style.DragChar = new Cell () { Rune = CM.Glyphs.ContinuousMeterSegment }; + single.Style.SetChar = new Cell { Rune = CM.Glyphs.ContinuousMeterSegment }; + single.Style.DragChar = new Cell { Rune = CM.Glyphs.ContinuousMeterSegment }; v.Add (single); - var label = new Label () { - X = 0, - Y = Pos.Bottom (single), - Height = 1, - Width = Dim.Width (single), - Text = $"{single.GetSetOptions ().FirstOrDefault ()}" - }; single.OptionsChanged += (s, e) => { - label.Text = $"{e.Options.FirstOrDefault ().Key}"; + single.Title = $"Continuous {e.Options.FirstOrDefault ().Key}"; }; - v.Add (label); + var oneOption = new List { "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); } -} +} \ No newline at end of file diff --git a/UnitTests/Views/SliderTests.cs b/UnitTests/Views/SliderTests.cs index c3b4a628e3..67f890a43a 100644 --- a/UnitTests/Views/SliderTests.cs +++ b/UnitTests/Views/SliderTests.cs @@ -1,16 +1,11 @@ -using Xunit; -using Terminal.Gui; -using System; +using System; using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; +using Xunit; namespace Terminal.Gui.ViewsTests; public class SliderOptionTests { - - [Fact] public void Slider_Option_Default_Constructor () { @@ -24,9 +19,9 @@ public void Slider_Option_Default_Constructor () public void Slider_Option_Values_Constructor () { var o = new SliderOption ("1 thousand", new Rune ('y'), 1000); - Assert.Equal ("1 thousand", o.Legend); + Assert.Equal ("1 thousand", o.Legend); Assert.Equal (new Rune ('y'), o.LegendAbbr); - Assert.Equal (1000, o.Data); + Assert.Equal (1000, o.Data); } [Fact] @@ -87,7 +82,7 @@ public void SliderOption_ToString_WhenPopulated_WithInt () var sliderOption = new SliderOption { Legend = "Lord flibble", LegendAbbr = new Rune ('l'), - Data = 1, + Data = 1 }; Assert.Equal ("{Legend=Lord flibble, LegendAbbr=l, Data=1}", sliderOption.ToString ()); @@ -100,7 +95,7 @@ public void SliderOption_ToString_WhenPopulated_WithSizeF () var sliderOption = new SliderOption { Legend = "Lord flibble", LegendAbbr = new Rune ('l'), - Data = new SizeF(32,11), + Data = new SizeF (32, 11) }; Assert.Equal ("{Legend=Lord flibble, LegendAbbr=l, Data={Width=32, Height=11}}", sliderOption.ToString ()); @@ -150,7 +145,6 @@ public void Constructor_Sets_Cancel_Default_To_False () } } - public class SliderTests { [Fact] public void Constructor_Default () @@ -165,9 +159,9 @@ public void Constructor_Default () Assert.Equal (Orientation.Horizontal, slider.Orientation); Assert.False (slider.AllowEmpty); Assert.True (slider.ShowLegends); - Assert.False (slider.ShowSpacing); + Assert.False (slider.ShowEndSpacing); Assert.Equal (SliderType.Single, slider.Type); - Assert.Equal (0, slider.InnerSpacing); + Assert.Equal (0, slider.InnerSpacing); Assert.False (slider.AutoSize); Assert.Equal (0, slider.FocusedOption); } @@ -192,7 +186,7 @@ public void OnOptionsChanged_Event_Raised () { // Arrange var slider = new Slider (); - bool eventRaised = false; + var eventRaised = false; slider.OptionsChanged += (sender, args) => eventRaised = true; // Act @@ -207,9 +201,9 @@ public void OnOptionFocused_Event_Raised () { // Arrange var slider = new Slider (new List { 1, 2, 3 }); - bool eventRaised = false; + var eventRaised = false; slider.OptionFocused += (sender, args) => eventRaised = true; - int newFocusedOption = 1; + var newFocusedOption = 1; var args = new SliderEventArgs (new Dictionary> (), newFocusedOption); // Act @@ -224,10 +218,10 @@ public void OnOptionFocused_Event_Cancelled () { // Arrange var slider = new Slider (new List { 1, 2, 3 }); - bool eventRaised = false; - bool cancel = false; + var eventRaised = false; + var cancel = false; slider.OptionFocused += (sender, args) => eventRaised = true; - int newFocusedOption = 1; + var newFocusedOption = 1; // Create args with cancel set to false cancel = false; @@ -240,7 +234,7 @@ public void OnOptionFocused_Event_Cancelled () slider.OnOptionFocused (newFocusedOption, args); // Assert - Assert.True (eventRaised); // Event should be raised + Assert.True (eventRaised); // Event should be raised Assert.Equal (newFocusedOption, slider.FocusedOption); // Focused option should change // Create args with cancel set to true @@ -253,7 +247,7 @@ public void OnOptionFocused_Event_Cancelled () slider.OnOptionFocused (2, args); // Assert - Assert.True (eventRaised); // Event should be raised + Assert.True (eventRaised); // Event should be raised Assert.Equal (newFocusedOption, slider.FocusedOption); // Focused option should not change } @@ -271,7 +265,7 @@ public void TryGetPositionByOption_ValidOptionHorizontal_Success (int option, in // 1--2--3--4 // Act - bool result = slider.TryGetPositionByOption (option, out var position); + var result = slider.TryGetPositionByOption (option, out var position); // Assert Assert.True (result); @@ -292,7 +286,7 @@ public void TryGetPositionByOption_ValidOptionVertical_Success (int option, int slider.InnerSpacing = 2; // Act - bool result = slider.TryGetPositionByOption (option, out var position); + var result = slider.TryGetPositionByOption (option, out var position); // Assert Assert.True (result); @@ -305,11 +299,11 @@ public void TryGetPositionByOption_InvalidOption_Failure () { // Arrange var slider = new Slider (new List { 1, 2, 3 }); - int option = -1; + var option = -1; var expectedPosition = (-1, -1); // Act - bool result = slider.TryGetPositionByOption (option, out var position); + var result = slider.TryGetPositionByOption (option, out var position); // Assert Assert.False (result); @@ -335,7 +329,7 @@ public void TryGetOptionByPosition_ValidPositionHorizontal_Success (int x, int y // Arrange // Act - bool result = slider.TryGetOptionByPosition (x, y, threshold, out int option); + var result = slider.TryGetOptionByPosition (x, y, threshold, out var option); // Assert Assert.True (result); @@ -366,12 +360,9 @@ public void TryGetOptionByPosition_ValidPositionVertical_Success (int x, int y, // 7 | // 8 | // 9 4 - slider.CalcSpacingConfig (); - - // Arrange // Act - bool result = slider.TryGetOptionByPosition (x, y, threshold, out int option); + var result = slider.TryGetOptionByPosition (x, y, threshold, out var option); // Assert Assert.True (result); @@ -384,13 +375,13 @@ public void TryGetOptionByPosition_InvalidPosition_Failure () { // Arrange var slider = new Slider (new List { 1, 2, 3 }); - int x = 10; - int y = 10; - int threshold = 2; - int expectedOption = -1; + var x = 10; + var y = 10; + var threshold = 2; + var expectedOption = -1; // Act - bool result = slider.TryGetOptionByPosition (x, y, threshold, out int option); + var result = slider.TryGetOptionByPosition (x, y, threshold, out var option); // Assert Assert.False (result); @@ -405,7 +396,7 @@ public void MovePlus_Should_MoveFocusRight_When_OptionIsAvailable () slider.AutoSize = true; // Act - bool result = slider.MovePlus (); + var result = slider.MovePlus (); // Assert Assert.True (result); @@ -421,7 +412,7 @@ public void MovePlus_Should_NotMoveFocusRight_When_AtEnd () slider.FocusedOption = 3; // Act - bool result = slider.MovePlus (); + var result = slider.MovePlus (); // Assert Assert.False (result); @@ -439,7 +430,7 @@ public void Set_Should_SetFocusedOption () // Act slider.FocusedOption = 2; - bool result = slider.Set (); + var result = slider.Set (); // Assert Assert.True (result); @@ -459,14 +450,47 @@ public void Set_Should_Not_UnSetFocusedOption_When_EmptyNotAllowed () Assert.NotEmpty (slider.GetSetOptions ()); // Act - bool result = slider.UnSetOption (slider.FocusedOption); + var result = slider.UnSetOption (slider.FocusedOption); // Assert Assert.False (result); Assert.NotEmpty (slider.GetSetOptions ()); } - // Add more tests for different scenarios and edge cases. -} + [Fact] + void Set_Options_Throws_If_Null () + { + // Arrange + var slider = new Slider (); + + // Act/Assert + Assert.Throws (() => slider.Options = null); + + } + + [Fact] + void Set_Options_No_Legend_Throws () + { + // Arrange + var slider = new Slider (); + + // Act/Assert + Assert.Throws (() => slider.Options = null); + + } + + // https://github.com/gui-cs/Terminal.Gui/issues/3099 + [Fact] + void One_Option_Does_Not_Throw () + { + // Arrange + var slider = new Slider (); + slider.BeginInit (); + slider.EndInit (); + // Act/Assert + slider.Options = new List> { new () }; + } + // Add more tests for different scenarios and edge cases. +} \ No newline at end of file