diff --git a/Terminal.Gui/Views/ProgressBar.cs b/Terminal.Gui/Views/ProgressBar.cs index 66ed44c50..31ff869bd 100644 --- a/Terminal.Gui/Views/ProgressBar.cs +++ b/Terminal.Gui/Views/ProgressBar.cs @@ -42,7 +42,8 @@ public class ProgressBar : View private int _delta; private float _fraction; private bool _isActivity; - private ProgressBarStyle _progressBarStyle; + private ProgressBarStyle _progressBarStyle = ProgressBarStyle.MarqueeBlocks; + private ProgressBarFormat _progressBarFormat = ProgressBarFormat.SimplePlusPercentage; private Rune _segmentCharacter = Glyphs.BlocksMeterSegment; /// @@ -61,7 +62,6 @@ public class ProgressBar : View set { _bidirectionalMarquee = value; - // SetContentSize (Viewport.Size with { Height = 1 }); } } @@ -74,12 +74,9 @@ public class ProgressBar : View { _fraction = Math.Min (value, 1); _isActivity = false; - // SetContentSize (Viewport.Size with { Height = 1 }); } } - private ProgressBarFormat _progressBarFormat; - /// Specifies the format that a uses to indicate the visual presentation. public ProgressBarFormat ProgressBarFormat { @@ -87,7 +84,6 @@ public class ProgressBar : View set { _progressBarFormat = value; - // SetContentSize (Viewport.Size with { Height = 1 }); } } @@ -118,8 +114,6 @@ public class ProgressBar : View break; } - - // SetContentSize (Viewport.Size with { Height = 1 }); } } @@ -130,7 +124,6 @@ public class ProgressBar : View set { _segmentCharacter = value; - // SetContentSize (Viewport.Size with { Height = 1 }); } } @@ -289,6 +282,7 @@ public class ProgressBar : View private void SetInitialProperties () { + Width = Dim.Auto (Dim.DimAutoStyle.Content); Height = Dim.Auto (Dim.DimAutoStyle.Content, minimumContentDim: 1); CanFocus = false; _fraction = 0; diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs index 0823cfe31..0bdc8507c 100644 --- a/Terminal.Gui/Views/RadioGroup.cs +++ b/Terminal.Gui/Views/RadioGroup.cs @@ -18,12 +18,15 @@ public class RadioGroup : View { CanFocus = true; + Width = Dim.Auto (Dim.DimAutoStyle.Content); + Height = Dim.Auto (Dim.DimAutoStyle.Content); + // Things this view knows how to do AddCommand ( Command.LineUp, () => { - MoveUp (); + MoveUpLeft (); return true; } @@ -33,7 +36,7 @@ public class RadioGroup : View Command.LineDown, () => { - MoveDown (); + MoveDownRight (); return true; } @@ -68,12 +71,7 @@ public class RadioGroup : View } ); - // Default keybindings for this view - KeyBindings.Add (Key.CursorUp, Command.LineUp); - KeyBindings.Add (Key.CursorDown, Command.LineDown); - KeyBindings.Add (Key.Home, Command.TopHome); - KeyBindings.Add (Key.End, Command.BottomEnd); - KeyBindings.Add (Key.Space, Command.Accept); + SetupKeyBindings (); LayoutStarted += RadioGroup_LayoutStarted; @@ -84,14 +82,33 @@ public class RadioGroup : View // TODO: Fix InvertColorsOnPress - only highlight the selected item + private void SetupKeyBindings () + { + KeyBindings.Clear (); + // Default keybindings for this view + if (Orientation == Orientation.Vertical) + { + KeyBindings.Add (Key.CursorUp, Command.LineUp); + KeyBindings.Add (Key.CursorDown, Command.LineDown); + } + else + { + KeyBindings.Add (Key.CursorLeft, Command.LineUp); + KeyBindings.Add (Key.CursorRight, Command.LineDown); + } + KeyBindings.Add (Key.Home, Command.TopHome); + KeyBindings.Add (Key.End, Command.BottomEnd); + KeyBindings.Add (Key.Space, Command.Accept); + } + private void RadioGroup_MouseClick (object sender, MouseEventEventArgs e) { SetFocus (); - int boundsX = e.MouseEvent.Position.X; - int boundsY = e.MouseEvent.Position.Y; + int viewportX = e.MouseEvent.Position.X; + int viewportY = e.MouseEvent.Position.Y; - int pos = _orientation == Orientation.Horizontal ? boundsX : boundsY; + int pos = _orientation == Orientation.Horizontal ? viewportX : viewportY; int rCount = _orientation == Orientation.Horizontal ? _horizontal.Last ().pos + _horizontal.Last ().length @@ -100,8 +117,8 @@ public class RadioGroup : View if (pos < rCount) { int c = _orientation == Orientation.Horizontal - ? _horizontal.FindIndex (x => x.pos <= boundsX && x.pos + x.length - 2 >= boundsX) - : boundsY; + ? _horizontal.FindIndex (x => x.pos <= viewportX && x.pos + x.length - 2 >= viewportX) + : viewportY; if (c > -1) { @@ -125,9 +142,8 @@ public class RadioGroup : View if (_horizontalSpace != value && _orientation == Orientation.Horizontal) { _horizontalSpace = value; - SetWidthHeight (_radioLabels); UpdateTextFormatterText (); - SetNeedsDisplay (); + SetContentSize (); } } } @@ -143,7 +159,7 @@ public class RadioGroup : View } /// - /// The radio labels to display. A key binding will be added for each radio radio enabling the user to select + /// The radio labels to display. A key binding will be added for each radio enabling the user to select /// and/or focus the radio label using the keyboard. See for details on how HotKeys work. /// /// The radio labels. @@ -172,16 +188,30 @@ public class RadioGroup : View } } - if (IsInitialized && prevCount != _radioLabels.Count) - { - SetWidthHeight (_radioLabels); - } - SelectedItem = 0; - SetNeedsDisplay (); + SetContentSize (); } } + /// + public override string Text + { + get + { + if (_radioLabels.Count == 0) + { + return string.Empty; + } + // Return labels as a CSV string + return string.Join (",", _radioLabels); + } + set => + + // Parse as CSV string with spaces trimmed + RadioLabels = value.Split (',').Select (x => x.Trim ()).ToArray (); + + } + /// The currently selected item from the list of radio labels /// The selected. public int SelectedItem @@ -320,7 +350,8 @@ public class RadioGroup : View if (!args.Cancel) { _orientation = newOrientation; - SetNeedsLayout (); + SetupKeyBindings (); + SetContentSize (); } return args.Cancel; @@ -373,42 +404,7 @@ public class RadioGroup : View /// Invoked when the selected radio label has changed. public event EventHandler SelectedItemChanged; - private void CalculateHorizontalPositions () - { - if (_orientation == Orientation.Horizontal) - { - _horizontal = new List<(int pos, int length)> (); - var start = 0; - var length = 0; - - for (var i = 0; i < _radioLabels.Count; i++) - { - start += length; - - length = _radioLabels [i].GetColumns () + 2 + (i < _radioLabels.Count - 1 ? _horizontalSpace : 0); - _horizontal.Add ((start, length)); - } - } - } - - private static Rectangle MakeRect (int x, int y, List radioLabels) - { - if (radioLabels is null) - { - return new (x, y, 0, 0); - } - - var width = 0; - - foreach (string s in radioLabels) - { - width = Math.Max (s.GetColumns () + 2, width); - } - - return new (x, y, width, radioLabels.Count); - } - - private void MoveDown () + private void MoveDownRight () { if (_cursor + 1 < _radioLabels.Count) { @@ -425,7 +421,7 @@ public class RadioGroup : View private void MoveEnd () { _cursor = Math.Max (_radioLabels.Count - 1, 0); } private void MoveHome () { _cursor = 0; } - private void MoveUp () + private void MoveUpLeft () { if (_cursor > 0) { @@ -439,39 +435,37 @@ public class RadioGroup : View } } - private void RadioGroup_LayoutStarted (object sender, EventArgs e) { SetWidthHeight (_radioLabels); } + private void RadioGroup_LayoutStarted (object sender, EventArgs e) { SetContentSize (); } private void SelectItem () { SelectedItem = _cursor; } - private void SetWidthHeight (List radioLabels) + private void SetContentSize () { switch (_orientation) { case Orientation.Vertical: - Rectangle r = MakeRect (0, 0, radioLabels); + var width = 0; - if (IsInitialized) + foreach (string s in _radioLabels) { - Width = r.Width + GetAdornmentsThickness ().Horizontal; - Height = radioLabels.Count + GetAdornmentsThickness ().Vertical; + width = Math.Max (s.GetColumns () + 2, width); } + SetContentSize (new (width, _radioLabels.Count)); break; case Orientation.Horizontal: - CalculateHorizontalPositions (); + _horizontal = new List<(int pos, int length)> (); + var start = 0; var length = 0; - foreach ((int pos, int length) item in _horizontal) + for (var i = 0; i < _radioLabels.Count; i++) { - length += item.length; - } + start += length; - if (IsInitialized) - { - Width = length + GetAdornmentsThickness ().Vertical; - Height = 1 + GetAdornmentsThickness ().Horizontal; + length = _radioLabels [i].GetColumns () + 2 + (i < _radioLabels.Count - 1 ? _horizontalSpace : 0); + _horizontal.Add ((start, length)); } - + SetContentSize (new (_horizontal.Sum (item => item.length), 1)); break; } } diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs index f5d795ff5..2b2312b11 100644 --- a/UICatalog/Scenarios/AllViewsTester.cs +++ b/UICatalog/Scenarios/AllViewsTester.cs @@ -17,7 +17,6 @@ public class AllViewsTester : Scenario // TODO: This is missing some private readonly List _posNames = new () { "Factor", "AnchorEnd", "Center", "Absolute" }; private ListView _classListView; - private CheckBox _computedCheckBox; private View _curView; private FrameView _hostPane; private RadioGroup _hRadioGroup; @@ -39,6 +38,9 @@ public class AllViewsTester : Scenario private RadioGroup _yRadioGroup; private TextField _yText; private int _yVal; + private RadioGroup _orientation; + private string _demoText = "This, that, and the other thing."; + private TextView _demoTextView; public override void Init () { @@ -90,7 +92,7 @@ public class AllViewsTester : Scenario { X = 0, Y = 0, - Width = 15, + Width = Dim.Auto (Dim.DimAutoStyle.Content), Height = Dim.Fill (1), // for status bar CanFocus = false, ColorScheme = Colors.ColorSchemes ["TopLevel"], @@ -101,7 +103,7 @@ public class AllViewsTester : Scenario { X = 0, Y = 0, - Width = Dim.Fill (), + Width = Dim.Auto (), Height = Dim.Fill (), AllowsMarking = false, ColorScheme = Colors.ColorSchemes ["TopLevel"], @@ -131,30 +133,20 @@ public class AllViewsTester : Scenario X = Pos.Right (_leftPane), Y = 0, // for menu Width = Dim.Fill (), - Height = 10, + Height = Dim.Auto (), CanFocus = false, ColorScheme = Colors.ColorSchemes ["TopLevel"], Title = "Settings" }; - _computedCheckBox = new CheckBox { X = 0, Y = 0, Text = "_Computed Layout", Checked = true }; - - _computedCheckBox.Toggled += (s, e) => - { - if (_curView != null) - { - _hostPane.LayoutSubviews (); - } - }; - _settingsPane.Add (_computedCheckBox); string [] radioItems = { "_Percent(x)", "_AnchorEnd", "_Center", "A_t(x)" }; _locationFrame = new FrameView { - X = Pos.Left (_computedCheckBox), - Y = Pos.Bottom (_computedCheckBox), - Height = 3 + radioItems.Length, - Width = 36, + X = 0, + Y = 0, + Height = Dim.Auto (), //3 + radioItems.Length, + Width = Dim.Auto (), Title = "Location (Pos)" }; _settingsPane.Add (_locationFrame); @@ -165,16 +157,16 @@ public class AllViewsTester : Scenario _xRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); _xText = new TextField { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_xVal}" }; - _xText.TextChanged += (s, args) => - { - try - { - _xVal = int.Parse (_xText.Text); - DimPosChanged (_curView); - } - catch - { } - }; + _xText.Accept += (s, args) => + { + try + { + _xVal = int.Parse (_xText.Text); + DimPosChanged (_curView); + } + catch + { } + }; _locationFrame.Add (_xText); _locationFrame.Add (_xRadioGroup); @@ -184,16 +176,16 @@ public class AllViewsTester : Scenario _locationFrame.Add (label); _yText = new TextField { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_yVal}" }; - _yText.TextChanged += (s, args) => - { - try - { - _yVal = int.Parse (_yText.Text); - DimPosChanged (_curView); - } - catch - { } - }; + _yText.Accept += (s, args) => + { + try + { + _yVal = int.Parse (_yText.Text); + DimPosChanged (_curView); + } + catch + { } + }; _locationFrame.Add (_yText); _yRadioGroup = new RadioGroup { X = Pos.X (label), Y = Pos.Bottom (label), RadioLabels = radioItems }; _yRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); @@ -208,14 +200,14 @@ public class AllViewsTester : Scenario Title = "Size (Dim)" }; - radioItems = new [] { "Auto (min)", "_Percent(width)", "_Fill(width)", "_Sized(width)" }; + radioItems = new [] { "Auto", "_Percent(width)", "_Fill(width)", "_Sized(width)" }; label = new Label { X = 0, Y = 0, Text = "Width:" }; _sizeFrame.Add (label); _wRadioGroup = new RadioGroup { X = 0, Y = Pos.Bottom (label), RadioLabels = radioItems }; _wRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); _wText = new TextField { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_wVal}" }; - _wText.TextChanged += (s, args) => + _wText.Accept += (s, args) => { try { @@ -241,34 +233,34 @@ public class AllViewsTester : Scenario _sizeFrame.Add (_wText); _sizeFrame.Add (_wRadioGroup); - radioItems = new [] { "_Auto (min)", "P_ercent(height)", "F_ill(height)", "Si_zed(height)" }; + radioItems = new [] { "_Auto", "P_ercent(height)", "F_ill(height)", "Si_zed(height)" }; label = new Label { X = Pos.Right (_wRadioGroup) + 1, Y = 0, Text = "Height:" }; _sizeFrame.Add (label); _hText = new TextField { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_hVal}" }; - _hText.TextChanged += (s, args) => - { - try - { - switch (_hRadioGroup.SelectedItem) - { - case 1: - _hVal = Math.Min (int.Parse (_hText.Text), 100); + _hText.Accept += (s, args) => + { + try + { + switch (_hRadioGroup.SelectedItem) + { + case 1: + _hVal = Math.Min (int.Parse (_hText.Text), 100); - break; - case 0: - case 2: - case 3: - _hVal = int.Parse (_hText.Text); + break; + case 0: + case 2: + case 3: + _hVal = int.Parse (_hText.Text); - break; - } + break; + } - DimPosChanged (_curView); - } - catch - { } - }; + DimPosChanged (_curView); + } + catch + { } + }; _sizeFrame.Add (_hText); _hRadioGroup = new RadioGroup { X = Pos.X (label), Y = Pos.Bottom (label), RadioLabels = radioItems }; @@ -277,6 +269,40 @@ public class AllViewsTester : Scenario _settingsPane.Add (_sizeFrame); + label = new Label { X = 0, Y = Pos.Bottom (_sizeFrame), Text = "_Orientation:" }; + _orientation = new RadioGroup + { + X = Pos.Right (label) + 1, + Y = Pos.Top (label), + RadioLabels = new [] { "Horizontal", "Vertical" }, + Orientation = Orientation.Horizontal + }; + _orientation.SelectedItemChanged += (s, selected) => + { + if (_curView?.GetType ().GetProperty ("Orientation") is {} prop) + { + prop.GetSetMethod ()?.Invoke (_curView, new object [] { _orientation.SelectedItem }); + } + }; + _settingsPane.Add (label, _orientation); + + label = new Label { X = 0, Y = Pos.Bottom (_orientation), Text = "_Text:" }; + _demoTextView = new () + { + X = Pos.Right (label) + 1, + Y = Pos.Top (label), + Width = Dim.Fill (), + Height = Dim.Auto (minimumContentDim: 2), + Text = _demoText + }; + _demoTextView.ContentsChanged += (s, e) => + { + _demoText = _demoTextView.Text; + _curView.Text = _demoText; + }; + + _settingsPane.Add (label, _demoTextView); + _hostPane = new FrameView { X = Pos.Right (_leftPane), @@ -327,7 +353,7 @@ public class AllViewsTester : Scenario view.GetType () .GetProperty ("Text") ?.GetSetMethod () - ?.Invoke (view, new [] { "Test Text" }); + ?.Invoke (view, new [] { _demoText }); } catch (TargetInvocationException e) { @@ -339,7 +365,7 @@ public class AllViewsTester : Scenario // If the view supports a Title property, set it so we have something to look at if (view != null && view.GetType ().GetProperty ("Title") != null) { - if (view.GetType ().GetProperty ("Title").PropertyType == typeof (string)) + if (view.GetType ().GetProperty ("Title")!.PropertyType == typeof (string)) { view?.GetType () .GetProperty ("Title") @@ -362,8 +388,16 @@ public class AllViewsTester : Scenario view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source }); } - // Set Settings - _computedCheckBox.Checked = view.LayoutStyle == LayoutStyle.Computed; + // If the view supports a Title property, set it so we have something to look at + if (view?.GetType ().GetProperty ("Orientation") is {} prop) + { + _orientation.SelectedItem = (int)prop.GetGetMethod()!.Invoke (view, null)!; + _orientation.Enabled = true; + } + else + { + _orientation.Enabled = false; + } view.Initialized += View_Initialized; @@ -407,7 +441,7 @@ public class AllViewsTester : Scenario view.Width = _wRadioGroup.SelectedItem switch { - 0 => Dim.Auto (minimumContentDim: _wVal), + 0 => Dim.Auto (), 1 => Dim.Percent (_wVal), 2 => Dim.Fill (_wVal), 3 => Dim.Sized (_wVal), @@ -416,7 +450,7 @@ public class AllViewsTester : Scenario view.Height = _hRadioGroup.SelectedItem switch { - 0 => Dim.Auto (minimumContentDim: _hVal), + 0 => Dim.Auto (), 1 => Dim.Percent (_hVal), 2 => Dim.Fill (_hVal), 3 => Dim.Sized (_hVal), @@ -428,6 +462,30 @@ public class AllViewsTester : Scenario MessageBox.ErrorQuery ("Exception", e.Message, "Ok"); } + if (view.Width is Dim.DimAuto) + { + _wText.Text = $"Auto"; + _wText.Enabled = false; + } + else + { + _wText.Text = $"{_wVal}"; + _wText.Enabled = true; + } + + if (view.Height is Dim.DimAuto) + { + _hText.Text = $"Auto"; + _hText.Enabled = false; + } + else + { + _hText.Text = $"{_hVal}"; + _hText.Enabled = true; + } + + + UpdateTitle (view); } @@ -470,8 +528,28 @@ public class AllViewsTester : Scenario var h = view.Height.ToString (); _wRadioGroup.SelectedItem = _dimNames.IndexOf (_dimNames.Where (s => w.Contains (s)).First ()); _hRadioGroup.SelectedItem = _dimNames.IndexOf (_dimNames.Where (s => h.Contains (s)).First ()); - _wText.Text = $"{view.Frame.Width}"; - _hText.Text = $"{view.Frame.Height}"; + if (view.Width is Dim.DimAuto) + { + _wText.Text = $"Auto"; + _wText.Enabled = false; + } + else + { + _wText.Text = $"100"; + _wText.Enabled = true; + } + + if (view.Height is Dim.DimAuto) + { + _hText.Text = $"Auto"; + _hText.Enabled = false; + } + else + { + _hText.Text = $"100"; + _hText.Enabled = true; + } + } private void UpdateTitle (View view) { _hostPane.Title = $"{view.GetType ().Name} - {view.X}, {view.Y}, {view.Width}, {view.Height}"; } @@ -488,7 +566,7 @@ public class AllViewsTester : Scenario view.Width = Dim.Fill (); } - if (view.Width is not Dim.DimAuto && (view.Height is null || view.Frame.Height == 0)) + if (view.Height is not Dim.DimAuto && (view.Height is null || view.Frame.Height == 0)) { view.Height = Dim.Fill (); } diff --git a/UICatalog/Scenarios/DimAutoDemo.cs b/UICatalog/Scenarios/DimAutoDemo.cs index 457171806..283b76655 100644 --- a/UICatalog/Scenarios/DimAutoDemo.cs +++ b/UICatalog/Scenarios/DimAutoDemo.cs @@ -33,7 +33,7 @@ public class DimAutoDemo : Scenario var textEdit = new TextView { Text = "", - X = 1, Y = 0, Width = 20, Height = 4 + X = 0, Y = 0, Width = 20, Height = 4 }; view.Add (textEdit); diff --git a/UICatalog/Scenarios/ProgressBarStyles.cs b/UICatalog/Scenarios/ProgressBarStyles.cs index 77d9c2390..f450ad73d 100644 --- a/UICatalog/Scenarios/ProgressBarStyles.cs +++ b/UICatalog/Scenarios/ProgressBarStyles.cs @@ -168,12 +168,14 @@ public class ProgressBarStyles : Scenario Title = "Blocks", X = Pos.Center (), Y = Pos.Bottom (button) + 1, - Width = Dim.Percent(50), + Width = Dim.Percent (50), BorderStyle = LineStyle.Single, CanFocus = true }; container.Add (blocksPB); + rbPBFormat.SelectedItem = (int)blocksPB.ProgressBarFormat; + var continuousPB = new ProgressBar { Title = "Continuous", diff --git a/UnitTests/View/Layout/Dim.AutoTests.cs b/UnitTests/View/Layout/Dim.AutoTests.cs index e381d3c09..8524f5ff6 100644 --- a/UnitTests/View/Layout/Dim.AutoTests.cs +++ b/UnitTests/View/Layout/Dim.AutoTests.cs @@ -110,7 +110,9 @@ public class DimAutoTests (ITestOutputHelper output) [Theory] [InlineData (1, 100, 100)] - [InlineData (1, 50, 50)] + [InlineData (1, 50, 52)] // 50% of 100 is 50, but the border adds 2 + [InlineData (1, 30, 32)] // 30% of 100 is 30, but the border adds 2 + [InlineData (2, 30, 32)] // 30% of 100 is 30, but the border adds 2 public void Min_Percent_Is_Content_Relative (int contentSize, int minPercent, int expected) { var view = new View diff --git a/UnitTests/Views/RadioGroupTests.cs b/UnitTests/Views/RadioGroupTests.cs index bc7ba90bb..8644b6bbb 100644 --- a/UnitTests/Views/RadioGroupTests.cs +++ b/UnitTests/Views/RadioGroupTests.cs @@ -206,8 +206,8 @@ public class RadioGroupTests Assert.Equal (2, rg.HorizontalSpace); Assert.Equal (0, rg.X); Assert.Equal (0, rg.Y); - Assert.Equal (21, rg.Width); - Assert.Equal (1, rg.Height); + Assert.Equal (21, rg.Frame.Width); + Assert.Equal (1, rg.Frame.Height); expected = @$" ┌────────────────────────────┐ @@ -231,8 +231,8 @@ public class RadioGroupTests Assert.Equal (4, rg.HorizontalSpace); Assert.Equal (0, rg.X); Assert.Equal (0, rg.Y); - Assert.Equal (23, rg.Width); - Assert.Equal (1, rg.Height); + Assert.Equal (23, rg.Frame.Width); + Assert.Equal (1, rg.Frame.Height); expected = @$" ┌────────────────────────────┐