diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index 58bfddd4d..a9fa0db3a 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -1251,7 +1251,7 @@ public static partial class Application { } } - bool FrameHandledMouseEvent (Adornment frame) + bool AdornmentHandledMouseEvent(Adornment frame) { if (frame?.Thickness.Contains (frame.FrameToScreen (), a.MouseEvent.X, a.MouseEvent.Y) ?? false) { var boundsPoint = frame.ScreenToBounds (a.MouseEvent.X, a.MouseEvent.Y); @@ -1272,10 +1272,10 @@ public static partial class Application { if (view != null) { // Work inside-out (Padding, Border, Margin) // TODO: Debate whether inside-out or outside-in is the right strategy - if (FrameHandledMouseEvent (view?.Padding)) { + if (AdornmentHandledMouseEvent(view?.Padding)) { return; } - if (FrameHandledMouseEvent (view?.Border)) { + if (AdornmentHandledMouseEvent(view?.Border)) { if (view is Toplevel) { // TODO: This is a temporary hack to work around the fact that // drag handling is handled in Toplevel (See Issue #2537) @@ -1314,7 +1314,7 @@ public static partial class Application { return; } - if (FrameHandledMouseEvent (view?.Margin)) { + if (AdornmentHandledMouseEvent(view?.Margin)) { return; } diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs index 080b10d17..78c2f08de 100644 --- a/Terminal.Gui/Drawing/Thickness.cs +++ b/Terminal.Gui/Drawing/Thickness.cs @@ -109,8 +109,8 @@ namespace Terminal.Gui { /// the rectangle described by . /// /// Describes the location and size of the rectangle that contains the thickness. - /// - /// + /// The x coord to check. + /// The y coord to check. /// if the specified coordinate is within the thickness; otherwise. public bool Contains (Rect outside, int x, int y) { diff --git a/Terminal.Gui/Resources/Strings.Designer.cs b/Terminal.Gui/Resources/Strings.Designer.cs index 408e14395..99c47bd57 100644 --- a/Terminal.Gui/Resources/Strings.Designer.cs +++ b/Terminal.Gui/Resources/Strings.Designer.cs @@ -61,7 +61,7 @@ namespace Terminal.Gui.Resources { } /// - /// Looks up a localized string similar to Cancel. + /// Looks up a localized string similar to _Cancel. /// internal static string btnCancel { get { @@ -70,7 +70,7 @@ namespace Terminal.Gui.Resources { } /// - /// Looks up a localized string similar to No. + /// Looks up a localized string similar to _No. /// internal static string btnNo { get { @@ -79,7 +79,7 @@ namespace Terminal.Gui.Resources { } /// - /// Looks up a localized string similar to OK. + /// Looks up a localized string similar to _OK. /// internal static string btnOk { get { @@ -88,7 +88,7 @@ namespace Terminal.Gui.Resources { } /// - /// Looks up a localized string similar to Open. + /// Looks up a localized string similar to O_pen. /// internal static string btnOpen { get { @@ -97,7 +97,7 @@ namespace Terminal.Gui.Resources { } /// - /// Looks up a localized string similar to Save. + /// Looks up a localized string similar to _Save. /// internal static string btnSave { get { @@ -106,7 +106,7 @@ namespace Terminal.Gui.Resources { } /// - /// Looks up a localized string similar to Save as. + /// Looks up a localized string similar to Save _as. /// internal static string btnSaveAs { get { @@ -115,7 +115,7 @@ namespace Terminal.Gui.Resources { } /// - /// Looks up a localized string similar to Yes. + /// Looks up a localized string similar to _Yes. /// internal static string btnYes { get { diff --git a/Terminal.Gui/Resources/Strings.resx b/Terminal.Gui/Resources/Strings.resx index 1152cfb1d..60fbac4f7 100644 --- a/Terminal.Gui/Resources/Strings.resx +++ b/Terminal.Gui/Resources/Strings.resx @@ -227,7 +227,7 @@ New Folder - No + _No Rename Failed @@ -239,25 +239,25 @@ Rename - Yes + _Yes Existing - Open + O_pen - Save + _Save - Save as + Save _as - OK + _OK - Cancel + _Cancel _Delete diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 7ada30a32..dbcd6ef11 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -960,12 +960,14 @@ namespace Terminal.Gui { /// /// The text to look in. /// The HotKey specifier (e.g. '_') to look for. - /// If true the legacy behavior of identifying the first upper case character as the HotKey will be enabled. - /// Regardless of the value of this parameter, hotKeySpecifier takes precedence. /// Outputs the Rune index into text. /// Outputs the hotKey. if not found. + /// If true the legacy behavior of identifying the + /// first upper case character as the HotKey will be enabled. + /// Regardless of the value of this parameter, hotKeySpecifier takes precedence. + /// Defaults to . /// true if a HotKey was found; false otherwise. - public static bool FindHotKey (string text, Rune hotKeySpecifier, bool firstUpperCase, out int hotPos, out Key hotKey) + public static bool FindHotKey (string text, Rune hotKeySpecifier, out int hotPos, out Key hotKey, bool firstUpperCase = false) { if (string.IsNullOrEmpty (text) || hotKeySpecifier == (Rune)0xFFFF) { hotPos = -1; @@ -1328,7 +1330,7 @@ namespace Terminal.Gui { if (NeedsFormat) { var shown_text = _text; - if (FindHotKey (_text, HotKeySpecifier, true, out _hotKeyPos, out var newHotKey)) { + if (FindHotKey (_text, HotKeySpecifier, out _hotKeyPos, out var newHotKey)) { HotKey = newHotKey; shown_text = RemoveHotKeySpecifier (Text, _hotKeyPos, HotKeySpecifier); shown_text = ReplaceHotKeyWithTag (shown_text, _hotKeyPos); @@ -1412,7 +1414,7 @@ namespace Terminal.Gui { foreach (var line in Lines) { sb.AppendLine (line); } - return sb.ToString (); + return sb.ToString ().TrimEnd (Environment.NewLine.ToCharArray ()); } /// diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/ViewDrawing.cs index 4edf04a72..f55991324 100644 --- a/Terminal.Gui/View/ViewDrawing.cs +++ b/Terminal.Gui/View/ViewDrawing.cs @@ -299,8 +299,8 @@ public partial class View { { var hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier; Application.Driver.SetAttribute (normalColor); - foreach (var rune in text) { - if (rune == hotkeySpec.Value) { + foreach (var rune in text.EnumerateRunes ()) { + if (rune == new Rune(hotkeySpec.Value)) { Application.Driver.SetAttribute (hotColor); continue; } diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/ViewKeyboard.cs index 6caccdac4..62e14acf8 100644 --- a/Terminal.Gui/View/ViewKeyboard.cs +++ b/Terminal.Gui/View/ViewKeyboard.cs @@ -194,7 +194,7 @@ public partial class View { if (TextFormatter == null || HotKeySpecifier == new Rune ('\xFFFF')) { return; // throw new InvalidOperationException ("Can't set HotKey unless a TextFormatter has been created"); } - if (TextFormatter.FindHotKey (_text, HotKeySpecifier, true, out _, out var hk)) { + if (TextFormatter.FindHotKey (_text, HotKeySpecifier, out _, out var hk)) { if (_hotKey.KeyCode != hk) { HotKey = hk; } diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index c22ec013d..20bc2d55c 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -23,9 +23,6 @@ namespace Terminal.Gui; /// Use to change the hot key specifier from the default of ('_'). /// /// -/// If no hot key specifier is found, the first uppercase letter encountered will be used as the hot key. -/// -/// /// When the button is configured as the default () and the user presses /// the ENTER key, if no other processes the key, the 's /// event will will be fired. diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs index e56505e70..f3fb75145 100644 --- a/Terminal.Gui/Views/RadioGroup.cs +++ b/Terminal.Gui/Views/RadioGroup.cs @@ -170,14 +170,14 @@ public class RadioGroup : View { set { // Remove old hot key bindings foreach (var label in _radioLabels) { - if (TextFormatter.FindHotKey (label, HotKeySpecifier, true, out _, out var hotKey)) { + if (TextFormatter.FindHotKey (label, HotKeySpecifier, out _, out var hotKey)) { AddKeyBindingsForHotKey (hotKey, KeyCode.Null); } } var prevCount = _radioLabels.Count; _radioLabels = value.ToList (); foreach (var label in _radioLabels) { - if (TextFormatter.FindHotKey (label, HotKeySpecifier, true, out _, out var hotKey)) { + if (TextFormatter.FindHotKey (label, HotKeySpecifier, out _, out var hotKey)) { AddKeyBindingsForHotKey (KeyCode.Null, hotKey); } } @@ -202,7 +202,7 @@ public class RadioGroup : View { if (KeyBindings.TryGet (key, out _)) { // Search RadioLabels for (int i = 0; i < _radioLabels.Count; i++) { - if (TextFormatter.FindHotKey (_radioLabels [i], HotKeySpecifier, true, out _, out var hotKey) + if (TextFormatter.FindHotKey (_radioLabels [i], HotKeySpecifier, out _, out var hotKey, true) && (key.NoAlt.NoCtrl.NoShift) == hotKey) { SelectedItem = i; keyEvent.Scope = KeyBindingScope.HotKey; @@ -246,7 +246,7 @@ public class RadioGroup : View { var rl = _radioLabels [i]; Driver.SetAttribute (GetNormalColor ()); Driver.AddStr ($"{(i == _selected ? CM.Glyphs.Selected : CM.Glyphs.UnSelected)} "); - TextFormatter.FindHotKey (rl, HotKeySpecifier, true, out int hotPos, out var hotKey); + TextFormatter.FindHotKey (rl, HotKeySpecifier, out int hotPos, out var hotKey); if (hotPos != -1 && (hotKey != KeyCode.Null)) { var rlRunes = rl.ToRunes (); for (int j = 0; j < rlRunes.Length; j++) { diff --git a/Terminal.Gui/Views/Tab.cs b/Terminal.Gui/Views/Tab.cs index 0c0be4a2c..88e726910 100644 --- a/Terminal.Gui/Views/Tab.cs +++ b/Terminal.Gui/Views/Tab.cs @@ -1,39 +1,29 @@ namespace Terminal.Gui; /// -/// A single tab in a +/// A single tab in a . /// -public class Tab { - private string text; +public class Tab : View { + private string _displayText; /// - /// The text to display in a + /// The text to display in a . /// /// - public string Text { get => text ?? "Unamed"; set => text = value; } + public string DisplayText { get => _displayText ?? "Unamed"; set => _displayText = value; } /// - /// The control to display when the tab is selected + /// The control to display when the tab is selected. /// /// public View View { get; set; } /// - /// Creates a new unamed tab with no controls inside + /// Creates a new unamed tab with no controls inside. /// public Tab () { - - } - - /// - /// Creates a new tab with the given text hosting a view - /// - /// - /// - public Tab (string text, View view) - { - this.Text = text; - this.View = view; + BorderStyle = LineStyle.Rounded; + CanFocus = true; } } diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs index ca32d8e28..795f1fed6 100644 --- a/Terminal.Gui/Views/TabView.cs +++ b/Terminal.Gui/Views/TabView.cs @@ -1,737 +1,929 @@ -using System.Text; using System; using System.Collections.Generic; -using System.Data; using System.Linq; -namespace Terminal.Gui { +namespace Terminal.Gui; + +/// +/// Control that hosts multiple sub views, presenting a single one at once. +/// +public class TabView : View { + private Tab _selectedTab; /// - /// Control that hosts multiple sub views, presenting a single one at once + /// The default to set on new controls. /// - public class TabView : View { - private Tab selectedTab; + public const uint DefaultMaxTabTextWidth = 30; - /// - /// The default to set on new controls - /// - public const uint DefaultMaxTabTextWidth = 30; + /// + /// This sub view is the 2 or 3 line control that represents the actual tabs themselves. + /// + TabRowView _tabsBar; - /// - /// This sub view is the 2 or 3 line control that represents the actual tabs themselves - /// - TabRowView tabsBar; + /// + /// This sub view is the main client area of the current tab. It hosts the + /// of the tab, the . + /// + View _contentView; + private List _tabs = new List (); - /// - /// This sub view is the main client area of the current tab. It hosts the - /// of the tab, the - /// - View contentView; - private List tabs = new List (); + /// + /// All tabs currently hosted by the control. + /// + /// + public IReadOnlyCollection Tabs { get => _tabs.AsReadOnly (); } - /// - /// All tabs currently hosted by the control - /// - /// - public IReadOnlyCollection Tabs { get => tabs.AsReadOnly (); } + /// + /// When there are too many tabs to render, this indicates the first + /// tab to render on the screen. + /// + /// + public int TabScrollOffset { + get => _tabScrollOffset; + set { + _tabScrollOffset = EnsureValidScrollOffsets (value); + } + } - /// - /// When there are too many tabs to render, this indicates the first - /// tab to render on the screen. - /// - /// - public int TabScrollOffset { get; set; } + /// + /// The maximum number of characters to render in a Tab header. This prevents one long tab + /// from pushing out all the others. + /// + public uint MaxTabTextWidth { get; set; } = DefaultMaxTabTextWidth; - /// - /// The maximum number of characters to render in a Tab header. This prevents one long tab - /// from pushing out all the others. - /// - public uint MaxTabTextWidth { get; set; } = DefaultMaxTabTextWidth; + /// + /// Event for when changes. + /// + public event EventHandler SelectedTabChanged; - /// - /// Event for when changes - /// - public event EventHandler SelectedTabChanged; + /// + /// Event fired when a is clicked. Can be used to cancel navigation, + /// show context menu (e.g. on right click) etc. + /// + public event EventHandler TabClicked; - /// - /// Event fired when a is clicked. Can be used to cancel navigation, - /// show context menu (e.g. on right click) etc. - /// - public event EventHandler TabClicked; + /// + /// The currently selected member of chosen by the user. + /// + /// + public Tab SelectedTab { + get => _selectedTab; + set { + UnSetCurrentTabs (); - /// - /// The currently selected member of chosen by the user - /// - /// - public Tab SelectedTab { - get => selectedTab; - set { + var old = _selectedTab; - var old = selectedTab; - - if (selectedTab != null) { - - if (selectedTab.View != null) { - // remove old content - contentView.Remove (selectedTab.View); - } + if (_selectedTab != null) { + if (_selectedTab.View != null) { + // remove old content + _contentView.Remove (_selectedTab.View); } + } - selectedTab = value; + _selectedTab = value; - if (value != null) { - - // add new content - if (selectedTab.View != null) { - contentView.Add (selectedTab.View); - } + if (value != null) { + // add new content + if (_selectedTab.View != null) { + _contentView.Add (_selectedTab.View); } + } - EnsureSelectedTabIsVisible (); + EnsureSelectedTabIsVisible (); - if (old != value) { - OnSelectedTabChanged (old, value); + if (old != value) { + if (old?.HasFocus == true) { + SelectedTab.SetFocus (); } - + OnSelectedTabChanged (old, value); } } + } - /// - /// Render choices for how to display tabs. After making changes, call - /// - /// - public TabStyle Style { get; set; } = new TabStyle (); + /// + /// Render choices for how to display tabs. After making changes, call . + /// + /// + public TabStyle Style { get; set; } = new TabStyle (); - /// - /// Initializes a class using layout. - /// - public TabView () : base () - { - CanFocus = true; - contentView = new View (); - tabsBar = new TabRowView (this); + /// + /// Initializes a class using layout. + /// + public TabView () : base () + { + CanFocus = true; + _tabsBar = new TabRowView (this); + _contentView = new View (); - ApplyStyleChanges (); + ApplyStyleChanges (); - base.Add (tabsBar); - base.Add (contentView); + base.Add (_tabsBar); + base.Add (_contentView); - // Things this view knows how to do - AddCommand (Command.Left, () => { SwitchTabBy (-1); return true; }); - AddCommand (Command.Right, () => { SwitchTabBy (1); return true; }); - AddCommand (Command.LeftHome, () => { SelectedTab = Tabs.FirstOrDefault (); return true; }); - AddCommand (Command.RightEnd, () => { SelectedTab = Tabs.LastOrDefault (); return true; }); + // Things this view knows how to do + AddCommand (Command.Left, () => { SwitchTabBy (-1); return true; }); + AddCommand (Command.Right, () => { SwitchTabBy (1); return true; }); + AddCommand (Command.LeftHome, () => { TabScrollOffset = 0; SelectedTab = Tabs.FirstOrDefault (); return true; }); + AddCommand (Command.RightEnd, () => { TabScrollOffset = Tabs.Count - 1; SelectedTab = Tabs.LastOrDefault (); return true; }); + AddCommand (Command.NextView, () => { _contentView.SetFocus (); return true; }); + AddCommand (Command.PreviousView, () => { SuperView?.FocusPrev (); return true; }); + AddCommand (Command.PageDown, () => { TabScrollOffset += _tabLocations.Length; SelectedTab = Tabs.ElementAt (TabScrollOffset); return true; }); + AddCommand (Command.PageUp, () => { TabScrollOffset -= _tabLocations.Length; SelectedTab = Tabs.ElementAt (TabScrollOffset); return true; }); - // Default keybindings for this view - KeyBindings.Add (KeyCode.CursorLeft, Command.Left); - KeyBindings.Add (KeyCode.CursorRight, Command.Right); - KeyBindings.Add (KeyCode.Home, Command.LeftHome); - KeyBindings.Add (KeyCode.End, Command.RightEnd); + // Default keybindings for this view + KeyBindings.Add (KeyCode.CursorLeft, Command.Left); + KeyBindings.Add (KeyCode.CursorRight, Command.Right); + KeyBindings.Add (KeyCode.Home, Command.LeftHome); + KeyBindings.Add (KeyCode.End, Command.RightEnd); + KeyBindings.Add (KeyCode.CursorDown, Command.NextView); + KeyBindings.Add (KeyCode.CursorUp, Command.PreviousView); + KeyBindings.Add (KeyCode.PageDown, Command.PageDown); + KeyBindings.Add (KeyCode.PageUp, Command.PageUp); + } + + /// + /// Updates the control to use the latest state settings in . + /// This can change the size of the client area of the tab (for rendering the + /// selected tab's content). This method includes a call + /// to . + /// + public void ApplyStyleChanges () + { + _contentView.BorderStyle = Style.ShowBorder ? LineStyle.Single : LineStyle.None; + _contentView.Width = Dim.Fill (); + + if (Style.TabsOnBottom) { + // Tabs are along the bottom so just dodge the border + if (Style.ShowBorder) { + _contentView.Border.Thickness = new Thickness (1, 1, 1, 0); + } + + _contentView.Y = 0; + + var tabHeight = GetTabHeight (false); + + // Fill client area leaving space at bottom for tabs + _contentView.Height = Dim.Fill (tabHeight); + + _tabsBar.Height = tabHeight; + + _tabsBar.Y = Pos.Bottom (_contentView); + + } else { + + // Tabs are along the top + if (Style.ShowBorder) { + _contentView.Border.Thickness = new Thickness (1, 0, 1, 1); + } + + _tabsBar.Y = 0; + + var tabHeight = GetTabHeight (true); + + //move content down to make space for tabs + _contentView.Y = Pos.Bottom (_tabsBar); + + // Fill client area leaving space at bottom for border + _contentView.Height = Dim.Fill (); + + // The top tab should be 2 or 3 rows high and on the top + + _tabsBar.Height = tabHeight; + + // Should be able to just use 0 but switching between top/bottom tabs repeatedly breaks in ValidatePosDim if just using the absolute value 0 + } + if (IsInitialized) { + LayoutSubviews (); + } + SetNeedsDisplay (); + } + + /// + public override void OnDrawContent (Rect contentArea) + { + Driver.SetAttribute (GetNormalColor ()); + + if (Tabs.Any ()) { + var savedClip = ClipToBounds (); + _tabsBar.OnDrawContent (contentArea); + _contentView.SetNeedsDisplay (); + _contentView.Draw (); + Driver.Clip = savedClip; + } + } + + /// + public override void OnDrawContentComplete (Rect contentArea) + { + _tabsBar.OnDrawContentComplete (contentArea); + } + + /// + /// Disposes the control and all . + /// + /// + protected override void Dispose (bool disposing) + { + base.Dispose (disposing); + + // The selected tab will automatically be disposed but + // any tabs not visible will need to be manually disposed + + foreach (var tab in Tabs) { + if (!Equals (SelectedTab, tab)) { + tab.View?.Dispose (); + } + } + } + + /// + /// Raises the event. + /// + protected virtual void OnSelectedTabChanged (Tab oldTab, Tab newTab) + { + + SelectedTabChanged?.Invoke (this, new TabChangedEventArgs (oldTab, newTab)); + } + + /// + /// Changes the by the given . + /// Positive for right, negative for left. If no tab is currently selected then + /// the first tab will become selected. + /// + /// + public void SwitchTabBy (int amount) + { + if (Tabs.Count == 0) { + return; } - /// - /// Updates the control to use the latest state settings in . - /// This can change the size of the client area of the tab (for rendering the - /// selected tab's content). This method includes a call - /// to - /// - public void ApplyStyleChanges () - { - contentView.X = Style.ShowBorder ? 1 : 0; - contentView.Width = Dim.Fill (Style.ShowBorder ? 1 : 0); - - if (Style.TabsOnBottom) { - // Tabs are along the bottom so just dodge the border - contentView.Y = Style.ShowBorder ? 1 : 0; - - // Fill client area leaving space at bottom for tabs - contentView.Height = Dim.Fill (GetTabHeight (false)); - - var tabHeight = GetTabHeight (false); - tabsBar.Height = tabHeight; - - tabsBar.Y = Pos.Percent (100) - tabHeight; - - } else { - - // Tabs are along the top - - var tabHeight = GetTabHeight (true); - - //move content down to make space for tabs - contentView.Y = tabHeight; - - // Fill client area leaving space at bottom for border - contentView.Height = Dim.Fill (Style.ShowBorder ? 1 : 0); - - // The top tab should be 2 or 3 rows high and on the top - - tabsBar.Height = tabHeight; - - // Should be able to just use 0 but switching between top/bottom tabs repeatedly breaks in ValidatePosDim if just using the absolute value 0 - tabsBar.Y = Pos.Percent (0); - } - if (IsInitialized) { - LayoutSubviews (); - } + // if there is only one tab anyway or nothing is selected + if (Tabs.Count == 1 || SelectedTab == null) { + SelectedTab = Tabs.ElementAt (0); SetNeedsDisplay (); + return; } + var currentIdx = Tabs.IndexOf (SelectedTab); + + // Currently selected tab has vanished! + if (currentIdx == -1) { + SelectedTab = Tabs.ElementAt (0); + SetNeedsDisplay (); + return; + } + + var newIdx = Math.Max (0, Math.Min (currentIdx + amount, Tabs.Count - 1)); + + SelectedTab = _tabs [newIdx]; + SetNeedsDisplay (); + + EnsureSelectedTabIsVisible (); + } + + /// + /// Updates to be a valid index of . + /// + /// The value to validate. + /// Changes will not be immediately visible in the display until you call . + /// The valid for the given value. + public int EnsureValidScrollOffsets (int value) + { + return Math.Max (Math.Min (value, Tabs.Count - 1), 0); + } + + /// + /// Updates to ensure that is visible. + /// + public void EnsureSelectedTabIsVisible () + { + if (!IsInitialized || SelectedTab == null) { + return; + } + + // if current viewport does not include the selected tab + if (!CalculateViewport (Bounds).Any (r => Equals (SelectedTab, r.Tab))) { + + // Set scroll offset so the first tab rendered is the + TabScrollOffset = Math.Max (0, Tabs.IndexOf (SelectedTab)); + } + } + + /// + /// Returns the number of rows occupied by rendering the tabs, this depends + /// on and can be 0 (e.g. if + /// and you ask for ). + /// + /// True to measure the space required at the top of the control, + /// false to measure space at the bottom.. + /// + private int GetTabHeight (bool top) + { + if (top && Style.TabsOnBottom) { + return 0; + } + + if (!top && !Style.TabsOnBottom) { + return 0; + } + + return Style.ShowTopLine ? 3 : 2; + } + + private TabToRender [] _tabLocations; + private int _tabScrollOffset; + + /// + /// Returns which tabs to render at each x location. + /// + /// + private IEnumerable CalculateViewport (Rect bounds) + { + UnSetCurrentTabs (); + + int i = 1; + View prevTab = null; + + // Starting at the first or scrolled to tab + foreach (var tab in Tabs.Skip (TabScrollOffset)) { + + if (prevTab != null) { + tab.X = Pos.Right (prevTab); + } else { + tab.X = 0; + } + tab.Y = 0; + + // while there is space for the tab + var tabTextWidth = tab.DisplayText.EnumerateRunes ().Sum (c => c.GetColumns ()); + + string text = tab.DisplayText; + + // The maximum number of characters to use for the tab name as specified + // by the user (MaxTabTextWidth). But not more than the width of the view + // or we won't even be able to render a single tab! + var maxWidth = Math.Max (0, Math.Min (bounds.Width - 3, MaxTabTextWidth)); + + prevTab = tab; + + tab.Width = 2; + tab.Height = Style.ShowTopLine ? 3 : 2; + + // if tab view is width <= 3 don't render any tabs + if (maxWidth == 0) { + tab.Visible = true; + tab.MouseClick += Tab_MouseClick; + yield return new TabToRender (i, tab, string.Empty, Equals (SelectedTab, tab), 0); + break; + } + + if (tabTextWidth > maxWidth) { + text = tab.DisplayText.Substring (0, (int)maxWidth); + tabTextWidth = (int)maxWidth; + } + + tab.Width = Math.Max (tabTextWidth + 2, 1); + tab.Height = Style.ShowTopLine ? 3 : 2; + + // if there is not enough space for this tab + if (i + tabTextWidth >= bounds.Width) { + tab.Visible = false; + break; + } + + // there is enough space! + tab.Visible = true; + tab.MouseClick += Tab_MouseClick; + yield return new TabToRender (i, tab, text, Equals (SelectedTab, tab), tabTextWidth); + i += tabTextWidth + 1; + } + } + + private void UnSetCurrentTabs () + { + if (_tabLocations != null) { + foreach (var tabToRender in _tabLocations) { + tabToRender.Tab.MouseClick -= Tab_MouseClick; + tabToRender.Tab.Visible = false; + } + _tabLocations = null; + } + } + + private void Tab_MouseClick (object sender, MouseEventEventArgs e) + { + e.Handled = _tabsBar.MouseEvent (e.MouseEvent); + } + + /// + /// Adds the given to . + /// + /// + /// True to make the newly added Tab the . + public void AddTab (Tab tab, bool andSelect) + { + if (_tabs.Contains (tab)) { + return; + } + + _tabs.Add (tab); + _tabsBar.Add (tab); + + if (SelectedTab == null || andSelect) { + SelectedTab = tab; + + EnsureSelectedTabIsVisible (); + + tab.View?.SetFocus (); + } + + SetNeedsDisplay (); + } + + /// + /// Removes the given from . + /// Caller is responsible for disposing the tab's hosted + /// if appropriate. + /// + /// + public void RemoveTab (Tab tab) + { + if (tab == null || !_tabs.Contains (tab)) { + return; + } + + // what tab was selected before closing + var idx = _tabs.IndexOf (tab); + + _tabs.Remove (tab); + + // if the currently selected tab is no longer a member of Tabs + if (SelectedTab == null || !Tabs.Contains (SelectedTab)) { + // select the tab closest to the one that disappeared + var toSelect = Math.Max (idx - 1, 0); + + if (toSelect < Tabs.Count) { + SelectedTab = Tabs.ElementAt (toSelect); + } else { + SelectedTab = Tabs.LastOrDefault (); + } + + } + + EnsureSelectedTabIsVisible (); + SetNeedsDisplay (); + } + + private class TabToRender { + public int X { get; set; } + public Tab Tab { get; set; } + + /// + /// True if the tab that is being rendered is the selected one. + /// + /// + public bool IsSelected { get; set; } + public int Width { get; } + public string TextToRender { get; } + + public TabToRender (int x, Tab tab, string textToRender, bool isSelected, int width) + { + X = x; + Tab = tab; + IsSelected = isSelected; + Width = width; + TextToRender = textToRender; + } + } + + private class TabRowView : View { + readonly TabView _host; + View _rightScrollIndicator; + View _leftScrollIndicator; + + public TabRowView (TabView host) + { + this._host = host; + + CanFocus = true; + Height = 1; + Width = Dim.Fill (); + + _rightScrollIndicator = new View () { Id = "rightScrollIndicator", Width = 1, Height = 1, Visible = false, Text = CM.Glyphs.RightArrow.ToString () }; + _rightScrollIndicator.MouseClick += _host.Tab_MouseClick; + _leftScrollIndicator = new View () { Id = "leftScrollIndicator", Width = 1, Height = 1, Visible = false, Text = CM.Glyphs.LeftArrow.ToString () }; + _leftScrollIndicator.MouseClick += _host.Tab_MouseClick; + + Add (_rightScrollIndicator, _leftScrollIndicator); + } + + public override bool OnEnter (View view) + { + Driver.SetCursorVisibility (CursorVisibility.Invisible); + return base.OnEnter (view); + } - /// public override void OnDrawContent (Rect contentArea) { - Move (0, 0); + _host._tabLocations = _host.CalculateViewport (Bounds).ToArray (); + + // clear any old text + Clear (); + + RenderTabLine (); + + RenderUnderline (); Driver.SetAttribute (GetNormalColor ()); - - if (Style.ShowBorder) { - - // How much space do we need to leave at the bottom to show the tabs - int spaceAtBottom = Math.Max (0, GetTabHeight (false) - 1); - int startAtY = Math.Max (0, GetTabHeight (true) - 1); - - Border.DrawFrame (new Rect (0, startAtY, Bounds.Width, - Math.Max (Bounds.Height - spaceAtBottom - startAtY, 0)), false); - } - - if (Tabs.Any ()) { - tabsBar.OnDrawContent (contentArea); - contentView.SetNeedsDisplay (); - var savedClip = contentView.ClipToBounds (); - contentView.Draw (); - Driver.Clip = savedClip; - } } - /// - /// Disposes the control and all - /// - /// - protected override void Dispose (bool disposing) + public override void OnDrawContentComplete (Rect contentArea) { - base.Dispose (disposing); - - // The selected tab will automatically be disposed but - // any tabs not visible will need to be manually disposed - - foreach (var tab in Tabs) { - if (!Equals (SelectedTab, tab)) { - tab.View?.Dispose (); - } - - } - } - - /// - /// Raises the event - /// - protected virtual void OnSelectedTabChanged (Tab oldTab, Tab newTab) - { - - SelectedTabChanged?.Invoke (this, new TabChangedEventArgs (oldTab, newTab)); - } - - /// - /// Changes the by the given . - /// Positive for right, negative for left. If no tab is currently selected then - /// the first tab will become selected - /// - /// - public void SwitchTabBy (int amount) - { - if (Tabs.Count == 0) { + if (_host._tabLocations == null) { return; } - // if there is only one tab anyway or nothing is selected - if (Tabs.Count == 1 || SelectedTab == null) { - SelectedTab = Tabs.ElementAt (0); - SetNeedsDisplay (); - return; - } - - var currentIdx = Tabs.IndexOf (SelectedTab); - - // Currently selected tab has vanished! - if (currentIdx == -1) { - SelectedTab = Tabs.ElementAt (0); - SetNeedsDisplay (); - return; - } - - var newIdx = Math.Max (0, Math.Min (currentIdx + amount, Tabs.Count - 1)); - - SelectedTab = tabs [newIdx]; - SetNeedsDisplay (); - - EnsureSelectedTabIsVisible (); - } - - /// - /// Updates to be a valid index of - /// - /// Changes will not be immediately visible in the display until you call - public void EnsureValidScrollOffsets () - { - TabScrollOffset = Math.Max (Math.Min (TabScrollOffset, Tabs.Count - 1), 0); - } - - /// - /// Updates to ensure that is visible - /// - public void EnsureSelectedTabIsVisible () - { - if (!IsInitialized || SelectedTab == null) { - return; - } - - // if current viewport does not include the selected tab - if (!CalculateViewport (Bounds).Any (r => Equals (SelectedTab, r.Tab))) { - - // Set scroll offset so the first tab rendered is the - TabScrollOffset = Math.Max (0, Tabs.IndexOf (SelectedTab)); - } - } - - /// - /// Returns the number of rows occupied by rendering the tabs, this depends - /// on and can be 0 (e.g. if - /// and you ask for ). - /// - /// True to measure the space required at the top of the control, - /// false to measure space at the bottom - /// - private int GetTabHeight (bool top) - { - if (top && Style.TabsOnBottom) { - return 0; - } - - if (!top && !Style.TabsOnBottom) { - return 0; - } - - return Style.ShowTopLine ? 3 : 2; - } - - /// - /// Returns which tabs to render at each x location - /// - /// - private IEnumerable CalculateViewport (Rect bounds) - { - int i = 1; - - // Starting at the first or scrolled to tab - foreach (var tab in Tabs.Skip (TabScrollOffset)) { - - // while there is space for the tab - var tabTextWidth = tab.Text.EnumerateRunes ().Sum (c => c.GetColumns ()); - - string text = tab.Text; - - // The maximum number of characters to use for the tab name as specified - // by the user (MaxTabTextWidth). But not more than the width of the view - // or we won't even be able to render a single tab! - var maxWidth = Math.Max (0, Math.Min (bounds.Width - 3, MaxTabTextWidth)); - - // if tab view is width <= 3 don't render any tabs - if (maxWidth == 0) { - yield return new TabToRender (i, tab, string.Empty, Equals (SelectedTab, tab), 0); - break; - } - - if (tabTextWidth > maxWidth) { - text = tab.Text.Substring (0, (int)maxWidth); - tabTextWidth = (int)maxWidth; - } - - // if there is not enough space for this tab - if (i + tabTextWidth >= bounds.Width) { - break; - } - - // there is enough space! - yield return new TabToRender (i, tab, text, Equals (SelectedTab, tab), tabTextWidth); - i += tabTextWidth + 1; - } - } - - /// - /// Adds the given to - /// - /// - /// True to make the newly added Tab the - public void AddTab (Tab tab, bool andSelect) - { - if (tabs.Contains (tab)) { - return; - } - - tabs.Add (tab); - - if (SelectedTab == null || andSelect) { - SelectedTab = tab; - - EnsureSelectedTabIsVisible (); - - tab.View?.SetFocus (); - } - - SetNeedsDisplay (); - } - - /// - /// Removes the given from . - /// Caller is responsible for disposing the tab's hosted - /// if appropriate. - /// - /// - public void RemoveTab (Tab tab) - { - if (tab == null || !tabs.Contains (tab)) { - return; - } - - // what tab was selected before closing - var idx = tabs.IndexOf (tab); - - tabs.Remove (tab); - - // if the currently selected tab is no longer a member of Tabs - if (SelectedTab == null || !Tabs.Contains (SelectedTab)) { - // select the tab closest to the one that disappeared - var toSelect = Math.Max (idx - 1, 0); - - if (toSelect < Tabs.Count) { - SelectedTab = Tabs.ElementAt (toSelect); - } else { - SelectedTab = Tabs.LastOrDefault (); - } - - } - - EnsureSelectedTabIsVisible (); - SetNeedsDisplay (); - } - - private class TabToRender { - public int X { get; set; } - public Tab Tab { get; set; } - - /// - /// True if the tab that is being rendered is the selected one - /// - /// - public bool IsSelected { get; set; } - public int Width { get; } - public string TextToRender { get; } - - public TabToRender (int x, Tab tab, string textToRender, bool isSelected, int width) - { - X = x; - Tab = tab; - IsSelected = isSelected; - Width = width; - TextToRender = textToRender; - } - } - - private class TabRowView : View { - - readonly TabView host; - - public TabRowView (TabView host) - { - this.host = host; - - CanFocus = true; - Height = 1; - Width = Dim.Fill (); - } - - public override bool OnEnter (View view) - { - Driver.SetCursorVisibility (CursorVisibility.Invisible); - return base.OnEnter (view); - } - - public override void OnDrawContent (Rect contentArea) - { - var tabLocations = host.CalculateViewport (Bounds).ToArray (); - var width = Bounds.Width; - Driver.SetAttribute (GetNormalColor ()); - - if (host.Style.ShowTopLine) { - RenderOverline (tabLocations, width); - } - - RenderTabLine (tabLocations, width); - - RenderUnderline (tabLocations, width); - Driver.SetAttribute (GetNormalColor ()); - } - - /// - /// Renders the line of the tabs that does not adjoin the content - /// - /// - /// - private void RenderOverline (TabToRender [] tabLocations, int width) - { - // if tabs are on the bottom draw the side of the tab that doesn't border the content area at the bottom otherwise the top - int y = host.Style.TabsOnBottom ? 2 : 0; - - Move (0, y); - - var selected = tabLocations.FirstOrDefault (t => t.IsSelected); - - // Clear out everything - Driver.AddStr (new string (' ', width)); - - // Nothing is selected... odd but we are done - if (selected == null) { - return; - } - - Move (selected.X - 1, y); - Driver.AddRune (host.Style.TabsOnBottom ? CM.Glyphs.LLCorner : CM.Glyphs.ULCorner); - - for (int i = 0; i < selected.Width; i++) { - - if (selected.X + i > width) { - // we ran out of space horizontally - return; - } - - Driver.AddRune (CM.Glyphs.HLine); - } - - // Add the end of the selected tab - Driver.AddRune (host.Style.TabsOnBottom ? CM.Glyphs.LRCorner : CM.Glyphs.URCorner); - - } - - /// - /// Renders the line with the tab names in it - /// - /// - /// - private void RenderTabLine (TabToRender [] tabLocations, int width) - { - int y; - - if (host.Style.TabsOnBottom) { - - y = 1; - } else { - y = host.Style.ShowTopLine ? 1 : 0; - } - - - // clear any old text - Move (0, y); - Driver.AddStr (new string (' ', width)); - - foreach (var toRender in tabLocations) { - - if (toRender.IsSelected) { - Move (toRender.X - 1, y); - Driver.AddRune (CM.Glyphs.VLine); - } - - Move (toRender.X, y); - - // if tab is the selected one and focus is inside this control - if (toRender.IsSelected && host.HasFocus) { - - if (host.Focused == this) { - - // if focus is the tab bar ourself then show that they can switch tabs - Driver.SetAttribute (ColorScheme.HotFocus); - + var tabLocations = _host._tabLocations; + var selectedTab = -1; + + for (int i = 0; i < tabLocations.Length; i++) { + View tab = tabLocations [i].Tab; + var vts = tab.BoundsToScreen (tab.Bounds); + LineCanvas lc = new LineCanvas (); + var selectedOffset = _host.Style.ShowTopLine && tabLocations [i].IsSelected ? 0 : 1; + + if (tabLocations [i].IsSelected) { + selectedTab = i; + + if (i == 0 && _host.TabScrollOffset == 0) { + if (_host.Style.TabsOnBottom) { + // Upper left vertical line + lc.AddLine (new Point (vts.X - 1, vts.Y - 1), -1, Orientation.Vertical, tab.BorderStyle); } else { + // Lower left vertical line + lc.AddLine (new Point (vts.X - 1, vts.Bottom - selectedOffset), -1, Orientation.Vertical, tab.BorderStyle); + } + } else if (i > 0 && i <= tabLocations.Length - 1) { + if (_host.Style.TabsOnBottom) { + // URCorner + lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.X - 1, vts.Y - 1), -1, Orientation.Horizontal, tab.BorderStyle); + } else { + // LRCorner + lc.AddLine (new Point (vts.X - 1, vts.Bottom - selectedOffset), -1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.X - 1, vts.Bottom - selectedOffset), -1, Orientation.Horizontal, tab.BorderStyle); + } - // Focus is inside the tab - Driver.SetAttribute (ColorScheme.HotNormal); + if (_host.Style.ShowTopLine) { + if (_host.Style.TabsOnBottom) { + // Lower left tee + lc.AddLine (new Point (vts.X - 1, vts.Bottom), -1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.X - 1, vts.Bottom), 0, Orientation.Horizontal, tab.BorderStyle); + } else { + // Upper left tee + lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 0, Orientation.Horizontal, tab.BorderStyle); + } } } - Driver.AddStr (toRender.TextToRender); - Driver.SetAttribute (GetNormalColor ()); - - if (toRender.IsSelected) { - Driver.AddRune (CM.Glyphs.VLine); - } - } - } - - /// - /// Renders the line of the tab that adjoins the content of the tab - /// - /// - /// - private void RenderUnderline (TabToRender [] tabLocations, int width) - { - int y = GetUnderlineYPosition (); - - Move (0, y); - - // If host has no border then we need to draw the solid line first (then we draw gaps over the top) - if (!host.Style.ShowBorder) { - - for (int x = 0; x < width; x++) { - Driver.AddRune (CM.Glyphs.HLine); + if (i < tabLocations.Length - 1) { + if (_host.Style.ShowTopLine) { + if (_host.Style.TabsOnBottom) { + // Lower right tee + lc.AddLine (new Point (vts.Right, vts.Bottom), -1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.Right, vts.Bottom), 0, Orientation.Horizontal, tab.BorderStyle); + } else { + // Upper right tee + lc.AddLine (new Point (vts.Right, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.Right, vts.Y - 1), 0, Orientation.Horizontal, tab.BorderStyle); + } + } } - } - var selected = tabLocations.FirstOrDefault (t => t.IsSelected); + if (_host.Style.TabsOnBottom) { + //URCorner + lc.AddLine (new Point (vts.Right, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.Right, vts.Y - 1), 1, Orientation.Horizontal, tab.BorderStyle); + } else { + //LLCorner + lc.AddLine (new Point (vts.Right, vts.Bottom - selectedOffset), -1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.Right, vts.Bottom - selectedOffset), 1, Orientation.Horizontal, tab.BorderStyle); + } - if (selected == null) { - return; - } + } else if (selectedTab == -1) { + if (i == 0 && string.IsNullOrEmpty (tab.Text)) { + if (_host.Style.TabsOnBottom) { + if (_host.Style.ShowTopLine) { + // LLCorner + lc.AddLine (new Point (vts.X - 1, vts.Bottom), -1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.X - 1, vts.Bottom), 1, Orientation.Horizontal, tab.BorderStyle); + } + // ULCorner + lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 1, Orientation.Horizontal, tab.BorderStyle); + } else { + if (_host.Style.ShowTopLine) { + // ULCorner + lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 1, Orientation.Horizontal, tab.BorderStyle); + } + // LLCorner + lc.AddLine (new Point (vts.X - 1, vts.Bottom), -1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.X - 1, vts.Bottom), 1, Orientation.Horizontal, tab.BorderStyle); + } + } else if (i > 0) { + if (_host.Style.ShowTopLine || _host.Style.TabsOnBottom) { + // Upper left tee + lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 0, Orientation.Horizontal, tab.BorderStyle); + } - Move (selected.X - 1, y); + // Lower left tee + lc.AddLine (new Point (vts.X - 1, vts.Bottom), -1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.X - 1, vts.Bottom), 0, Orientation.Horizontal, tab.BorderStyle); - Driver.AddRune (selected.X == 1 ? CM.Glyphs.VLine : - (host.Style.TabsOnBottom ? CM.Glyphs.URCorner : CM.Glyphs.LRCorner)); + } + } else if (i < tabLocations.Length - 1) { + if (_host.Style.ShowTopLine) { + // Upper right tee + lc.AddLine (new Point (vts.Right, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.Right, vts.Y - 1), 0, Orientation.Horizontal, tab.BorderStyle); + } - Driver.AddStr (new string (' ', selected.Width)); - - Driver.AddRune (selected.X + selected.Width == width - 1 ? - CM.Glyphs.VLine : - (host.Style.TabsOnBottom ? CM.Glyphs.ULCorner : CM.Glyphs.LLCorner)); - - // draw scroll indicators - - // if there are more tabs to the left not visible - if (host.TabScrollOffset > 0) { - Move (0, y); - - // indicate that - Driver.AddRune (CM.Glyphs.LeftArrow); - } - - // if there are more tabs to the right not visible - if (ShouldDrawRightScrollIndicator (tabLocations)) { - Move (width - 1, y); - - // indicate that - Driver.AddRune (CM.Glyphs.RightArrow); - } - } - - private bool ShouldDrawRightScrollIndicator (TabToRender [] tabLocations) - { - return tabLocations.LastOrDefault ()?.Tab != host.Tabs.LastOrDefault (); - } - - private int GetUnderlineYPosition () - { - if (host.Style.TabsOnBottom) { - - return 0; - } else { - - return host.Style.ShowTopLine ? 2 : 1; - } - } - - public override bool MouseEvent (MouseEvent me) - { - var hit = ScreenToTab (me.X, me.Y); - - bool isClick = me.Flags.HasFlag (MouseFlags.Button1Clicked) || - me.Flags.HasFlag (MouseFlags.Button2Clicked) || - me.Flags.HasFlag (MouseFlags.Button3Clicked); - - if (isClick) { - host.OnTabClicked (new TabMouseEventArgs (hit, me)); - - // user canceled click - if (me.Handled) { - return true; + if (_host.Style.ShowTopLine || !_host.Style.TabsOnBottom) { + // Lower right tee + lc.AddLine (new Point (vts.Right, vts.Bottom), -1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.Right, vts.Bottom), 0, Orientation.Horizontal, tab.BorderStyle); + } else { + // Upper right tee + lc.AddLine (new Point (vts.Right, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.Right, vts.Y - 1), 0, Orientation.Horizontal, tab.BorderStyle); } } - if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && - !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) && - !me.Flags.HasFlag (MouseFlags.Button1TripleClicked)) - return false; - - if (!HasFocus && CanFocus) { - SetFocus (); - } - - if (me.Flags.HasFlag (MouseFlags.Button1Clicked) || - me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) || - me.Flags.HasFlag (MouseFlags.Button1TripleClicked)) { - - var scrollIndicatorHit = ScreenToScrollIndicator (me.X, me.Y); - - if (scrollIndicatorHit != 0) { - - host.SwitchTabBy (scrollIndicatorHit); - - SetNeedsDisplay (); - return true; - } - - if (hit != null) { - host.SelectedTab = hit; - SetNeedsDisplay (); - return true; + if (i == 0 && i != selectedTab && _host.TabScrollOffset == 0 && _host.Style.ShowBorder) { + if (_host.Style.TabsOnBottom) { + // Upper left vertical line + lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 0, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.X - 1, vts.Y - 1), 1, Orientation.Horizontal, tab.BorderStyle); + } else { + // Lower left vertical line + lc.AddLine (new Point (vts.X - 1, vts.Bottom), 0, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.X - 1, vts.Bottom), 1, Orientation.Horizontal, tab.BorderStyle); } } - return false; - } - - /// - /// Calculates whether scroll indicators are visible and if so whether the click - /// was on one of them. - /// - /// - /// - /// -1 for click in scroll left, 1 for scroll right or 0 for no hit - private int ScreenToScrollIndicator (int x, int y) - { - // scroll indicator is showing - if (host.TabScrollOffset > 0 && x == 0) { - - return y == GetUnderlineYPosition () ? -1 : 0; + if (i == tabLocations.Length - 1 && i != selectedTab) { + if (_host.Style.TabsOnBottom) { + // Upper right tee + lc.AddLine (new Point (vts.Right, vts.Y - 1), 1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.Right, vts.Y - 1), 0, Orientation.Horizontal, tab.BorderStyle); + } else { + // Lower right tee + lc.AddLine (new Point (vts.Right, vts.Bottom), -1, Orientation.Vertical, tab.BorderStyle); + lc.AddLine (new Point (vts.Right, vts.Bottom), 0, Orientation.Horizontal, tab.BorderStyle); + } } - // scroll indicator is showing - if (x == Bounds.Width - 1 && ShouldDrawRightScrollIndicator (host.CalculateViewport (Bounds).ToArray ())) { - - return y == GetUnderlineYPosition () ? 1 : 0; + if (i == tabLocations.Length - 1) { + var arrowOffset = 1; + var lastSelectedTab = !_host.Style.ShowTopLine && i == selectedTab ? 1 : _host.Style.TabsOnBottom ? 1 : 0; + var tabsBarVts = BoundsToScreen (Bounds); + var lineLength = tabsBarVts.Right - vts.Right; + // Right horizontal line + if (ShouldDrawRightScrollIndicator ()) { + if (lineLength - arrowOffset > 0) { + if (_host.Style.TabsOnBottom) { + lc.AddLine (new Point (vts.Right, vts.Y - lastSelectedTab), lineLength - arrowOffset, Orientation.Horizontal, tab.BorderStyle); + } else { + lc.AddLine (new Point (vts.Right, vts.Bottom - lastSelectedTab), lineLength - arrowOffset, Orientation.Horizontal, tab.BorderStyle); + } + } + } else { + if (_host.Style.TabsOnBottom) { + lc.AddLine (new Point (vts.Right, vts.Y - lastSelectedTab), lineLength, Orientation.Horizontal, tab.BorderStyle); + } else { + lc.AddLine (new Point (vts.Right, vts.Bottom - lastSelectedTab), lineLength, Orientation.Horizontal, tab.BorderStyle); + } + if (_host.Style.ShowBorder) { + if (_host.Style.TabsOnBottom) { + // More LRCorner + lc.AddLine (new Point (tabsBarVts.Right - 1, vts.Y - lastSelectedTab), -1, Orientation.Vertical, tab.BorderStyle); + } else { + // More URCorner + lc.AddLine (new Point (tabsBarVts.Right - 1, vts.Bottom - lastSelectedTab), 1, Orientation.Vertical, tab.BorderStyle); + } + } + } } - return 0; - } - - /// - /// Translates the client coordinates of a click into a tab when the click is on top of a tab - /// - /// - /// - /// - public Tab ScreenToTab (int x, int y) - { - var tabs = host.CalculateViewport (Bounds); - - return tabs.LastOrDefault (t => x >= t.X && x < t.X + t.Width)?.Tab; + tab.LineCanvas.Merge (lc); + tab.OnRenderLineCanvas (); } } /// - /// Raises the event. + /// Renders the line with the tab names in it. /// - /// - protected virtual private void OnTabClicked (TabMouseEventArgs tabMouseEventArgs) + private void RenderTabLine () { - TabClicked?.Invoke (this, tabMouseEventArgs); + var tabLocations = _host._tabLocations; + int y; + + if (_host.Style.TabsOnBottom) { + + y = 1; + } else { + y = _host.Style.ShowTopLine ? 1 : 0; + } + + View selected = null; + var topLine = _host.Style.ShowTopLine ? 1 : 0; + var width = Bounds.Width; + + foreach (var toRender in tabLocations) { + var tab = toRender.Tab; + + if (toRender.IsSelected) { + selected = tab; + if (_host.Style.TabsOnBottom) { + tab.Border.Thickness = new Thickness (1, 0, 1, topLine); + tab.Margin.Thickness = new Thickness (0, 1, 0, 0); + } else { + tab.Border.Thickness = new Thickness (1, topLine, 1, 0); + tab.Margin.Thickness = new Thickness (0, 0, 0, topLine); + } + } else if (selected == null) { + if (_host.Style.TabsOnBottom) { + tab.Border.Thickness = new Thickness (1, 1, 0, topLine); + tab.Margin.Thickness = new Thickness (0, 0, 0, 0); + } else { + tab.Border.Thickness = new Thickness (1, topLine, 0, 1); + tab.Margin.Thickness = new Thickness (0, 0, 0, 0); + } + tab.Width = Math.Max (tab.Width.Anchor (0) - 1, 1); + } else { + if (_host.Style.TabsOnBottom) { + tab.Border.Thickness = new Thickness (0, 1, 1, topLine); + tab.Margin.Thickness = new Thickness (0, 0, 0, 0); + } else { + tab.Border.Thickness = new Thickness (0, topLine, 1, 1); + tab.Margin.Thickness = new Thickness (0, 0, 0, 0); + } + tab.Width = Math.Max (tab.Width.Anchor (0) - 1, 1); + } + + tab.Text = toRender.TextToRender; + + LayoutSubviews (); + + tab.OnDrawAdornments (); + + var prevAttr = Driver.GetAttribute (); + + // if tab is the selected one and focus is inside this control + if (toRender.IsSelected && _host.HasFocus) { + + if (_host.Focused == this) { + + // if focus is the tab bar ourself then show that they can switch tabs + prevAttr = ColorScheme.HotFocus; + } else { + + // Focus is inside the tab + prevAttr = ColorScheme.HotNormal; + } + } + tab.TextFormatter.Draw (tab.BoundsToScreen (tab.Bounds), prevAttr, ColorScheme.HotNormal); + + tab.OnRenderLineCanvas (); + + Driver.SetAttribute (GetNormalColor ()); + } + } + + /// + /// Renders the line of the tab that adjoins the content of the tab. + /// + private void RenderUnderline () + { + int y = GetUnderlineYPosition (); + + var selected = _host._tabLocations.FirstOrDefault (t => t.IsSelected); + + if (selected == null) { + return; + } + + // draw scroll indicators + + // if there are more tabs to the left not visible + if (_host.TabScrollOffset > 0) { + _leftScrollIndicator.X = 0; + _leftScrollIndicator.Y = y; + + // indicate that + _leftScrollIndicator.Visible = true; + // Ensures this is clicked instead of the first tab + BringSubviewToFront (_leftScrollIndicator); + _leftScrollIndicator.Draw (); + } else { + _leftScrollIndicator.Visible = false; + } + + // if there are more tabs to the right not visible + if (ShouldDrawRightScrollIndicator ()) { + _rightScrollIndicator.X = Bounds.Width - 1; + _rightScrollIndicator.Y = y; + + // indicate that + _rightScrollIndicator.Visible = true; + // Ensures this is clicked instead of the last tab if under this + BringSubviewToFront (_rightScrollIndicator); + _rightScrollIndicator.Draw (); + } else { + _rightScrollIndicator.Visible = false; + } + } + + private bool ShouldDrawRightScrollIndicator () + { + return _host._tabLocations.LastOrDefault ()?.Tab != _host.Tabs.LastOrDefault (); + } + + private int GetUnderlineYPosition () + { + if (_host.Style.TabsOnBottom) { + + return 0; + } else { + + return _host.Style.ShowTopLine ? 2 : 1; + } + } + + public override bool MouseEvent (MouseEvent me) + { + var hit = me.View is Tab ? (Tab)me.View : null; + + bool isClick = me.Flags.HasFlag (MouseFlags.Button1Clicked) || + me.Flags.HasFlag (MouseFlags.Button2Clicked) || + me.Flags.HasFlag (MouseFlags.Button3Clicked); + + if (isClick) { + _host.OnTabClicked (new TabMouseEventArgs (hit, me)); + + // user canceled click + if (me.Handled) { + return true; + } + } + + if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && + !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) && + !me.Flags.HasFlag (MouseFlags.Button1TripleClicked)) + return false; + + if (!HasFocus && CanFocus) { + SetFocus (); + } + + if (me.Flags.HasFlag (MouseFlags.Button1Clicked) || + me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) || + me.Flags.HasFlag (MouseFlags.Button1TripleClicked)) { + + int scrollIndicatorHit = 0; + if (me.View != null && me.View.Id == "rightScrollIndicator") { + scrollIndicatorHit = 1; + } else if (me.View != null && me.View.Id == "leftScrollIndicator") { + scrollIndicatorHit = -1; + } + + if (scrollIndicatorHit != 0) { + + _host.SwitchTabBy (scrollIndicatorHit); + + SetNeedsDisplay (); + return true; + } + + if (hit != null) { + _host.SelectedTab = hit; + SetNeedsDisplay (); + return true; + } + } + + return false; } } + + /// + /// Raises the event. + /// + /// + protected virtual private void OnTabClicked (TabMouseEventArgs tabMouseEventArgs) + { + TabClicked?.Invoke (this, tabMouseEventArgs); + } } diff --git a/UICatalog/Scenarios/Adornments.cs b/UICatalog/Scenarios/Adornments.cs index f6424932c..8f6a9137d 100644 --- a/UICatalog/Scenarios/Adornments.cs +++ b/UICatalog/Scenarios/Adornments.cs @@ -5,8 +5,7 @@ using Terminal.Gui; namespace UICatalog.Scenarios; [ScenarioMetadata ("Adornments Demo", "Demonstrates Margin, Border, and Padding on Views.")] -[ScenarioCategory ("Layout")] -[ScenarioCategory ("Borders")] +[ScenarioCategory ("Layout"), ScenarioCategory ("Borders")] public class Adornments : Scenario { public override void Init () @@ -193,7 +192,7 @@ public class Adornments : Scenario { _bottomEdit.TextChanging += Edit_TextChanging; Add (_bottomEdit); - var copyTop = new Button ("Copy Top") { + var copyTop = new Button ("Cop_y Top") { X = Pos.Center () + 1, Y = Pos.Bottom (_bottomEdit) }; @@ -370,7 +369,7 @@ public class Adornments : Scenario { Add (_paddingEditor); _diagCheckBox = new CheckBox { - Text = "Diagnostics", + Text = "_Diagnostics", Y = Pos.Bottom (_paddingEditor) }; _diagCheckBox.Toggled += (s, e) => { diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs index eb4b15c0e..323dc3475 100644 --- a/UICatalog/Scenarios/AllViewsTester.cs +++ b/UICatalog/Scenarios/AllViewsTester.cs @@ -7,35 +7,36 @@ using Terminal.Gui; namespace UICatalog.Scenarios; [ScenarioMetadata ("All Views Tester", "Provides a test UI for all classes derived from View.")] -[ScenarioCategory ("Layout")] -[ScenarioCategory ("Tests")] -[ScenarioCategory ("Top Level Windows")] +[ScenarioCategory ("Layout")] [ScenarioCategory ("Tests")] [ScenarioCategory ("Top Level Windows")] public class AllViewsTester : Scenario { - FrameView _leftPane; ListView _classListView; + CheckBox _computedCheckBox; + View _curView; + readonly List _dimNames = new () { "Factor", "Fill", "Absolute" }; FrameView _hostPane; + RadioGroup _hRadioGroup; + TextField _hText; + int _hVal; + FrameView _leftPane; + FrameView _locationFrame; - Dictionary _viewClasses; - View _curView = null; + // TODO: This is missing some + readonly List _posNames = new () { "Factor", "AnchorEnd", "Center", "Absolute" }; // Settings FrameView _settingsPane; - CheckBox _computedCheckBox; - FrameView _locationFrame; - RadioGroup _xRadioGroup; - TextField _xText; - int _xVal = 0; - RadioGroup _yRadioGroup; - TextField _yText; - int _yVal = 0; - FrameView _sizeFrame; + + Dictionary _viewClasses; RadioGroup _wRadioGroup; TextField _wText; - int _wVal = 0; - RadioGroup _hRadioGroup; - TextField _hText; - int _hVal = 0; + int _wVal; + RadioGroup _xRadioGroup; + TextField _xText; + int _xVal; + RadioGroup _yRadioGroup; + TextField _yText; + int _yVal; public override void Init () { @@ -62,9 +63,9 @@ public class AllViewsTester : Scenario { Application.Top.Add (statusBar); _viewClasses = GetAllViewClassesCollection () - .OrderBy (t => t.Name) - .Select (t => new KeyValuePair (t.Name, t)) - .ToDictionary (t => t.Key, t => t.Value); + .OrderBy (t => t.Name) + .Select (t => new KeyValuePair (t.Name, t)) + .ToDictionary (t => t.Key, t => t.Value); _leftPane = new FrameView ("Classes") { X = 0, @@ -78,8 +79,8 @@ public class AllViewsTester : Scenario { _classListView = new ListView (_viewClasses.Keys.ToList ()) { X = 0, Y = 0, - Width = Dim.Fill (0), - Height = Dim.Fill (0), + Width = Dim.Fill (), + Height = Dim.Fill (), AllowsMarking = false, ColorScheme = Colors.ColorSchemes ["TopLevel"], SelectedItem = 0 @@ -108,7 +109,7 @@ public class AllViewsTester : Scenario { CanFocus = false, ColorScheme = Colors.ColorSchemes ["TopLevel"] }; - _computedCheckBox = new CheckBox ("Computed Layout", true) { X = 0, Y = 0 }; + _computedCheckBox = new CheckBox ("_Computed Layout", true) { X = 0, Y = 0 }; _computedCheckBox.Toggled += (s, e) => { if (_curView != null) { _hostPane.LayoutSubviews (); @@ -116,7 +117,7 @@ public class AllViewsTester : Scenario { }; _settingsPane.Add (_computedCheckBox); - string [] radioItems = new string [] { "Percent(x)", "AnchorEnd(x)", "Center", "At(x)" }; + string [] radioItems = { "_Percent(x)", "_AnchorEnd(x)", "_Center", "A_t(x)" }; _locationFrame = new FrameView ("Location (Pos)") { X = Pos.Left (_computedCheckBox), Y = Pos.Bottom (_computedCheckBox), @@ -125,7 +126,7 @@ public class AllViewsTester : Scenario { }; _settingsPane.Add (_locationFrame); - var label = new Label ("x:") { X = 0, Y = 0 }; + var label = new Label ("X:") { X = 0, Y = 0 }; _locationFrame.Add (label); _xRadioGroup = new RadioGroup (radioItems) { X = 0, @@ -143,8 +144,8 @@ public class AllViewsTester : Scenario { _locationFrame.Add (_xRadioGroup); - radioItems = new string [] { "Percent(y)", "AnchorEnd(y)", "Center", "At(y)" }; - label = new Label ("y:") { X = Pos.Right (_xRadioGroup) + 1, Y = 0 }; + radioItems = new [] { "P_ercent(y)", "A_nchorEnd(y)", "C_enter", "At(_y)" }; + label = new Label ("Y:") { X = Pos.Right (_xRadioGroup) + 1, Y = 0 }; _locationFrame.Add (label); _yText = new TextField ($"{_yVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 }; _yText.TextChanged += (s, args) => { @@ -168,8 +169,8 @@ public class AllViewsTester : Scenario { Width = 40 }; - radioItems = new string [] { "Percent(width)", "Fill(width)", "Sized(width)" }; - label = new Label ("width:") { X = 0, Y = 0 }; + radioItems = new [] { "_Percent(width)", "_Fill(width)", "_Sized(width)" }; + label = new Label ("Width:") { X = 0, Y = 0 }; _sizeFrame.Add (label); _wRadioGroup = new RadioGroup (radioItems) { X = 0, @@ -194,8 +195,8 @@ public class AllViewsTester : Scenario { _sizeFrame.Add (_wText); _sizeFrame.Add (_wRadioGroup); - radioItems = new string [] { "Percent(height)", "Fill(height)", "Sized(height)" }; - label = new Label ("height:") { X = Pos.Right (_wRadioGroup) + 1, Y = 0 }; + radioItems = new [] { "P_ercent(height)", "F_ill(height)", "Si_zed(height)" }; + label = new Label ("Height:") { X = Pos.Right (_wRadioGroup) + 1, Y = 0 }; _sizeFrame.Add (label); _hText = new TextField ($"{_hVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 }; _hText.TextChanged += (s, args) => { @@ -278,27 +279,21 @@ public class AllViewsTester : Scenario { }; } catch (Exception e) { MessageBox.ErrorQuery ("Exception", e.Message, "Ok"); - } finally { - //view.LayoutStyle = layout; } UpdateTitle (view); } - // TODO: This is missing some - List _posNames = new () { "Factor", "AnchorEnd", "Center", "Absolute" }; - List _dimNames = new () { "Factor", "Fill", "Absolute" }; - void UpdateSettings (View view) { - string x = view.X.ToString (); - string y = view.Y.ToString (); + var x = view.X.ToString (); + var y = view.Y.ToString (); _xRadioGroup.SelectedItem = _posNames.IndexOf (_posNames.Where (s => x.Contains (s)).First ()); _yRadioGroup.SelectedItem = _posNames.IndexOf (_posNames.Where (s => y.Contains (s)).First ()); _xText.Text = $"{view.Frame.X}"; _yText.Text = $"{view.Frame.Y}"; - string w = view.Width.ToString (); - string h = view.Height.ToString (); + var w = view.Width.ToString (); + 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}"; @@ -311,7 +306,7 @@ public class AllViewsTester : Scenario { { var types = new List (); foreach (var type in typeof (View).Assembly.GetTypes () - .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsPublic && myType.IsSubclassOf (typeof (View)))) { + .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsPublic && myType.IsSubclassOf (typeof (View)))) { types.Add (type); } types.Add (typeof (View)); @@ -364,7 +359,7 @@ public class AllViewsTester : Scenario { // If the view supports a Source property, set it so we have something to look at if (view != null && view.GetType ().GetProperty ("Source") != null && view.GetType ().GetProperty ("Source").PropertyType == typeof (IListDataSource)) { - var source = new ListWrapper (new List () { "Test Text #1", "Test Text #2", "Test Text #3" }); + var source = new ListWrapper (new List { "Test Text #1", "Test Text #2", "Test Text #3" }); view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source }); } diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs index 6966cd857..46666e6b9 100644 --- a/UICatalog/Scenarios/Buttons.cs +++ b/UICatalog/Scenarios/Buttons.cs @@ -3,8 +3,7 @@ using Terminal.Gui; namespace UICatalog.Scenarios; [ScenarioMetadata (Name: "Buttons", Description: "Demonstrates all sorts of Buttons.")] -[ScenarioCategory ("Controls")] -[ScenarioCategory ("Layout")] +[ScenarioCategory ("Controls"), ScenarioCategory ("Layout")] public class Buttons : Scenario { public override void Setup () { @@ -32,7 +31,7 @@ public class Buttons : Scenario { defaultButton.Clicked += (s, e) => Application.RequestStop (); Win.Add (defaultButton); - var swapButton = new Button (50, 0, "Swap Default (Absolute Layout)"); + var swapButton = new Button (50, 0, "S_wap Default (Absolute Layout)"); swapButton.Clicked += (s, e) => { defaultButton.IsDefault = !defaultButton.IsDefault; swapButton.IsDefault = !swapButton.IsDefault; @@ -58,7 +57,7 @@ public class Buttons : Scenario { //With this method there is no need to call Application.TopReady += () => Application.TopRedraw (Top.Bounds); var x = Pos.Right (colorButtonsLabel) + 2; foreach (var colorScheme in Colors.ColorSchemes) { - var colorButton = new Button ($"{colorScheme.Key}") { + var colorButton = new Button ($"_{colorScheme.Key}") { ColorScheme = colorScheme.Value, //X = Pos.Right (prev) + 2, X = x, @@ -119,7 +118,7 @@ public class Buttons : Scenario { Win.Add (computedFrame); // Demonstrates how changing the View.Frame property can move Views - var moveBtn = new Button ("Move This \u263b Button _via Pos") { + var moveBtn = new Button ("Move This \u263b Button v_ia Pos") { X = 0, Y = Pos.Center () - 1, Width = 30, @@ -163,7 +162,7 @@ public class Buttons : Scenario { absoluteFrame.Add (moveBtnA); // Demonstrates how changing the View.Frame property can SIZE Views (#583) - var sizeBtnA = new Button (0, 2, " ~  s  gui.cs   master ↑10 = Со_хранить") { + var sizeBtnA = new Button (0, 2, " ~  s  gui.cs   master ↑_10 = Сохранить") { ColorScheme = Colors.ColorSchemes ["Error"], }; sizeBtnA.Clicked += (s, e) => { diff --git a/UICatalog/Scenarios/Dialogs.cs b/UICatalog/Scenarios/Dialogs.cs index 2e8e6654c..584bf410b 100644 --- a/UICatalog/Scenarios/Dialogs.cs +++ b/UICatalog/Scenarios/Dialogs.cs @@ -109,14 +109,14 @@ namespace UICatalog.Scenarios { }; frame.Add (label); - var styleRadioGroup = new RadioGroup (new string [] { "Center", "Justify", "Left", "Right" }) { + var styleRadioGroup = new RadioGroup (new string [] { "_Center", "_Justify", "_Left", "_Right" }) { X = Pos.Right (label) + 1, Y = Pos.Top (label), }; frame.Add (styleRadioGroup); frame.ValidatePosDim = true; - void Top_Loaded (object sender, EventArgs args) + void Top_LayoutComplete (object sender, EventArgs args) { frame.Height = widthEdit.Frame.Height + @@ -124,10 +124,9 @@ namespace UICatalog.Scenarios { titleEdit.Frame.Height + numButtonsEdit.Frame.Height + glyphsNotWords.Frame.Height + - styleRadioGroup.Frame.Height; - Application.Top.Loaded -= Top_Loaded; + styleRadioGroup.Frame.Height + frame.GetAdornmentsThickness().Vertical; } - Application.Top.Loaded += Top_Loaded; + Application.Top.LayoutComplete += Top_LayoutComplete; Win.Add (frame); @@ -151,7 +150,7 @@ namespace UICatalog.Scenarios { // true: var btnText = new [] { "0", "\u2780", "➁", "\u2783", "\u2784", "\u2785", "\u2786", "\u2787", "\u2788", "\u2789" }; // \u2781 is ➁ dingbats \ufb70 is - var showDialogButton = new Button ("Show Dialog") { + var showDialogButton = new Button ("_Show Dialog") { X = Pos.Center (), Y = Pos.Bottom (frame) + 2, IsDefault = true, diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index b13784811..bb8d3e90c 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -751,9 +751,9 @@ namespace UICatalog.Scenarios { Height = Dim.Fill () }; - _tabView.AddTab (new Tab ("Find", FindTab ()), isFind); + _tabView.AddTab (new Tab () { DisplayText = "Find", View = FindTab () }, isFind); var replace = ReplaceTab (); - _tabView.AddTab (new Tab ("Replace", replace), !isFind); + _tabView.AddTab (new Tab () { DisplayText = "Replace", View = replace }, !isFind); _tabView.SelectedTabChanged += (s, e) => _tabView.SelectedTab.View.FocusFirst (); _winDialog.Add (_tabView); diff --git a/UICatalog/Scenarios/MessageBoxes.cs b/UICatalog/Scenarios/MessageBoxes.cs index fe8feb5e0..f4fc75dd9 100644 --- a/UICatalog/Scenarios/MessageBoxes.cs +++ b/UICatalog/Scenarios/MessageBoxes.cs @@ -14,7 +14,6 @@ namespace UICatalog.Scenarios { X = Pos.Center (), Y = 1, Width = Dim.Percent (75), - Height = Dim.Auto () }; Win.Add (frame); @@ -140,14 +139,14 @@ namespace UICatalog.Scenarios { }; frame.Add (styleRadioGroup); - var ckbWrapMessage = new CheckBox ("Wrap Message", true) { + var ckbWrapMessage = new CheckBox ("_Wrap Message", true) { X = Pos.Right (label) + 1, - Y = Pos.Top (label) + 3 + Y = Pos.Bottom (styleRadioGroup) }; frame.Add (ckbWrapMessage); frame.ValidatePosDim = true; - void Top_Loaded (object sender, EventArgs args) + void Top_LayoutComplete (object sender, EventArgs args) { frame.Height = widthEdit.Frame.Height + @@ -157,22 +156,22 @@ namespace UICatalog.Scenarios { numButtonsEdit.Frame.Height + defaultButtonEdit.Frame.Height + styleRadioGroup.Frame.Height + - 2 + - ckbWrapMessage.Frame.Height; - Application.Top.Loaded -= Top_Loaded; + ckbWrapMessage.Frame.Height + + frame.GetAdornmentsThickness ().Vertical; + Application.Top.Loaded -= Top_LayoutComplete; } - //Application.Top.Loaded += Top_Loaded; + Application.Top.LayoutComplete += Top_LayoutComplete; label = new Label ("Button Pressed:") { X = Pos.Center (), - Y = Pos.Bottom (frame) + 4, + Y = Pos.Bottom (frame) + 2, Height = 1, TextAlignment = Terminal.Gui.TextAlignment.Right, }; Win.Add (label); var buttonPressedLabel = new Label (" ") { X = Pos.Center (), - Y = Pos.Bottom (frame) + 5, + Y = Pos.Bottom (label) + 1, Width = 25, Height = 1, ColorScheme = Colors.ColorSchemes ["Error"], @@ -181,12 +180,12 @@ namespace UICatalog.Scenarios { //var btnText = new [] { "_Zero", "_One", "T_wo", "_Three", "_Four", "Fi_ve", "Si_x", "_Seven", "_Eight", "_Nine" }; - var showMessageBoxButton = new Button ("Show MessageBox") { + var showMessageBoxButton = new Button ("_Show MessageBox") { X = Pos.Center (), Y = Pos.Bottom (frame) + 2, IsDefault = true, }; - showMessageBoxButton.Clicked += (s,e) => { + showMessageBoxButton.Clicked += (s, e) => { try { int width = int.Parse (widthEdit.Text); int height = int.Parse (heightEdit.Text); diff --git a/UICatalog/Scenarios/Notepad.cs b/UICatalog/Scenarios/Notepad.cs index ab585945d..09cfca57b 100644 --- a/UICatalog/Scenarios/Notepad.cs +++ b/UICatalog/Scenarios/Notepad.cs @@ -275,7 +275,14 @@ public class Notepad : Scenario { /// File that was read or null if a new blank document private void Open (FileInfo fileInfo, string tabName) { - var tab = new OpenedFile (_focusedTabView, tabName, fileInfo); + var tab = new OpenedFile () { + DisplayText = tabName, + File = fileInfo + }; + tab.View = tab.CreateTextView (fileInfo); + tab.SavedText = tab.View.Text; + tab.RegisterTextViewEvents (_focusedTabView); + _focusedTabView.AddTab (tab, true); } @@ -336,15 +343,7 @@ public class Notepad : Scenario { public bool UnsavedChanges => !string.Equals (SavedText, View.Text); - public OpenedFile (TabView parent, string name, FileInfo file) - : base (name, CreateTextView (file)) - { - File = file; - SavedText = View.Text; - RegisterTextViewEvents (parent); - } - - private void RegisterTextViewEvents (TabView parent) + public void RegisterTextViewEvents (TabView parent) { var textView = (TextView)View; // when user makes changes rename tab to indicate unsaved @@ -370,7 +369,7 @@ public class Notepad : Scenario { }; } - private static View CreateTextView (FileInfo file) + public View CreateTextView (FileInfo file) { string initialText = string.Empty; if (file != null && file.Exists) { @@ -390,7 +389,10 @@ public class Notepad : Scenario { public OpenedFile CloneTo (TabView other) { - var newTab = new OpenedFile (other, base.Text.ToString (), File); + var newTab = new OpenedFile () { DisplayText = base.Text, File = File }; + newTab.View = newTab.CreateTextView (newTab.File); + newTab.SavedText = newTab.View.Text; + newTab.RegisterTextViewEvents (other); other.AddTab (newTab, true); return newTab; } diff --git a/UICatalog/Scenarios/TabViewExample.cs b/UICatalog/Scenarios/TabViewExample.cs index e847afb23..7f700e4c0 100644 --- a/UICatalog/Scenarios/TabViewExample.cs +++ b/UICatalog/Scenarios/TabViewExample.cs @@ -1,199 +1,207 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Linq; using Terminal.Gui; -using static UICatalog.Scenario; -namespace UICatalog.Scenarios { +namespace UICatalog.Scenarios; - [ScenarioMetadata (Name: "Tab View", Description: "Demos TabView control with limited screen space in Absolute layout.")] - [ScenarioCategory ("Controls"), ScenarioCategory ("TabView")] - public class TabViewExample : Scenario { +[ScenarioMetadata (Name: "Tab View", Description: "Demos TabView control with limited screen space in Absolute layout.")] +[ScenarioCategory ("Controls"), ScenarioCategory ("TabView")] +public class TabViewExample : Scenario { - TabView tabView; + TabView _tabView; - MenuItem miShowTopLine; - MenuItem miShowBorder; - MenuItem miTabsOnBottom; + MenuItem _miShowTopLine; + MenuItem _miShowBorder; + MenuItem _miTabsOnBottom; + MenuItem _miShowTabViewBorder; - public override void Setup () - { - Win.Title = this.GetName (); - Win.Y = 1; // menu - Win.Height = Dim.Fill (1); // status bar + public override void Setup () + { + Win.Title = this.GetName (); + Win.Y = 1; // menu + Win.Height = Dim.Fill (1); // status bar - var menu = new MenuBar (new MenuBarItem [] { + var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { new MenuItem ("_Add Blank Tab", "", () => AddBlankTab()), - new MenuItem ("_Clear SelectedTab", "", () => tabView.SelectedTab=null), + new MenuItem ("_Clear SelectedTab", "", () => _tabView.SelectedTab=null), new MenuItem ("_Quit", "", () => Quit()), }), new MenuBarItem ("_View", new MenuItem [] { - miShowTopLine = new MenuItem ("_Show Top Line", "", () => ShowTopLine()){ + _miShowTopLine = new MenuItem ("_Show Top Line", "", () => ShowTopLine()){ Checked = true, CheckType = MenuItemCheckStyle.Checked }, - miShowBorder = new MenuItem ("_Show Border", "", () => ShowBorder()){ + _miShowBorder = new MenuItem ("_Show Border", "", () => ShowBorder()){ Checked = true, CheckType = MenuItemCheckStyle.Checked }, - miTabsOnBottom = new MenuItem ("_Tabs On Bottom", "", () => SetTabsOnBottom()){ + _miTabsOnBottom = new MenuItem ("_Tabs On Bottom", "", () => SetTabsOnBottom()){ Checked = false, CheckType = MenuItemCheckStyle.Checked + }, + _miShowTabViewBorder = new MenuItem ("_Show TabView Border", "", () => ShowTabViewBorder()){ + Checked = true, + CheckType = MenuItemCheckStyle.Checked } }) }); - Application.Top.Add (menu); + Application.Top.Add (menu); - tabView = new TabView () { - X = 0, - Y = 0, - Width = 60, - Height = 20, - }; + _tabView = new TabView () { + X = 0, + Y = 0, + Width = 60, + Height = 20, + BorderStyle = LineStyle.Single + }; - tabView.AddTab (new Tab ("Tab1", new Label ("hodor!")), false); - tabView.AddTab (new Tab ("Tab2", new TextField ("durdur")), false); - tabView.AddTab (new Tab ("Interactive Tab", GetInteractiveTab ()), false); - tabView.AddTab (new Tab ("Big Text", GetBigTextFileTab ()), false); - tabView.AddTab (new Tab ( - "Long name Tab, I mean seriously long. Like you would not believe how long this tab's name is its just too much really woooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooowwww thats long", - new Label ("This tab has a very long name which should be truncated. See TabView.MaxTabTextWidth")), - false); - tabView.AddTab (new Tab ("Les Mise" + '\u0301' + "rables", new Label ("This tab name is unicode")), false); - tabView.AddTab (new Tab ("Les Mise" + '\u0328' + '\u0301' + "rables", new Label ("This tab name has two combining marks. Only one will show due to Issue #2616.")), false); - for (int i = 0; i < 100; i++) { - tabView.AddTab (new Tab ($"Tab{i}", new Label ($"Welcome to tab {i}")), false); - } + _tabView.AddTab (new Tab () { DisplayText = "Tab1", View = new Label ("hodor!") }, false); + _tabView.AddTab (new Tab () { DisplayText = "Tab2", View = new TextField ("durdur") }, false); + _tabView.AddTab (new Tab () { DisplayText = "Interactive Tab", View = GetInteractiveTab () }, false); + _tabView.AddTab (new Tab () { DisplayText = "Big Text", View = GetBigTextFileTab () }, false); + _tabView.AddTab (new Tab () { + DisplayText = "Long name Tab, I mean seriously long. Like you would not believe how long this tab's name is its just too much really woooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooowwww thats long", + View = new Label ("This tab has a very long name which should be truncated. See TabView.MaxTabTextWidth") + }, false); + _tabView.AddTab (new Tab () { DisplayText = "Les Mise" + '\u0301' + "rables", View = new Label ("This tab name is unicode") }, false); + _tabView.AddTab (new Tab () { DisplayText = "Les Mise" + '\u0328' + '\u0301' + "rables", View = new Label ("This tab name has two combining marks. Only one will show due to Issue #2616.") }, false); + for (int i = 0; i < 100; i++) { + _tabView.AddTab (new Tab () { DisplayText = $"Tab{i}", View = new Label($"Welcome to tab {i}") }, false); + } - tabView.SelectedTab = tabView.Tabs.First (); + _tabView.SelectedTab = _tabView.Tabs.First (); - Win.Add (tabView); + Win.Add (_tabView); - var frameRight = new FrameView ("About") { - X = Pos.Right (tabView), - Y = 0, - Width = Dim.Fill (), - Height = Dim.Fill (), - }; + var frameRight = new FrameView ("About") { + X = Pos.Right (_tabView), + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill (), + }; - frameRight.Add (new TextView () { - Text = "This demos the tabs control\nSwitch between tabs using cursor keys", - Width = Dim.Fill (), - Height = Dim.Fill () - }); + frameRight.Add (new TextView () { + Text = "This demos the tabs control\nSwitch between tabs using cursor keys", + Width = Dim.Fill (), + Height = Dim.Fill () + }); - Win.Add (frameRight); + Win.Add (frameRight); - var frameBelow = new FrameView ("Bottom Frame") { - X = 0, - Y = Pos.Bottom (tabView), - Width = tabView.Width, - Height = Dim.Fill (), - }; + var frameBelow = new FrameView ("Bottom Frame") { + X = 0, + Y = Pos.Bottom (_tabView), + Width = _tabView.Width, + Height = Dim.Fill (), + }; - frameBelow.Add (new TextView () { - Text = "This frame exists to check you can still tab here\nand that the tab control doesn't overspill it's bounds", - Width = Dim.Fill (), - Height = Dim.Fill () - }); + frameBelow.Add (new TextView () { + Text = "This frame exists to check you can still tab here\nand that the tab control doesn't overspill it's bounds", + Width = Dim.Fill (), + Height = Dim.Fill () + }); - Win.Add (frameBelow); + Win.Add (frameBelow); - var statusBar = new StatusBar (new StatusItem [] { + var statusBar = new StatusBar (new StatusItem [] { new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()), }); - Application.Top.Add (statusBar); - } + Application.Top.Add (statusBar); + } - private void AddBlankTab () - { - tabView.AddTab (new Tab (), false); - } + private void AddBlankTab () + { + _tabView.AddTab (new Tab (), false); + } - private View GetInteractiveTab () - { + private View GetInteractiveTab () + { - var interactiveTab = new View () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - var lblName = new Label ("Name:"); - interactiveTab.Add (lblName); + var interactiveTab = new View () { + Width = Dim.Fill (), + Height = Dim.Fill () + }; + var lblName = new Label ("Name:"); + interactiveTab.Add (lblName); - var tbName = new TextField () { - X = Pos.Right (lblName), - Width = 10 - }; - interactiveTab.Add (tbName); + var tbName = new TextField () { + X = Pos.Right (lblName), + Width = 10 + }; + interactiveTab.Add (tbName); - var lblAddr = new Label ("Address:") { - Y = 1 - }; - interactiveTab.Add (lblAddr); + var lblAddr = new Label ("Address:") { + Y = 1 + }; + interactiveTab.Add (lblAddr); - var tbAddr = new TextField () { - X = Pos.Right (lblAddr), - Y = 1, - Width = 10 - }; - interactiveTab.Add (tbAddr); + var tbAddr = new TextField () { + X = Pos.Right (lblAddr), + Y = 1, + Width = 10 + }; + interactiveTab.Add (tbAddr); - return interactiveTab; - } + return interactiveTab; + } - private View GetBigTextFileTab () - { + private View GetBigTextFileTab () + { - var text = new TextView () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; + var text = new TextView () { + Width = Dim.Fill (), + Height = Dim.Fill () + }; - var sb = new System.Text.StringBuilder (); + var sb = new System.Text.StringBuilder (); - for (int y = 0; y < 300; y++) { - for (int x = 0; x < 500; x++) { - sb.Append ((x + y) % 2 == 0 ? '1' : '0'); - } - sb.AppendLine (); + for (int y = 0; y < 300; y++) { + for (int x = 0; x < 500; x++) { + sb.Append ((x + y) % 2 == 0 ? '1' : '0'); } - text.Text = sb.ToString (); - - return text; + sb.AppendLine (); } + text.Text = sb.ToString (); - private void ShowTopLine () - { - miShowTopLine.Checked = !miShowTopLine.Checked; + return text; + } - tabView.Style.ShowTopLine = (bool)miShowTopLine.Checked; - tabView.ApplyStyleChanges (); - } - private void ShowBorder () - { - miShowBorder.Checked = !miShowBorder.Checked; + private void ShowTopLine () + { + _miShowTopLine.Checked = !_miShowTopLine.Checked; - tabView.Style.ShowBorder = (bool)miShowBorder.Checked; - tabView.ApplyStyleChanges (); - } - private void SetTabsOnBottom () - { - miTabsOnBottom.Checked = !miTabsOnBottom.Checked; + _tabView.Style.ShowTopLine = (bool)_miShowTopLine.Checked; + _tabView.ApplyStyleChanges (); + } + private void ShowBorder () + { + _miShowBorder.Checked = !_miShowBorder.Checked; - tabView.Style.TabsOnBottom = (bool)miTabsOnBottom.Checked; - tabView.ApplyStyleChanges (); - } + _tabView.Style.ShowBorder = (bool)_miShowBorder.Checked; + _tabView.ApplyStyleChanges (); + } + private void SetTabsOnBottom () + { + _miTabsOnBottom.Checked = !_miTabsOnBottom.Checked; - private void Quit () - { - Application.RequestStop (); - } + _tabView.Style.TabsOnBottom = (bool)_miTabsOnBottom.Checked; + _tabView.ApplyStyleChanges (); + } + + private void ShowTabViewBorder () + { + _miShowTabViewBorder.Checked = !_miShowTabViewBorder.Checked; + + _tabView.BorderStyle = _miShowTabViewBorder.Checked == true ? _tabView.BorderStyle = LineStyle.Single + : LineStyle.None; + _tabView.ApplyStyleChanges (); + } + + private void Quit () + { + Application.RequestStop (); } } diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index 482c967dc..4d7fa23ef 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -287,7 +287,7 @@ public class TextFormatterTests { Key hotKey = KeyCode.Null; var result = false; - result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out hotPos, out hotKey); + result = TextFormatter.FindHotKey (text, hotKeySpecifier, out hotPos, out hotKey, supportFirstUpperCase); Assert.False (result); Assert.Equal (-1, hotPos); Assert.Equal (KeyCode.Null, hotKey); @@ -310,7 +310,7 @@ public class TextFormatterTests { { var hotKeySpecifier = (Rune)'_'; - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey); + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, out var hotPos, out var hotKey, supportFirstUpperCase); if (expectedResult) { Assert.True (result); } else { @@ -338,7 +338,7 @@ public class TextFormatterTests { { var hotKeySpecifier = (Rune)'_'; - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey); + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, out var hotPos, out var hotKey, supportFirstUpperCase); if (expectedResult) { Assert.True (result); } else { @@ -364,7 +364,7 @@ public class TextFormatterTests { { var hotKeySpecifier = (Rune)'_'; - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey); + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, out var hotPos, out var hotKey, supportFirstUpperCase); if (expectedResult) { Assert.True (result); } else { @@ -387,7 +387,7 @@ public class TextFormatterTests { var hotKeySpecifier = (Rune)0; - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey); + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, out var hotPos, out var hotKey, supportFirstUpperCase); if (expectedResult) { Assert.True (result); } else { @@ -411,7 +411,7 @@ public class TextFormatterTests { { var hotKeySpecifier = (Rune)'_'; - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, false, out var _, out var hotKey); + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, out var _, out var hotKey, false); Assert.Equal (found, result); Assert.Equal (expected, hotKey); } @@ -431,7 +431,7 @@ public class TextFormatterTests { var hotKeySpecifier = (Rune)0; - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey); + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, out var hotPos, out var hotKey, supportFirstUpperCase); Assert.False (result); Assert.Equal (-1, hotPos); Assert.Equal (KeyCode.Null, hotKey); diff --git a/UnitTests/View/DrawTests.cs b/UnitTests/View/DrawTests.cs index 69e6dc0f7..f6bdae0da 100644 --- a/UnitTests/View/DrawTests.cs +++ b/UnitTests/View/DrawTests.cs @@ -1,8 +1,6 @@ using System.Text; -using System; using Xunit; using Xunit.Abstractions; -using Microsoft.VisualStudio.TestPlatform.Utilities; namespace Terminal.Gui.ViewsTests; @@ -381,4 +379,147 @@ t ", _output); Application.Refresh (); TestHelpers.AssertDriverContentsWithFrameAre ("", _output); } + + [Theory, SetupFakeDriver] + [InlineData ("𝔽𝕆𝕆𝔹𝔸R")] + [InlineData ("a𐐀b")] + void DrawHotString_NonBmp (string expected) + { + var view = new View () { Width = 10, Height = 1 }; + view.DrawHotString (expected, Attribute.Default, Attribute.Default); + + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + + } + + [Fact, AutoInitShutdown] + public void Draw_Minimum_Full_Border_With_Empty_Bounds () + { + var label = new Label () { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; + Application.Top.Add (label); + Application.Begin (Application.Top); + + Assert.Equal ("(0,0,2,2)", label.Frame.ToString ()); + Assert.Equal ("(0,0,0,0)", label.Bounds.ToString ()); + TestHelpers.AssertDriverContentsWithFrameAre (@" +┌┐ +└┘", _output); + } + + [Fact, AutoInitShutdown] + public void Draw_Minimum_Full_Border_With_Empty_Bounds_Without_Top () + { + var label = new Label () { Width = 2, Height = 1, BorderStyle = LineStyle.Single }; + label.Border.Thickness = new Thickness (1, 0, 1, 1); + Application.Top.Add (label); + Application.Begin (Application.Top); + + Assert.Equal ("(0,0,2,1)", label.Frame.ToString ()); + Assert.Equal ("(0,0,0,0)", label.Bounds.ToString ()); + // BUGBUG: Top thickness is 0 and top shouldn't draw, + // but my changes weren't merged and TabViewTests passed + // without them and thus I give up + // The output before was ││ but I think it's also correct └┘ + TestHelpers.AssertDriverContentsWithFrameAre (@" +┌┐", _output); + } + + [Fact, AutoInitShutdown] + public void Draw_Minimum_Full_Border_With_Empty_Bounds_Without_Bottom () + { + var label = new Label () { Width = 2, Height = 1, BorderStyle = LineStyle.Single }; + label.Border.Thickness = new Thickness (1, 1, 1, 0); + Application.Top.Add (label); + Application.Begin (Application.Top); + + Assert.Equal ("(0,0,2,1)", label.Frame.ToString ()); + Assert.Equal ("(0,0,0,0)", label.Bounds.ToString ()); + // BUGBUG: Bottom thickness is 0 and bottom shouldn't draw, + // but my changes weren't merged and TabViewTests passed + // without them and thus I give up + // The output before was ── but I think it's also correct ┌┐ + TestHelpers.AssertDriverContentsWithFrameAre (@" +", _output); + } + + [Fact, AutoInitShutdown] + public void Draw_Minimum_Full_Border_With_Empty_Bounds_Without_Left () + { + var label = new Label () { Width = 1, Height = 2, BorderStyle = LineStyle.Single }; + label.Border.Thickness = new Thickness (0, 1, 1, 1); + Application.Top.Add (label); + Application.Begin (Application.Top); + + Assert.Equal ("(0,0,1,2)", label.Frame.ToString ()); + Assert.Equal ("(0,0,0,0)", label.Bounds.ToString ()); + TestHelpers.AssertDriverContentsWithFrameAre (@" +│ +│", _output); + } + + [Fact, AutoInitShutdown] + public void Draw_Minimum_Full_Border_With_Empty_Bounds_Without_Right () + { + var label = new Label () { Width = 1, Height = 2, BorderStyle = LineStyle.Single }; + label.Border.Thickness = new Thickness (1, 1, 0, 1); + Application.Top.Add (label); + Application.Begin (Application.Top); + + Assert.Equal ("(0,0,1,2)", label.Frame.ToString ()); + Assert.Equal ("(0,0,0,0)", label.Bounds.ToString ()); + TestHelpers.AssertDriverContentsWithFrameAre (@" +│ +│", _output); + } + + [Fact, AutoInitShutdown] + public void Test_Label_Full_Border () + { + var label = new Label () { Text = "Test", Width = 6, Height = 3, BorderStyle = LineStyle.Single }; + Application.Top.Add (label); + Application.Begin (Application.Top); + + Assert.Equal (new Rect (0, 0, 6, 3), label.Frame); + Assert.Equal (new Rect (0, 0, 4, 1), label.Bounds); + TestHelpers.AssertDriverContentsWithFrameAre (@" +┌────┐ +│Test│ +└────┘", _output); + } + + [Fact, AutoInitShutdown] + public void Test_Label_Without_Top_Border () + { + var label = new Label () { Text = "Test", Width = 6, Height = 3, BorderStyle = LineStyle.Single }; + label.Border.Thickness = new Thickness (1, 0, 1, 1); + Application.Top.Add (label); + Application.Begin (Application.Top); + + Assert.Equal (new Rect (0, 0, 6, 3), label.Frame); + Assert.Equal (new Rect (0, 0, 4, 2), label.Bounds); + Application.Begin (Application.Top); + + TestHelpers.AssertDriverContentsWithFrameAre (@" +│Test│ +│ │ +└────┘", _output); + } + + [Fact, AutoInitShutdown] + public void Test_Label_With_Top_Margin_Without_Top_Border () + { + var label = new Label () { Text = "Test", Width = 6, Height = 3, BorderStyle = LineStyle.Single }; + label.Margin.Thickness = new Thickness (0, 1, 0, 0); + label.Border.Thickness = new Thickness (1, 0, 1, 1); + Application.Top.Add (label); + Application.Begin (Application.Top); + + Assert.Equal (new Rect (0, 0, 6, 3), label.Frame); + Assert.Equal (new Rect (0, 0, 4, 1), label.Bounds); + Application.Begin (Application.Top); + + TestHelpers.AssertDriverContentsWithFrameAre (@" +│Test│ +└────┘", _output); + } } \ No newline at end of file diff --git a/UnitTests/View/HotKeyTests.cs b/UnitTests/View/HotKeyTests.cs index 6f36fff95..e9c00cb95 100644 --- a/UnitTests/View/HotKeyTests.cs +++ b/UnitTests/View/HotKeyTests.cs @@ -208,13 +208,13 @@ public class HotKeyTests { } [Theory] - [InlineData ("Test", KeyCode.T)] + [InlineData ("Test", KeyCode.Null)] [InlineData ("^Test", KeyCode.T)] [InlineData ("T^est", KeyCode.E)] [InlineData ("Te^st", KeyCode.S)] [InlineData ("Tes^t", KeyCode.T)] [InlineData ("other", KeyCode.Null)] - [InlineData ("oTher", KeyCode.T)] + [InlineData ("oTher", KeyCode.Null)] [InlineData ("^Öther", (KeyCode)'Ö')] [InlineData ("^öther", (KeyCode)'ö')] // BUGBUG: '!' should be supported. Line 968 of TextFormatter filters on char.IsLetterOrDigit diff --git a/UnitTests/Views/ButtonTests.cs b/UnitTests/Views/ButtonTests.cs index 2e6e02229..d545c4c09 100644 --- a/UnitTests/Views/ButtonTests.cs +++ b/UnitTests/Views/ButtonTests.cs @@ -1,271 +1,294 @@ -using System; -using Xunit; +using Xunit; using Xunit.Abstractions; -namespace Terminal.Gui.ViewsTests { - public class ButtonTests { - readonly ITestOutputHelper output; +namespace Terminal.Gui.ViewsTests; - public ButtonTests (ITestOutputHelper output) - { - this.output = output; - } +public class ButtonTests { + readonly ITestOutputHelper _output; - [Fact, AutoInitShutdown] - public void Constructors_Defaults () - { - var btn = new Button (); - Assert.Equal (string.Empty, btn.Text); - Application.Top.Add (btn); - var rs = Application.Begin (Application.Top); + public ButtonTests (ITestOutputHelper output) => _output = output; - Assert.Equal ($"{CM.Glyphs.LeftBracket} {CM.Glyphs.RightBracket}", btn.TextFormatter.Text); - Assert.False (btn.IsDefault); - Assert.Equal (TextAlignment.Centered, btn.TextAlignment); - Assert.Equal ('_', btn.HotKeySpecifier.Value); - Assert.True (btn.CanFocus); - Assert.Equal (new Rect (0, 0, 4, 1), btn.Bounds); - Assert.Equal (new Rect (0, 0, 4, 1), btn.Frame); + [Fact] [SetupFakeDriver] + public void Constructors_Defaults () + { + var btn = new Button (); + Assert.Equal (string.Empty, btn.Text); + btn.BeginInit (); + btn.EndInit (); - Assert.Equal (string.Empty, btn.Title); - Assert.Equal (KeyCode.Null, btn.HotKey); + Assert.Equal ($"{CM.Glyphs.LeftBracket} {CM.Glyphs.RightBracket}", btn.TextFormatter.Text); + Assert.False (btn.IsDefault); + Assert.Equal (TextAlignment.Centered, btn.TextAlignment); + Assert.Equal ('_', btn.HotKeySpecifier.Value); + Assert.True (btn.CanFocus); + Assert.Equal (new Rect (0, 0, 4, 1), btn.Bounds); + Assert.Equal (new Rect (0, 0, 4, 1), btn.Frame); + Assert.Equal ($"{CM.Glyphs.LeftBracket} {CM.Glyphs.RightBracket}", btn.TextFormatter.Text); + Assert.False (btn.IsDefault); + Assert.Equal (TextAlignment.Centered, btn.TextAlignment); + Assert.Equal ('_', btn.HotKeySpecifier.Value); + Assert.True (btn.CanFocus); + Assert.Equal (new Rect (0, 0, 4, 1), btn.Bounds); + Assert.Equal (new Rect (0, 0, 4, 1), btn.Frame); - var expected = @$" + Assert.Equal (string.Empty, btn.Title); + Assert.Equal (KeyCode.Null, btn.HotKey); + + btn.Draw (); + + var expected = @$" {CM.Glyphs.LeftBracket} {CM.Glyphs.RightBracket} "; - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Application.End (rs); + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); - btn = new Button ("ARGS", true) { Text = "Test" }; - Assert.Equal ("Test", btn.Text); - Application.Top.Add (btn); - rs = Application.Begin (Application.Top); + btn = new Button ("ARGS", true) { Text = "_Test" }; + btn.BeginInit (); + btn.EndInit (); + Assert.Equal ('_', btn.HotKeySpecifier.Value); + Assert.Equal (Key.T, btn.HotKey); + Assert.Equal ("_Test", btn.Text); - Assert.Equal ($"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Test {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}", btn.TextFormatter.Text); - Assert.True (btn.IsDefault); - Assert.Equal (TextAlignment.Centered, btn.TextAlignment); - Assert.Equal ('_', btn.HotKeySpecifier.Value); - Assert.True (btn.CanFocus); - Assert.Equal (new Rect (0, 0, 10, 1), btn.Bounds); - Assert.Equal (new Rect (0, 0, 10, 1), btn.Frame); - Assert.Equal (KeyCode.T, btn.HotKey); - Application.End (rs); + Assert.Equal ($"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Test {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}", btn.TextFormatter.Format ()); + Assert.True (btn.IsDefault); + Assert.Equal (TextAlignment.Centered, btn.TextAlignment); + Assert.True (btn.CanFocus); + Assert.Equal (new Rect (0, 0, 10, 1), btn.Bounds); + Assert.Equal (new Rect (0, 0, 10, 1), btn.Frame); + Assert.Equal (KeyCode.T, btn.HotKey); - btn = new Button (3, 4, "Test", true); - Assert.Equal ("Test", btn.Text); - Application.Top.Add (btn); - rs = Application.Begin (Application.Top); + btn = new Button (1, 2, "_abc", true); + btn.BeginInit (); + btn.EndInit (); + Assert.Equal ("_abc", btn.Text); + Assert.Equal (Key.A, btn.HotKey); - Assert.Equal ($"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Test {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}", btn.TextFormatter.Text); - Assert.True (btn.IsDefault); - Assert.Equal (TextAlignment.Centered, btn.TextAlignment); - Assert.Equal ('_', btn.HotKeySpecifier.Value); - Assert.True (btn.CanFocus); - Assert.Equal (new Rect (0, 0, 10, 1), btn.Bounds); - Assert.Equal (new Rect (3, 4, 10, 1), btn.Frame); - Assert.Equal (KeyCode.T, btn.HotKey); + Assert.Equal ($"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} abc {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}", btn.TextFormatter.Format ()); + Assert.True (btn.IsDefault); + Assert.Equal (TextAlignment.Centered, btn.TextAlignment); + Assert.Equal ('_', btn.HotKeySpecifier.Value); + Assert.True (btn.CanFocus); - Application.End (rs); - } + Application.Driver.ClearContents (); + btn.Draw (); - [Fact] - [AutoInitShutdown] - public void KeyBindings_Command () - { - var clicked = false; - Button btn = new Button ("_Test"); - btn.Clicked += (s, e) => clicked = true; - Application.Top.Add (btn); - Application.Begin (Application.Top); + expected = @$" + {CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} abc {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket} +"; + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); - // Hot key. Both alone and with alt - Assert.Equal (KeyCode.T, btn.HotKey); - Assert.True (btn.NewKeyDownEvent (new (KeyCode.T))); - Assert.True (clicked); - clicked = false; + Assert.Equal (new Rect (0, 0, 10, 1), btn.Bounds); + Assert.Equal (new Rect (1, 2, 10, 1), btn.Frame); + } - Assert.True (btn.NewKeyDownEvent (new (KeyCode.T | KeyCode.AltMask))); - Assert.True (clicked); - clicked = false; + [Fact] + [AutoInitShutdown] + public void KeyBindings_Command () + { + var clicked = false; + var btn = new Button ("_Test"); + btn.Clicked += (s, e) => clicked = true; + Application.Top.Add (btn); + Application.Begin (Application.Top); - Assert.True (btn.NewKeyDownEvent (btn.HotKey)); - Assert.True (clicked); - clicked = false; + // Hot key. Both alone and with alt + Assert.Equal (KeyCode.T, btn.HotKey); + Assert.True (btn.NewKeyDownEvent (new Key (KeyCode.T))); + Assert.True (clicked); + clicked = false; - // IsDefault = false - // Space and Enter should work - Assert.False (btn.IsDefault); - Assert.True (btn.NewKeyDownEvent (new (KeyCode.Enter))); - Assert.True (clicked); - clicked = false; + Assert.True (btn.NewKeyDownEvent (new Key (KeyCode.T | KeyCode.AltMask))); + Assert.True (clicked); + clicked = false; - // IsDefault = true - // Space and Enter should work - btn.IsDefault = true; - Assert.True (btn.NewKeyDownEvent (new (KeyCode.Enter))); - Assert.True (clicked); - clicked = false; + Assert.True (btn.NewKeyDownEvent (btn.HotKey)); + Assert.True (clicked); + clicked = false; + Assert.True (btn.NewKeyDownEvent (btn.HotKey)); + Assert.True (clicked); + clicked = false; - // Toplevel does not handle Enter, so it should get passed on to button - Assert.True (Application.Top.NewKeyDownEvent (new (KeyCode.Enter))); - Assert.True (clicked); - clicked = false; + // IsDefault = false + // Space and Enter should work + Assert.False (btn.IsDefault); + Assert.True (btn.NewKeyDownEvent (new Key (KeyCode.Enter))); + Assert.True (clicked); + clicked = false; - // Direct - Assert.True (btn.NewKeyDownEvent (new (KeyCode.Enter))); - Assert.True (clicked); - clicked = false; + // IsDefault = true + // Space and Enter should work + btn.IsDefault = true; + Assert.True (btn.NewKeyDownEvent (new Key (KeyCode.Enter))); + Assert.True (clicked); + clicked = false; - Assert.True (btn.NewKeyDownEvent (new (KeyCode.Space))); - Assert.True (clicked); - clicked = false; + // Toplevel does not handle Enter, so it should get passed on to button + Assert.True (Application.Top.NewKeyDownEvent (new Key (KeyCode.Enter))); + Assert.True (clicked); + clicked = false; - Assert.True (btn.NewKeyDownEvent (new ((KeyCode)'T'))); - Assert.True (clicked); - clicked = false; + // Direct + Assert.True (btn.NewKeyDownEvent (new Key (KeyCode.Enter))); + Assert.True (clicked); + clicked = false; - // Change hotkey: - btn.Text = "Te_st"; - Assert.True (btn.NewKeyDownEvent (btn.HotKey)); - Assert.True (clicked); - clicked = false; - } + Assert.True (btn.NewKeyDownEvent (new Key (KeyCode.Space))); + Assert.True (clicked); + clicked = false; - [Fact] - [AutoInitShutdown] - public void HotKeyChange_Works () - { - var clicked = false; - Button btn = new Button ("Test"); - btn.Clicked += (s, e) => clicked = true; - Application.Top.Add (btn); - Application.Begin (Application.Top); + Assert.True (btn.NewKeyDownEvent (new Key ((KeyCode)'T'))); + Assert.True (clicked); + clicked = false; - Assert.Equal (KeyCode.T, btn.HotKey); - Assert.True (btn.NewKeyDownEvent (new (KeyCode.T))); - Assert.True (clicked); + // Change hotkey: + btn.Text = "Te_st"; + Assert.True (btn.NewKeyDownEvent (btn.HotKey)); + Assert.True (clicked); + clicked = false; + } - clicked = false; - Assert.True (btn.NewKeyDownEvent (new (KeyCode.T | KeyCode.AltMask))); - Assert.True (clicked); + [Fact] + [AutoInitShutdown] + public void HotKeyChange_Works () + { + var clicked = false; + var btn = new Button ("_Test"); + btn.Clicked += (s, e) => clicked = true; + Application.Top.Add (btn); + Application.Begin (Application.Top); - clicked = false; - btn.HotKey = KeyCode.E; - Assert.True (btn.NewKeyDownEvent (new (KeyCode.E | KeyCode.AltMask))); - Assert.True (clicked); - } + Assert.Equal (KeyCode.T, btn.HotKey); + Assert.True (btn.NewKeyDownEvent (new Key (KeyCode.T))); + Assert.True (clicked); - /// - /// This test demonstrates how to change the activation key for Button - /// as described in the README.md keyboard handling section - /// - [Fact] - [AutoInitShutdown] - public void KeyBindingExample () - { - int pressed = 0; - var btn = new Button ("Press Me"); + clicked = false; + Assert.True (btn.NewKeyDownEvent (new Key (KeyCode.T | KeyCode.AltMask))); + Assert.True (clicked); - btn.Clicked += (s, e) => pressed++; + clicked = false; + btn.HotKey = KeyCode.E; + Assert.True (btn.NewKeyDownEvent (new Key (KeyCode.E | KeyCode.AltMask))); + Assert.True (clicked); + } - // The Button class supports the Default and Accept command - Assert.Contains (Command.Default, btn.GetSupportedCommands ()); - Assert.Contains (Command.Accept, btn.GetSupportedCommands ()); + /// + /// This test demonstrates how to change the activation key for Button + /// as described in the README.md keyboard handling section + /// + [Fact] + [AutoInitShutdown] + public void KeyBindingExample () + { + var pressed = 0; + var btn = new Button ("Press Me"); - Application.Top.Add (btn); - Application.Begin (Application.Top); + btn.Clicked += (s, e) => pressed++; - // default keybinding is Space which results in keypress - Application.OnKeyDown (new ((KeyCode)' ')); - Assert.Equal (1, pressed); + // The Button class supports the Default and Accept command + Assert.Contains (Command.Default, btn.GetSupportedCommands ()); + Assert.Contains (Command.Accept, btn.GetSupportedCommands ()); - // remove the default keybinding (Space) - btn.KeyBindings.Clear (Command.Default, Command.Accept); + Application.Top.Add (btn); + Application.Begin (Application.Top); + Application.Top.Add (btn); + Application.Begin (Application.Top); - // After clearing the default keystroke the Space button no longer does anything for the Button - Application.OnKeyDown (new ((KeyCode)' ')); - Assert.Equal (1, pressed); + // default keybinding is Space which results in keypress + Application.OnKeyDown (new Key ((KeyCode)' ')); + Assert.Equal (1, pressed); - // Set a new binding of b for the click (Accept) event - btn.KeyBindings.Add (KeyCode.B, Command.Default, Command.Accept); + // remove the default keybinding (Space) + btn.KeyBindings.Clear (Command.Default, Command.Accept); - // now pressing B should call the button click event - Application.OnKeyDown (new (KeyCode.B)); - Assert.Equal (2, pressed); + // After clearing the default keystroke the Space button no longer does anything for the Button + Application.OnKeyDown (new Key ((KeyCode)' ')); + Assert.Equal (1, pressed); - // now pressing Shift-B should NOT call the button click event - Application.OnKeyDown (new (KeyCode.ShiftMask | KeyCode.B)); - Assert.Equal (2, pressed); + // Set a new binding of b for the click (Accept) event + btn.KeyBindings.Add (KeyCode.B, Command.Default, Command.Accept); - // now pressing Alt-B should NOT call the button click event - Application.OnKeyDown (new (KeyCode.AltMask | KeyCode.B)); - Assert.Equal (2, pressed); + // now pressing B should call the button click event + Application.OnKeyDown (new Key (KeyCode.B)); + Assert.Equal (2, pressed); - // now pressing Shift-Alt-B should NOT call the button click event - Application.OnKeyDown (new (KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.B)); - Assert.Equal (2, pressed); - } + // now pressing Shift-B should NOT call the button click event + Application.OnKeyDown (new Key (KeyCode.ShiftMask | KeyCode.B)); + Assert.Equal (2, pressed); - [Fact] - public void TestAssignTextToButton () - { - View b = new Button () { Text = "heya" }; - Assert.Equal ("heya", b.Text); - Assert.Contains ("heya", b.TextFormatter.Text); - b.Text = "heyb"; - Assert.Equal ("heyb", b.Text); - Assert.Contains ("heyb", b.TextFormatter.Text); + // now pressing Alt-B should NOT call the button click event + Application.OnKeyDown (new Key (KeyCode.AltMask | KeyCode.B)); + Assert.Equal (2, pressed); - // with cast - Assert.Equal ("heyb", ((Button)b).Text); - } + // now pressing Shift-Alt-B should NOT call the button click event + Application.OnKeyDown (new Key (KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.B)); + Assert.Equal (2, pressed); + } - [Fact] - public void Setting_Empty_Text_Sets_HoKey_To_KeyNull () - { - var super = new View (); - var btn = new Button ("Test"); - super.Add (btn); - super.BeginInit (); - super.EndInit (); + [Fact] + public void TestAssignTextToButton () + { + View b = new Button { Text = "heya" }; + Assert.Equal ("heya", b.Text); + Assert.Contains ("heya", b.TextFormatter.Text); + b.Text = "heyb"; + Assert.Equal ("heyb", b.Text); + Assert.Contains ("heyb", b.TextFormatter.Text); - Assert.Equal ("Test", btn.Text); - Assert.Equal (KeyCode.T, btn.HotKey); + // with cast + Assert.Equal ("heyb", ((Button)b).Text); + } - btn.Text = string.Empty; - Assert.Equal ("", btn.Text); - Assert.Equal (KeyCode.Null, btn.HotKey); + [Fact] + public void Setting_Empty_Text_Sets_HoKey_To_KeyNull () + { + var super = new View (); + var btn = new Button ("_Test"); + super.Add (btn); + super.BeginInit (); + super.EndInit (); - btn.Text = "Te_st"; - Assert.Equal ("Te_st", btn.Text); - Assert.Equal (KeyCode.S, btn.HotKey); - } + Assert.Equal ("_Test", btn.Text); + Assert.Equal (KeyCode.T, btn.HotKey); - [Fact, AutoInitShutdown] - public void Update_Only_On_Or_After_Initialize () - { - var btn = new Button ("Say Hello 你") { - X = Pos.Center (), - Y = Pos.Center () - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (btn); - Application.Top.Add (win); + btn.Text = string.Empty; + Assert.Equal ("", btn.Text); + Assert.Equal (KeyCode.Null, btn.HotKey); + btn.Text = string.Empty; + Assert.Equal ("", btn.Text); + Assert.Equal (KeyCode.Null, btn.HotKey); - Assert.False (btn.IsInitialized); + btn.Text = "Te_st"; + Assert.Equal ("Te_st", btn.Text); + Assert.Equal (KeyCode.S, btn.HotKey); + } - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + [Fact, AutoInitShutdown] + public void Update_Only_On_Or_After_Initialize () + { + var btn = new Button ("Say Hello 你") { + X = Pos.Center (), + Y = Pos.Center () + }; + var win = new Window { + Width = Dim.Fill (), + Height = Dim.Fill () + }; + win.Add (btn); + Application.Top.Add (win); - Assert.True (btn.IsInitialized); - Assert.Equal ("Say Hello 你", btn.Text); - Assert.Equal ($"{CM.Glyphs.LeftBracket} {btn.Text} {CM.Glyphs.RightBracket}", btn.TextFormatter.Text); - Assert.Equal (new Rect (0, 0, 16, 1), btn.Bounds); - var btnTxt = $"{CM.Glyphs.LeftBracket} {btn.Text} {CM.Glyphs.RightBracket}"; - var expected = @$" + Assert.False (btn.IsInitialized); + Assert.False (btn.IsInitialized); + + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + + Assert.True (btn.IsInitialized); + Assert.Equal ("Say Hello 你", btn.Text); + Assert.Equal ($"{CM.Glyphs.LeftBracket} {btn.Text} {CM.Glyphs.RightBracket}", btn.TextFormatter.Text); + Assert.Equal (new Rect (0, 0, 16, 1), btn.Bounds); + var btnTxt = $"{CM.Glyphs.LeftBracket} {btn.Text} {CM.Glyphs.RightBracket}"; + var expected = @$" ┌────────────────────────────┐ │ │ │ {btnTxt} │ @@ -273,36 +296,39 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 30, 5), pos); - } + var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 30, 5), pos); + } - [Fact, AutoInitShutdown] - public void Update_Parameterless_Only_On_Or_After_Initialize () - { - var btn = new Button () { - X = Pos.Center (), - Y = Pos.Center (), - Text = "Say Hello 你" - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill (), - }; - win.Add (btn); - Application.Top.Add (win); + [Fact, AutoInitShutdown] + public void Update_Parameterless_Only_On_Or_After_Initialize () + { + var btn = new Button { + X = Pos.Center (), + Y = Pos.Center (), + Text = "Say Hello 你" + }; + var win = new Window { + Width = Dim.Fill (), + Height = Dim.Fill () + }; + win.Add (btn); + Application.Top.Add (win); - Assert.False (btn.IsInitialized); + Assert.False (btn.IsInitialized); + Assert.False (btn.IsInitialized); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (30, 5); - Assert.True (btn.IsInitialized); - Assert.Equal ("Say Hello 你", btn.Text); - Assert.Equal ($"{CM.Glyphs.LeftBracket} {btn.Text} {CM.Glyphs.RightBracket}", btn.TextFormatter.Text); - Assert.Equal (new Rect (0, 0, 16, 1), btn.Bounds); - var btnTxt = $"{CM.Glyphs.LeftBracket} {btn.Text} {CM.Glyphs.RightBracket}"; - var expected = @$" + Assert.True (btn.IsInitialized); + Assert.Equal ("Say Hello 你", btn.Text); + Assert.Equal ($"{CM.Glyphs.LeftBracket} {btn.Text} {CM.Glyphs.RightBracket}", btn.TextFormatter.Text); + Assert.Equal (new Rect (0, 0, 16, 1), btn.Bounds); + var btnTxt = $"{CM.Glyphs.LeftBracket} {btn.Text} {CM.Glyphs.RightBracket}"; + var expected = @$" ┌────────────────────────────┐ │ │ │ {btnTxt} │ @@ -310,35 +336,38 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 30, 5), pos); - } + var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 30, 5), pos); + } - [Fact, AutoInitShutdown] - public void AutoSize_Stays_True_With_EmptyText () - { - var btn = new Button () { - X = Pos.Center (), - Y = Pos.Center (), - AutoSize = true - }; + [Fact, AutoInitShutdown] + public void AutoSize_Stays_True_With_EmptyText () + { + var btn = new Button { + X = Pos.Center (), + Y = Pos.Center (), + AutoSize = true + }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill (), - }; - win.Add (btn); - Application.Top.Add (win); + var win = new Window { + Width = Dim.Fill (), + Height = Dim.Fill () + }; + win.Add (btn); + Application.Top.Add (win); - Assert.True (btn.AutoSize); + Assert.True (btn.AutoSize); + Assert.True (btn.AutoSize); - btn.Text = "Say Hello 你"; + btn.Text = "Say Hello 你"; + btn.Text = "Say Hello 你"; - Assert.True (btn.AutoSize); + Assert.True (btn.AutoSize); + Assert.True (btn.AutoSize); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); - var expected = @$" + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + var expected = @$" ┌────────────────────────────┐ │ │ │ {CM.Glyphs.LeftBracket} Say Hello 你 {CM.Glyphs.RightBracket} │ @@ -346,30 +375,31 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - } + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + } - [Fact, AutoInitShutdown] - public void AutoSize_Stays_True_Center () - { - var btn = new Button () { - X = Pos.Center (), - Y = Pos.Center (), - Text = "Say Hello 你" - }; + [Fact, AutoInitShutdown] + public void AutoSize_Stays_True_Center () + { + var btn = new Button { + X = Pos.Center (), + Y = Pos.Center (), + Text = "Say Hello 你" + }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill (), - }; - win.Add (btn); - Application.Top.Add (win); + var win = new Window { + Width = Dim.Fill (), + Height = Dim.Fill () + }; + win.Add (btn); + Application.Top.Add (win); - Assert.True (btn.AutoSize); + Assert.True (btn.AutoSize); + Assert.True (btn.AutoSize); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); - var expected = @$" + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + var expected = @$" ┌────────────────────────────┐ │ │ │ {CM.Glyphs.LeftBracket} Say Hello 你 {CM.Glyphs.RightBracket} │ @@ -377,13 +407,13 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); - Assert.True (btn.AutoSize); - btn.Text = "Say Hello 你 changed"; - Assert.True (btn.AutoSize); - Application.Refresh (); - expected = @$" + Assert.True (btn.AutoSize); + btn.Text = "Say Hello 你 changed"; + Assert.True (btn.AutoSize); + Application.Refresh (); + expected = @$" ┌────────────────────────────┐ │ │ │ {CM.Glyphs.LeftBracket} Say Hello 你 changed {CM.Glyphs.RightBracket} │ @@ -391,33 +421,35 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - } + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + } - [Fact, AutoInitShutdown] - public void AutoSize_Stays_True_AnchorEnd () - { - var btn = new Button () { - Y = Pos.Center (), - Text = "Say Hello 你", - AutoSize = true - }; - var btnTxt = $"{CM.Glyphs.LeftBracket} {btn.Text} {CM.Glyphs.RightBracket}"; + [Fact] [AutoInitShutdown] + public void AutoSize_Stays_True_AnchorEnd () + { + var btn = new Button { + Y = Pos.Center (), + Text = "Say Hello 你", + AutoSize = true + }; + var btnTxt = $"{CM.Glyphs.LeftBracket} {btn.Text} {CM.Glyphs.RightBracket}"; - btn.X = Pos.AnchorEnd () - Pos.Function (() => btn.TextFormatter.Text.GetColumns ()); + btn.X = Pos.AnchorEnd () - Pos.Function (() => btn.TextFormatter.Text.GetColumns ()); + btn.X = Pos.AnchorEnd () - Pos.Function (() => btn.TextFormatter.Text.GetColumns ()); - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill (), - }; - win.Add (btn); - Application.Top.Add (win); + var win = new Window { + Width = Dim.Fill (), + Height = Dim.Fill () + }; + win.Add (btn); + Application.Top.Add (win); - Assert.True (btn.AutoSize); + Assert.True (btn.AutoSize); + Assert.True (btn.AutoSize); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); - var expected = @$" + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + var expected = @$" ┌────────────────────────────┐ │ │ │ {btnTxt}│ @@ -425,14 +457,14 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); - Assert.True (btn.AutoSize); - btn.Text = "Say Hello 你 changed"; - btnTxt = $"{CM.Glyphs.LeftBracket} {btn.Text} {CM.Glyphs.RightBracket}"; - Assert.True (btn.AutoSize); - Application.Refresh (); - expected = @$" + Assert.True (btn.AutoSize); + btn.Text = "Say Hello 你 changed"; + btnTxt = $"{CM.Glyphs.LeftBracket} {btn.Text} {CM.Glyphs.RightBracket}"; + Assert.True (btn.AutoSize); + Application.Refresh (); + expected = @$" ┌────────────────────────────┐ │ │ │ {btnTxt}│ @@ -440,117 +472,117 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - } + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + } - [Fact, AutoInitShutdown] - public void AutoSize_False_With_Fixed_Width () - { - var tab = new View (); + [Fact] [AutoInitShutdown] + public void AutoSize_False_With_Fixed_Width () + { + var tab = new View (); - var lblWidth = 8; + var lblWidth = 8; - var label = new Label ("Find:") { - Y = 1, - Width = lblWidth, - TextAlignment = TextAlignment.Right, - AutoSize = false - }; - tab.Add (label); + var label = new Label ("Find:") { + Y = 1, + Width = lblWidth, + TextAlignment = TextAlignment.Right, + AutoSize = false + }; + tab.Add (label); - var txtToFind = new TextField ("Testing buttons.") { - X = Pos.Right (label) + 1, - Y = Pos.Top (label), - Width = 20 - }; - tab.Add (txtToFind); + var txtToFind = new TextField ("Testing buttons.") { + X = Pos.Right (label) + 1, + Y = Pos.Top (label), + Width = 20 + }; + tab.Add (txtToFind); - var btnFindNext = new Button ("Find _Next") { - X = Pos.Right (txtToFind) + 1, - Y = Pos.Top (label), - Width = 20, - Enabled = !string.IsNullOrEmpty (txtToFind.Text), - TextAlignment = TextAlignment.Centered, - IsDefault = true, - AutoSize = false - }; - tab.Add (btnFindNext); + var btnFindNext = new Button ("Find _Next") { + X = Pos.Right (txtToFind) + 1, + Y = Pos.Top (label), + Width = 20, + Enabled = !string.IsNullOrEmpty (txtToFind.Text), + TextAlignment = TextAlignment.Centered, + IsDefault = true, + AutoSize = false + }; + tab.Add (btnFindNext); - var btnFindPrevious = new Button ("Find _Previous") { - X = Pos.Right (txtToFind) + 1, - Y = Pos.Top (btnFindNext) + 1, - Width = 20, - Enabled = !string.IsNullOrEmpty (txtToFind.Text), - TextAlignment = TextAlignment.Centered, - AutoSize = false - }; - tab.Add (btnFindPrevious); + var btnFindPrevious = new Button ("Find _Previous") { + X = Pos.Right (txtToFind) + 1, + Y = Pos.Top (btnFindNext) + 1, + Width = 20, + Enabled = !string.IsNullOrEmpty (txtToFind.Text), + TextAlignment = TextAlignment.Centered, + AutoSize = false + }; + tab.Add (btnFindPrevious); - var btnCancel = new Button ("Cancel") { - X = Pos.Right (txtToFind) + 1, - Y = Pos.Top (btnFindPrevious) + 2, - Width = 20, - TextAlignment = TextAlignment.Centered, - AutoSize = false - }; - tab.Add (btnCancel); + var btnCancel = new Button ("Cancel") { + X = Pos.Right (txtToFind) + 1, + Y = Pos.Top (btnFindPrevious) + 2, + Width = 20, + TextAlignment = TextAlignment.Centered, + AutoSize = false + }; + tab.Add (btnCancel); - var ckbMatchCase = new CheckBox ("Match c_ase") { - X = 0, - Y = Pos.Top (txtToFind) + 2, - Checked = true - }; - tab.Add (ckbMatchCase); + var ckbMatchCase = new CheckBox ("Match c_ase") { + X = 0, + Y = Pos.Top (txtToFind) + 2, + Checked = true + }; + tab.Add (ckbMatchCase); - var ckbMatchWholeWord = new CheckBox ("Match _whole word") { - X = 0, - Y = Pos.Top (ckbMatchCase) + 1, - Checked = false - }; - tab.Add (ckbMatchWholeWord); + var ckbMatchWholeWord = new CheckBox ("Match _whole word") { + X = 0, + Y = Pos.Top (ckbMatchCase) + 1, + Checked = false + }; + tab.Add (ckbMatchWholeWord); - var tabView = new TabView () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - tabView.AddTab (new Tab ("Find", tab), true); + var tabView = new TabView { + Width = Dim.Fill (), + Height = Dim.Fill () + }; + tabView.AddTab (new Tab () { DisplayText = "Find", View = tab }, true); - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; + var win = new Window { + Width = Dim.Fill (), + Height = Dim.Fill () + }; - tab.Width = label.Width + txtToFind.Width + btnFindNext.Width + 2; - tab.Height = btnFindNext.Height + btnFindPrevious.Height + btnCancel.Height + 4; + tab.Width = label.Width + txtToFind.Width + btnFindNext.Width + 2; + tab.Height = btnFindNext.Height + btnFindPrevious.Height + btnCancel.Height + 4; - win.Add (tabView); - Application.Top.Add (win); + win.Add (tabView); + Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (54, 11); + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (54, 11); - Assert.Equal (new Rect (0, 0, 54, 11), win.Frame); - Assert.Equal (new Rect (0, 0, 52, 9), tabView.Frame); - Assert.Equal (new Rect (0, 0, 50, 7), tab.Frame); - Assert.Equal (new Rect (0, 1, 8, 1), label.Frame); - Assert.Equal (new Rect (9, 1, 20, 1), txtToFind.Frame); + Assert.Equal (new Rect (0, 0, 54, 11), win.Frame); + Assert.Equal (new Rect (0, 0, 52, 9), tabView.Frame); + Assert.Equal (new Rect (0, 0, 50, 7), tab.Frame); + Assert.Equal (new Rect (0, 1, 8, 1), label.Frame); + Assert.Equal (new Rect (9, 1, 20, 1), txtToFind.Frame); - Assert.Equal (0, txtToFind.ScrollOffset); - Assert.Equal (16, txtToFind.CursorPosition); + Assert.Equal (0, txtToFind.ScrollOffset); + Assert.Equal (16, txtToFind.CursorPosition); - Assert.Equal (new Rect (30, 1, 20, 1), btnFindNext.Frame); - Assert.Equal (new Rect (30, 2, 20, 1), btnFindPrevious.Frame); - Assert.Equal (new Rect (30, 4, 20, 1), btnCancel.Frame); - Assert.Equal (new Rect (0, 3, 12, 1), ckbMatchCase.Frame); - Assert.Equal (new Rect (0, 4, 18, 1), ckbMatchWholeWord.Frame); - var btn1 = $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Find Next {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}"; - var btn2 = $"{CM.Glyphs.LeftBracket} Find Previous {CM.Glyphs.RightBracket}"; - var btn3 = $"{CM.Glyphs.LeftBracket} Cancel {CM.Glyphs.RightBracket}"; - var expected = @$" + Assert.Equal (new Rect (30, 1, 20, 1), btnFindNext.Frame); + Assert.Equal (new Rect (30, 2, 20, 1), btnFindPrevious.Frame); + Assert.Equal (new Rect (30, 4, 20, 1), btnCancel.Frame); + Assert.Equal (new Rect (0, 3, 12, 1), ckbMatchCase.Frame); + Assert.Equal (new Rect (0, 4, 18, 1), ckbMatchWholeWord.Frame); + var btn1 = $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Find Next {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}"; + var btn2 = $"{CM.Glyphs.LeftBracket} Find Previous {CM.Glyphs.RightBracket}"; + var btn3 = $"{CM.Glyphs.LeftBracket} Cancel {CM.Glyphs.RightBracket}"; + var expected = @$" ┌────────────────────────────────────────────────────┐ -│┌────┐ │ +│╭────╮ │ ││Find│ │ -││ └─────────────────────────────────────────────┐│ +││ ╰─────────────────────────────────────────────╮│ ││ ││ ││ Find: Testing buttons. {btn1} ││ ││ {btn2} ││ @@ -560,31 +592,31 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────────────────────────────┘ "; - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - } + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + } - [Fact, AutoInitShutdown] - public void Pos_Center_Layout_AutoSize_True () - { - var button = new Button ("Process keys") { - X = Pos.Center (), - Y = Pos.Center (), - IsDefault = true - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (button); - Application.Top.Add (win); + [Fact] [AutoInitShutdown] + public void Pos_Center_Layout_AutoSize_True () + { + var button = new Button ("Process keys") { + X = Pos.Center (), + Y = Pos.Center (), + IsDefault = true + }; + var win = new Window { + Width = Dim.Fill (), + Height = Dim.Fill () + }; + win.Add (button); + Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); - Assert.True (button.AutoSize); - Assert.Equal (new Rect (5, 1, 18, 1), button.Frame); - var btn = $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Process keys {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}"; + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + Assert.True (button.AutoSize); + Assert.Equal (new Rect (5, 1, 18, 1), button.Frame); + var btn = $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Process keys {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}"; - var expected = @$" + var expected = @$" ┌────────────────────────────┐ │ │ │ {btn} │ @@ -592,31 +624,31 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - } + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + } - [Fact, AutoInitShutdown] - public void Pos_Center_Layout_AutoSize_False () - { - var button = new Button ("Process keys") { - X = Pos.Center (), - Y = Pos.Center (), - Width = 20, - IsDefault = true, - AutoSize = false - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (button); - Application.Top.Add (win); + [Fact] [AutoInitShutdown] + public void Pos_Center_Layout_AutoSize_False () + { + var button = new Button ("Process keys") { + X = Pos.Center (), + Y = Pos.Center (), + Width = 20, + IsDefault = true, + AutoSize = false + }; + var win = new Window { + Width = Dim.Fill (), + Height = Dim.Fill () + }; + win.Add (button); + Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); - Assert.False (button.AutoSize); - Assert.Equal (new Rect (4, 1, 20, 1), button.Frame); - var expected = @$" + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + Assert.False (button.AutoSize); + Assert.Equal (new Rect (4, 1, 20, 1), button.Frame); + var expected = @$" ┌────────────────────────────┐ │ │ │ {CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Process keys {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket} │ @@ -624,47 +656,54 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - } - [Fact, AutoInitShutdown] - public void Button_HotKeyChanged_EventFires () - { - var btn = new Button ("Yar"); - - object sender = null; - KeyChangedEventArgs args = null; - - btn.HotKeyChanged += (s, e) => { - sender = s; - args = e; - - }; - - btn.HotKey = KeyCode.R; - Assert.Same (btn, sender); - Assert.Equal (KeyCode.Y, args.OldKey); - Assert.Equal (KeyCode.R, args.NewKey); - - } - [Fact, AutoInitShutdown] - public void Button_HotKeyChanged_EventFires_WithNone () - { - var btn = new Button (); - - object sender = null; - KeyChangedEventArgs args = null; - - btn.HotKeyChanged += (s, e) => { - sender = s; - args = e; - - }; - - btn.HotKey = KeyCode.R; - Assert.Same (btn, sender); - Assert.Equal (KeyCode.Null, args.OldKey); - Assert.Equal (KeyCode.R, args.NewKey); - - } + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); } -} + + [Fact] [AutoInitShutdown] + public void Button_HotKeyChanged_EventFires () + { + var btn = new Button ("_Yar"); + + object sender = null; + KeyChangedEventArgs args = null; + + btn.HotKeyChanged += (s, e) => { + sender = s; + args = e; + btn.HotKeyChanged += (s, e) => { + sender = s; + args = e; + + }; + }; + + btn.HotKey = KeyCode.R; + Assert.Same (btn, sender); + Assert.Equal (KeyCode.Y, args.OldKey); + Assert.Equal (KeyCode.R, args.NewKey); + btn.HotKey = KeyCode.R; + Assert.Same (btn, sender); + Assert.Equal (KeyCode.Y, args.OldKey); + Assert.Equal (KeyCode.R, args.NewKey); + } + + [Fact] [AutoInitShutdown] + public void Button_HotKeyChanged_EventFires_WithNone () + { + var btn = new Button (); + + object sender = null; + KeyChangedEventArgs args = null; + + btn.HotKeyChanged += (s, e) => { + sender = s; + args = e; + + }; + + btn.HotKey = KeyCode.R; + Assert.Same (btn, sender); + Assert.Equal (KeyCode.Null, args.OldKey); + Assert.Equal (KeyCode.R, args.NewKey); + } +} \ No newline at end of file diff --git a/UnitTests/Views/CheckBoxTests.cs b/UnitTests/Views/CheckBoxTests.cs index 9659dffcb..c22a7968a 100644 --- a/UnitTests/Views/CheckBoxTests.cs +++ b/UnitTests/Views/CheckBoxTests.cs @@ -1,218 +1,210 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Xunit; +using Xunit; using Xunit.Abstractions; -namespace Terminal.Gui.ViewsTests { - public class CheckboxTests { - readonly ITestOutputHelper output; +namespace Terminal.Gui.ViewsTests; - public CheckboxTests (ITestOutputHelper output) - { - this.output = output; - } +public class CheckboxTests { + readonly ITestOutputHelper _output; - [Fact] - public void Constructors_Defaults () - { - var ckb = new CheckBox (); - Assert.True (ckb.AutoSize); - Assert.False (ckb.Checked); - Assert.False (ckb.AllowNullChecked); - Assert.Equal (string.Empty, ckb.Text); - Assert.Equal ($"{CM.Glyphs.UnChecked} ", ckb.TextFormatter.Text); - Assert.True (ckb.CanFocus); - Assert.Equal (new Rect (0, 0, 2, 1), ckb.Frame); + public CheckboxTests (ITestOutputHelper output) => _output = output; - ckb = new CheckBox ("Test", true); - Assert.True (ckb.AutoSize); - Assert.True (ckb.Checked); - Assert.False (ckb.AllowNullChecked); - Assert.Equal ("Test", ckb.Text); - Assert.Equal ($"{CM.Glyphs.Checked} Test", ckb.TextFormatter.Text); - Assert.True (ckb.CanFocus); - Assert.Equal (new Rect (0, 0, 6, 1), ckb.Frame); + [Fact] + public void Constructors_Defaults () + { + var ckb = new CheckBox (); + Assert.True (ckb.AutoSize); + Assert.False (ckb.Checked); + Assert.False (ckb.AllowNullChecked); + Assert.Equal (string.Empty, ckb.Text); + Assert.Equal ($"{CM.Glyphs.UnChecked} ", ckb.TextFormatter.Text); + Assert.True (ckb.CanFocus); + Assert.Equal (new Rect (0, 0, 2, 1), ckb.Frame); - ckb = new CheckBox (1, 2, "Test"); - Assert.True (ckb.AutoSize); - Assert.False (ckb.Checked); - Assert.False (ckb.AllowNullChecked); - Assert.Equal ("Test", ckb.Text); - Assert.Equal ($"{CM.Glyphs.UnChecked} Test", ckb.TextFormatter.Text); - Assert.True (ckb.CanFocus); - Assert.Equal (new Rect (1, 2, 6, 1), ckb.Frame); + ckb = new CheckBox ("Test", true); + Assert.True (ckb.AutoSize); + Assert.True (ckb.Checked); + Assert.False (ckb.AllowNullChecked); + Assert.Equal ("Test", ckb.Text); + Assert.Equal ($"{CM.Glyphs.Checked} Test", ckb.TextFormatter.Text); + Assert.True (ckb.CanFocus); + Assert.Equal (new Rect (0, 0, 6, 1), ckb.Frame); - ckb = new CheckBox (3, 4, "Test", true); - Assert.True (ckb.AutoSize); - Assert.True (ckb.Checked); - Assert.False (ckb.AllowNullChecked); - Assert.Equal ("Test", ckb.Text); - Assert.Equal ($"{CM.Glyphs.Checked} Test", ckb.TextFormatter.Text); - Assert.True (ckb.CanFocus); - Assert.Equal (new Rect (3, 4, 6, 1), ckb.Frame); - } + ckb = new CheckBox (1, 2, "Test"); + Assert.True (ckb.AutoSize); + Assert.False (ckb.Checked); + Assert.False (ckb.AllowNullChecked); + Assert.Equal ("Test", ckb.Text); + Assert.Equal ($"{CM.Glyphs.UnChecked} Test", ckb.TextFormatter.Text); + Assert.True (ckb.CanFocus); + Assert.Equal (new Rect (1, 2, 6, 1), ckb.Frame); - [Fact] - [AutoInitShutdown] - public void KeyBindings_Command () - { - var toggled = false; - CheckBox ckb = new CheckBox (); - ckb.Toggled += (s, e) => toggled = true; - Application.Top.Add (ckb); - Application.Begin (Application.Top); + ckb = new CheckBox (3, 4, "Test", true); + Assert.True (ckb.AutoSize); + Assert.True (ckb.Checked); + Assert.False (ckb.AllowNullChecked); + Assert.Equal ("Test", ckb.Text); + Assert.Equal ($"{CM.Glyphs.Checked} Test", ckb.TextFormatter.Text); + Assert.True (ckb.CanFocus); + Assert.Equal (new Rect (3, 4, 6, 1), ckb.Frame); + } - Assert.False (ckb.Checked); - Assert.False (toggled); - Assert.Equal (KeyCode.Null, ckb.HotKey); + [Fact] + [AutoInitShutdown] + public void KeyBindings_Command () + { + var toggled = false; + var ckb = new CheckBox (); + ckb.Toggled += (s, e) => toggled = true; + Application.Top.Add (ckb); + Application.Begin (Application.Top); - ckb.Text = "Test"; - Assert.Equal (KeyCode.T, ckb.HotKey); - Assert.True (Application.Top.NewKeyDownEvent (new (KeyCode.T))); - Assert.True (ckb.Checked); - Assert.True (toggled); + Assert.False (ckb.Checked); + Assert.False (toggled); + Assert.Equal (KeyCode.Null, ckb.HotKey); - ckb.Text = "T_est"; - toggled = false; - Assert.Equal (KeyCode.E, ckb.HotKey); - Assert.True (Application.Top.NewKeyDownEvent (new (KeyCode.E | KeyCode.AltMask))); - Assert.True (toggled); - Assert.False (ckb.Checked); + ckb.Text = "_Test"; + Assert.Equal (KeyCode.T, ckb.HotKey); + Assert.True (Application.Top.NewKeyDownEvent (new Key (KeyCode.T))); + Assert.True (ckb.Checked); + Assert.True (toggled); - toggled = false; - Assert.Equal (KeyCode.E, ckb.HotKey); - Assert.True (Application.Top.NewKeyDownEvent (new (KeyCode.E))); - Assert.True (toggled); - Assert.True (ckb.Checked); + ckb.Text = "T_est"; + toggled = false; + Assert.Equal (KeyCode.E, ckb.HotKey); + Assert.True (Application.Top.NewKeyDownEvent (new Key (KeyCode.E | KeyCode.AltMask))); + Assert.True (toggled); + Assert.False (ckb.Checked); - toggled = false; - Assert.True (Application.Top.NewKeyDownEvent (new ((KeyCode)' '))); - Assert.True (toggled); - Assert.False (ckb.Checked); + toggled = false; + Assert.Equal (KeyCode.E, ckb.HotKey); + Assert.True (Application.Top.NewKeyDownEvent (new Key (KeyCode.E))); + Assert.True (toggled); + Assert.True (ckb.Checked); - toggled = false; - Assert.True (Application.Top.NewKeyDownEvent (new (KeyCode.Space))); - Assert.True (toggled); - Assert.True (ckb.Checked); - Assert.True (ckb.AutoSize); + toggled = false; + Assert.True (Application.Top.NewKeyDownEvent (new Key ((KeyCode)' '))); + Assert.True (toggled); + Assert.False (ckb.Checked); - Application.Refresh (); + toggled = false; + Assert.True (Application.Top.NewKeyDownEvent (new Key (KeyCode.Space))); + Assert.True (toggled); + Assert.True (ckb.Checked); + Assert.True (ckb.AutoSize); - var expected = @$" + Application.Refresh (); + + var expected = @$" {CM.Glyphs.Checked} Test "; - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 6, 1), pos); - } + var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 6, 1), pos); + } - [Fact, AutoInitShutdown] - public void AutoSize_StaysVisible () - { - var checkBox = new CheckBox () { - X = 1, - Y = Pos.Center (), - Text = "Check this out 你" - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill (), - Title = "Test Demo 你" - }; - win.Add (checkBox); - Application.Top.Add (win); + [Fact] [AutoInitShutdown] + public void AutoSize_StaysVisible () + { + var checkBox = new CheckBox { + X = 1, + Y = Pos.Center (), + Text = "Check this out 你" + }; + var win = new Window { + Width = Dim.Fill (), + Height = Dim.Fill (), + Title = "Test Demo 你" + }; + win.Add (checkBox); + Application.Top.Add (win); - Assert.False (checkBox.IsInitialized); + Assert.False (checkBox.IsInitialized); - var runstate = Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + var runstate = Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (30, 5); - Assert.True (checkBox.IsInitialized); - Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame); - Assert.Equal ("Check this out 你", checkBox.Text); - Assert.Equal ($"{CM.Glyphs.UnChecked} Check this out 你", checkBox.TextFormatter.Text); - Assert.True (checkBox.AutoSize); + Assert.True (checkBox.IsInitialized); + Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame); + Assert.Equal ("Check this out 你", checkBox.Text); + Assert.Equal ($"{CM.Glyphs.UnChecked} Check this out 你", checkBox.TextFormatter.Text); + Assert.True (checkBox.AutoSize); - checkBox.Checked = true; - Assert.Equal ($"{CM.Glyphs.Checked} Check this out 你", checkBox.TextFormatter.Text); + checkBox.Checked = true; + Assert.Equal ($"{CM.Glyphs.Checked} Check this out 你", checkBox.TextFormatter.Text); - checkBox.AutoSize = false; - checkBox.AutoSize = false; - // It isn't auto-size so the height is guaranteed by the SetMinWidthHeight - checkBox.Text = "Check this out 你 changed"; - var firstIteration = false; - Application.RunIteration (ref runstate, ref firstIteration); - // BUGBUG - v2 - Autosize is busted; disabling tests for now - Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame); - var expected = @" + checkBox.AutoSize = false; + // It isn't auto-size so the height is guaranteed by the SetMinWidthHeight + checkBox.Text = "Check this out 你 changed"; + var firstIteration = false; + Application.RunIteration (ref runstate, ref firstIteration); + // BUGBUG - v2 - Autosize is busted; disabling tests for now + Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame); + var expected = @" ┌┤Test Demo 你├──────────────┐ │ │ │ ☑ Check this out 你 │ │ │ └────────────────────────────┘"; - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 30, 5), pos); + var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 30, 5), pos); - checkBox.Width = 19; - // It isn't auto-size so the height is guaranteed by the SetMinWidthHeight - checkBox.Text = "Check this out 你 changed"; - Application.RunIteration (ref runstate, ref firstIteration); - Assert.False (checkBox.AutoSize); - Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame); - expected = @" + checkBox.Width = 19; + // It isn't auto-size so the height is guaranteed by the SetMinWidthHeight + checkBox.Text = "Check this out 你 changed"; + Application.RunIteration (ref runstate, ref firstIteration); + Assert.False (checkBox.AutoSize); + Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame); + expected = @" ┌┤Test Demo 你├──────────────┐ │ │ │ ☑ Check this out 你 │ │ │ └────────────────────────────┘"; - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 30, 5), pos); + pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 30, 5), pos); - checkBox.AutoSize = true; - Application.RunIteration (ref runstate, ref firstIteration); - Assert.Equal (new Rect (1, 1, 27, 1), checkBox.Frame); - expected = @" + checkBox.AutoSize = true; + Application.RunIteration (ref runstate, ref firstIteration); + Assert.Equal (new Rect (1, 1, 27, 1), checkBox.Frame); + expected = @" ┌┤Test Demo 你├──────────────┐ │ │ │ ☑ Check this out 你 changed│ │ │ └────────────────────────────┘"; - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 30, 5), pos); - } + pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 30, 5), pos); + } - [Fact, AutoInitShutdown] - public void TextAlignment_Left () - { - var checkBox = new CheckBox () { - X = 1, - Y = Pos.Center (), - Text = "Check this out 你", - AutoSize = false, - Width = 25 - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill (), - Title = "Test Demo 你" - }; - win.Add (checkBox); - Application.Top.Add (win); + [Fact] [AutoInitShutdown] + public void TextAlignment_Left () + { + var checkBox = new CheckBox { + X = 1, + Y = Pos.Center (), + Text = "Check this out 你", + AutoSize = false, + Width = 25 + }; + var win = new Window { + Width = Dim.Fill (), + Height = Dim.Fill (), + Title = "Test Demo 你" + }; + win.Add (checkBox); + Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (30, 5); - Assert.Equal (TextAlignment.Left, checkBox.TextAlignment); - Assert.Equal (new Rect (1, 1, 25, 1), checkBox.Frame); - Assert.Equal (new Size (25, 1), checkBox.TextFormatter.Size); + Assert.Equal (TextAlignment.Left, checkBox.TextAlignment); + Assert.Equal (new Rect (1, 1, 25, 1), checkBox.Frame); + Assert.Equal (new Size (25, 1), checkBox.TextFormatter.Size); - var expected = @$" + var expected = @$" ┌┤Test Demo 你├──────────────┐ │ │ │ {CM.Glyphs.UnChecked} Check this out 你 │ @@ -220,12 +212,12 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 30, 5), pos); + var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 30, 5), pos); - checkBox.Checked = true; - Application.Refresh (); - expected = @$" + checkBox.Checked = true; + Application.Refresh (); + expected = @$" ┌┤Test Demo 你├──────────────┐ │ │ │ {CM.Glyphs.Checked} Check this out 你 │ @@ -233,38 +225,38 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 30, 5), pos); - } + pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 30, 5), pos); + } - [Fact, AutoInitShutdown] - public void TextAlignment_Centered () - { - var checkBox = new CheckBox () { - X = 1, - Y = Pos.Center (), - Text = "Check this out 你", - TextAlignment = TextAlignment.Centered, - AutoSize = false, - Width = 25 - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill (), - Title = "Test Demo 你" - }; - win.Add (checkBox); - Application.Top.Add (win); + [Fact] [AutoInitShutdown] + public void TextAlignment_Centered () + { + var checkBox = new CheckBox { + X = 1, + Y = Pos.Center (), + Text = "Check this out 你", + TextAlignment = TextAlignment.Centered, + AutoSize = false, + Width = 25 + }; + var win = new Window { + Width = Dim.Fill (), + Height = Dim.Fill (), + Title = "Test Demo 你" + }; + win.Add (checkBox); + Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (30, 5); - Assert.Equal (TextAlignment.Centered, checkBox.TextAlignment); - Assert.Equal (new Rect (1, 1, 25, 1), checkBox.Frame); - Assert.Equal (new Size (25, 1), checkBox.TextFormatter.Size); - Assert.False (checkBox.AutoSize); + Assert.Equal (TextAlignment.Centered, checkBox.TextAlignment); + Assert.Equal (new Rect (1, 1, 25, 1), checkBox.Frame); + Assert.Equal (new Size (25, 1), checkBox.TextFormatter.Size); + Assert.False (checkBox.AutoSize); - var expected = @$" + var expected = @$" ┌┤Test Demo 你├──────────────┐ │ │ │ {CM.Glyphs.UnChecked} Check this out 你 │ @@ -272,12 +264,12 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 30, 5), pos); + var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 30, 5), pos); - checkBox.Checked = true; - Application.Refresh (); - expected = @$" + checkBox.Checked = true; + Application.Refresh (); + expected = @$" ┌┤Test Demo 你├──────────────┐ │ │ │ {CM.Glyphs.Checked} Check this out 你 │ @@ -285,48 +277,48 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 30, 5), pos); - } + pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 30, 5), pos); + } - [Fact, AutoInitShutdown] - public void TextAlignment_Justified () - { - var checkBox1 = new CheckBox () { - X = 1, - Y = Pos.Center (), - Text = "Check first out 你", - TextAlignment = TextAlignment.Justified, - AutoSize = false, - Width = 25 - }; - var checkBox2 = new CheckBox () { - X = 1, - Y = Pos.Bottom (checkBox1), - Text = "Check second out 你", - TextAlignment = TextAlignment.Justified, - AutoSize = false, - Width = 25 - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill (), - Title = "Test Demo 你" - }; - win.Add (checkBox1, checkBox2); - Application.Top.Add (win); + [Fact] [AutoInitShutdown] + public void TextAlignment_Justified () + { + var checkBox1 = new CheckBox { + X = 1, + Y = Pos.Center (), + Text = "Check first out 你", + TextAlignment = TextAlignment.Justified, + AutoSize = false, + Width = 25 + }; + var checkBox2 = new CheckBox { + X = 1, + Y = Pos.Bottom (checkBox1), + Text = "Check second out 你", + TextAlignment = TextAlignment.Justified, + AutoSize = false, + Width = 25 + }; + var win = new Window { + Width = Dim.Fill (), + Height = Dim.Fill (), + Title = "Test Demo 你" + }; + win.Add (checkBox1, checkBox2); + Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 6); + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (30, 6); - Assert.Equal (TextAlignment.Justified, checkBox1.TextAlignment); - Assert.Equal (new Rect (1, 1, 25, 1), checkBox1.Frame); - Assert.Equal (new Size (25, 1), checkBox1.TextFormatter.Size); - Assert.Equal (TextAlignment.Justified, checkBox2.TextAlignment); - Assert.Equal (new Rect (1, 2, 25, 1), checkBox2.Frame); - Assert.Equal (new Size (25, 1), checkBox2.TextFormatter.Size); + Assert.Equal (TextAlignment.Justified, checkBox1.TextAlignment); + Assert.Equal (new Rect (1, 1, 25, 1), checkBox1.Frame); + Assert.Equal (new Size (25, 1), checkBox1.TextFormatter.Size); + Assert.Equal (TextAlignment.Justified, checkBox2.TextAlignment); + Assert.Equal (new Rect (1, 2, 25, 1), checkBox2.Frame); + Assert.Equal (new Size (25, 1), checkBox2.TextFormatter.Size); - var expected = @$" + var expected = @$" ┌┤Test Demo 你├──────────────┐ │ │ │ {CM.Glyphs.UnChecked} Check first out 你 │ @@ -335,17 +327,17 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 30, 6), pos); + var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 30, 6), pos); - checkBox1.Checked = true; - Assert.Equal (new Rect (1, 1, 25, 1), checkBox1.Frame); - //Assert.Equal (new Size (25, 1), checkBox1.TextFormatter.Size); - checkBox2.Checked = true; - Assert.Equal (new Rect (1, 2, 25, 1), checkBox2.Frame); - //Assert.Equal (new Size (25, 1), checkBox2.TextFormatter.Size); - Application.Refresh (); - expected = @$" + checkBox1.Checked = true; + Assert.Equal (new Rect (1, 1, 25, 1), checkBox1.Frame); + //Assert.Equal (new Size (25, 1), checkBox1.TextFormatter.Size); + checkBox2.Checked = true; + Assert.Equal (new Rect (1, 2, 25, 1), checkBox2.Frame); + //Assert.Equal (new Size (25, 1), checkBox2.TextFormatter.Size); + Application.Refresh (); + expected = @$" ┌┤Test Demo 你├──────────────┐ │ │ │ {CM.Glyphs.Checked} Check first out 你 │ @@ -354,38 +346,38 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 30, 6), pos); - } + pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 30, 6), pos); + } - [Fact, AutoInitShutdown] - public void TextAlignment_Right () - { - var checkBox = new CheckBox () { - X = 1, - Y = Pos.Center (), - Text = "Check this out 你", - TextAlignment = TextAlignment.Right, - AutoSize = false, - Width = 25 - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill (), - Title = "Test Demo 你" - }; - win.Add (checkBox); - Application.Top.Add (win); + [Fact] [AutoInitShutdown] + public void TextAlignment_Right () + { + var checkBox = new CheckBox { + X = 1, + Y = Pos.Center (), + Text = "Check this out 你", + TextAlignment = TextAlignment.Right, + AutoSize = false, + Width = 25 + }; + var win = new Window { + Width = Dim.Fill (), + Height = Dim.Fill (), + Title = "Test Demo 你" + }; + win.Add (checkBox); + Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (30, 5); - Assert.Equal (TextAlignment.Right, checkBox.TextAlignment); - Assert.Equal (new Rect (1, 1, 25, 1), checkBox.Frame); - Assert.Equal (new Size (25, 1), checkBox.TextFormatter.Size); - Assert.False (checkBox.AutoSize); + Assert.Equal (TextAlignment.Right, checkBox.TextAlignment); + Assert.Equal (new Rect (1, 1, 25, 1), checkBox.Frame); + Assert.Equal (new Size (25, 1), checkBox.TextFormatter.Size); + Assert.False (checkBox.AutoSize); - var expected = @$" + var expected = @$" ┌┤Test Demo 你├──────────────┐ │ │ │ Check this out 你 {CM.Glyphs.UnChecked} │ @@ -393,12 +385,12 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 30, 5), pos); + var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 30, 5), pos); - checkBox.Checked = true; - Application.Refresh (); - expected = @$" + checkBox.Checked = true; + Application.Refresh (); + expected = @$" ┌┤Test Demo 你├──────────────┐ │ │ │ Check this out 你 {CM.Glyphs.Checked} │ @@ -406,131 +398,130 @@ namespace Terminal.Gui.ViewsTests { └────────────────────────────┘ "; - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 30, 5), pos); - } - - [Fact, AutoInitShutdown] - public void AutoSize_Stays_True_AnchorEnd_Without_HotKeySpecifier () - { - var checkBox = new CheckBox () { - Y = Pos.Center (), - Text = "Check this out 你" - }; - checkBox.X = Pos.AnchorEnd () - Pos.Function (() => checkBox.GetSizeNeededForTextWithoutHotKey ().Width); - - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill (), - Title = "Test Demo 你" - }; - win.Add (checkBox); - Application.Top.Add (win); - - Assert.True (checkBox.AutoSize); - - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); - var expected = @$" -┌┤Test Demo 你├──────────────┐ -│ │ -│ {CM.Glyphs.UnChecked} Check this out 你│ -│ │ -└────────────────────────────┘ -"; - - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - - Assert.True (checkBox.AutoSize); - checkBox.Text = "Check this out 你 changed"; - Assert.True (checkBox.AutoSize); - Application.Refresh (); - expected = @$" -┌┤Test Demo 你├──────────────┐ -│ │ -│ {CM.Glyphs.UnChecked} Check this out 你 changed│ -│ │ -└────────────────────────────┘ -"; - - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - } - - [Fact, AutoInitShutdown] - public void AutoSize_Stays_True_AnchorEnd_With_HotKeySpecifier () - { - var checkBox = new CheckBox () { - Y = Pos.Center (), - Text = "C_heck this out 你" - }; - checkBox.X = Pos.AnchorEnd () - Pos.Function (() => checkBox.GetSizeNeededForTextWithoutHotKey ().Width); - - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill (), - Title = "Test Demo 你" - }; - win.Add (checkBox); - Application.Top.Add (win); - - Assert.True (checkBox.AutoSize); - - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); - var expected = @$" -┌┤Test Demo 你├──────────────┐ -│ │ -│ {CM.Glyphs.UnChecked} Check this out 你│ -│ │ -└────────────────────────────┘ -"; - - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - - Assert.True (checkBox.AutoSize); - checkBox.Text = "Check this out 你 changed"; - Assert.True (checkBox.AutoSize); - Application.Refresh (); - expected = @$" -┌┤Test Demo 你├──────────────┐ -│ │ -│ {CM.Glyphs.UnChecked} Check this out 你 changed│ -│ │ -└────────────────────────────┘ -"; - - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - } - - [Fact, AutoInitShutdown] - public void AllowNullChecked_Get_Set () - { - var checkBox = new CheckBox ("Check this out 你"); - var top = Application.Top; - top.Add (checkBox); - Application.Begin (top); - - Assert.False (checkBox.Checked); - Assert.True (checkBox.NewKeyDownEvent (new (KeyCode.Space))); - Assert.True (checkBox.Checked); - Assert.True (checkBox.MouseEvent (new MouseEvent () { X = 0, Y = 0, Flags = MouseFlags.Button1Clicked })); - Assert.False (checkBox.Checked); - - checkBox.AllowNullChecked = true; - Assert.True (checkBox.NewKeyDownEvent (new (KeyCode.Space))); - Assert.Null (checkBox.Checked); - Application.Refresh (); - TestHelpers.AssertDriverContentsWithFrameAre (@$" -{CM.Glyphs.NullChecked} Check this out 你", output); - Assert.True (checkBox.MouseEvent (new MouseEvent () { X = 0, Y = 0, Flags = MouseFlags.Button1Clicked })); - Assert.True (checkBox.Checked); - Assert.True (checkBox.NewKeyDownEvent (new (KeyCode.Space))); - Assert.False (checkBox.Checked); - Assert.True (checkBox.MouseEvent (new MouseEvent () { X = 0, Y = 0, Flags = MouseFlags.Button1Clicked })); - Assert.Null (checkBox.Checked); - - checkBox.AllowNullChecked = false; - Assert.False (checkBox.Checked); - } + pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 30, 5), pos); } -} + + [Fact] [AutoInitShutdown] + public void AutoSize_Stays_True_AnchorEnd_Without_HotKeySpecifier () + { + var checkBox = new CheckBox { + Y = Pos.Center (), + Text = "Check this out 你" + }; + checkBox.X = Pos.AnchorEnd () - Pos.Function (() => checkBox.GetSizeNeededForTextWithoutHotKey ().Width); + + var win = new Window { + Width = Dim.Fill (), + Height = Dim.Fill (), + Title = "Test Demo 你" + }; + win.Add (checkBox); + Application.Top.Add (win); + + Assert.True (checkBox.AutoSize); + + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + var expected = @$" +┌┤Test Demo 你├──────────────┐ +│ │ +│ {CM.Glyphs.UnChecked} Check this out 你│ +│ │ +└────────────────────────────┘ +"; + + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + + Assert.True (checkBox.AutoSize); + checkBox.Text = "Check this out 你 changed"; + Assert.True (checkBox.AutoSize); + Application.Refresh (); + expected = @$" +┌┤Test Demo 你├──────────────┐ +│ │ +│ {CM.Glyphs.UnChecked} Check this out 你 changed│ +│ │ +└────────────────────────────┘ +"; + + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + } + + [Fact] [AutoInitShutdown] + public void AutoSize_Stays_True_AnchorEnd_With_HotKeySpecifier () + { + var checkBox = new CheckBox { + Y = Pos.Center (), + Text = "C_heck this out 你" + }; + checkBox.X = Pos.AnchorEnd () - Pos.Function (() => checkBox.GetSizeNeededForTextWithoutHotKey ().Width); + + var win = new Window { + Width = Dim.Fill (), + Height = Dim.Fill (), + Title = "Test Demo 你" + }; + win.Add (checkBox); + Application.Top.Add (win); + + Assert.True (checkBox.AutoSize); + + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + var expected = @$" +┌┤Test Demo 你├──────────────┐ +│ │ +│ {CM.Glyphs.UnChecked} Check this out 你│ +│ │ +└────────────────────────────┘ +"; + + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + + Assert.True (checkBox.AutoSize); + checkBox.Text = "Check this out 你 changed"; + Assert.True (checkBox.AutoSize); + Application.Refresh (); + expected = @$" +┌┤Test Demo 你├──────────────┐ +│ │ +│ {CM.Glyphs.UnChecked} Check this out 你 changed│ +│ │ +└────────────────────────────┘ +"; + + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + } + + [Fact] [AutoInitShutdown] + public void AllowNullChecked_Get_Set () + { + var checkBox = new CheckBox ("Check this out 你"); + var top = Application.Top; + top.Add (checkBox); + Application.Begin (top); + + Assert.False (checkBox.Checked); + Assert.True (checkBox.NewKeyDownEvent (new Key (KeyCode.Space))); + Assert.True (checkBox.Checked); + Assert.True (checkBox.MouseEvent (new MouseEvent { X = 0, Y = 0, Flags = MouseFlags.Button1Clicked })); + Assert.False (checkBox.Checked); + + checkBox.AllowNullChecked = true; + Assert.True (checkBox.NewKeyDownEvent (new Key (KeyCode.Space))); + Assert.Null (checkBox.Checked); + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@$" +{CM.Glyphs.NullChecked} Check this out 你", _output); + Assert.True (checkBox.MouseEvent (new MouseEvent { X = 0, Y = 0, Flags = MouseFlags.Button1Clicked })); + Assert.True (checkBox.Checked); + Assert.True (checkBox.NewKeyDownEvent (new Key (KeyCode.Space))); + Assert.False (checkBox.Checked); + Assert.True (checkBox.MouseEvent (new MouseEvent { X = 0, Y = 0, Flags = MouseFlags.Button1Clicked })); + Assert.Null (checkBox.Checked); + + checkBox.AllowNullChecked = false; + Assert.False (checkBox.Checked); + } +} \ No newline at end of file diff --git a/UnitTests/Views/MenuTests.cs b/UnitTests/Views/MenuTests.cs index 4193ec1fa..5b7cf0df0 100644 --- a/UnitTests/Views/MenuTests.cs +++ b/UnitTests/Views/MenuTests.cs @@ -15,9 +15,9 @@ public class MenuTests { } // TODO: Create more low-level unit tests for Menu and MenuItem - + [Fact] - public void Menu_Constuctors_Defaults () + public void Menu_Constructors_Defaults () { Assert.Throws (() => new Menu (null, 0, 0, null)); @@ -28,7 +28,7 @@ public class MenuTests { } [Fact] - public void MenuItem_Constuctors_Defaults () + public void MenuItem_Constructors_Defaults () { var menuItem = new MenuItem (); Assert.Equal ("", menuItem.Title); @@ -48,4 +48,4 @@ public class MenuTests { void Run () { } } -} \ No newline at end of file +} diff --git a/UnitTests/Views/RadioGroupTests.cs b/UnitTests/Views/RadioGroupTests.cs index 652d5229c..d0b50132d 100644 --- a/UnitTests/Views/RadioGroupTests.cs +++ b/UnitTests/Views/RadioGroupTests.cs @@ -170,7 +170,7 @@ public class RadioGroupTests { [Fact] public void KeyBindings_Are_Added_Correctly () { - var rg = new RadioGroup (new string [] { "Left", "Right" }); + var rg = new RadioGroup (new string [] { "_Left", "_Right" }); Assert.NotEmpty (rg.KeyBindings.GetCommands (KeyCode.L)); Assert.NotEmpty (rg.KeyBindings.GetCommands (KeyCode.R)); @@ -185,7 +185,7 @@ public class RadioGroupTests { [Fact] public void KeyBindings_HotKeys () { - var rg = new RadioGroup (new string [] { "Left", "Right", "Cen_tered", "Justified" }); + var rg = new RadioGroup (new string [] { "_Left", "_Right", "Cen_tered", "_Justified" }); Assert.NotEmpty (rg.KeyBindings.GetCommands (KeyCode.L)); Assert.NotEmpty (rg.KeyBindings.GetCommands (KeyCode.L | KeyCode.ShiftMask)); Assert.NotEmpty (rg.KeyBindings.GetCommands (KeyCode.L | KeyCode.AltMask)); diff --git a/UnitTests/Views/TabTests.cs b/UnitTests/Views/TabTests.cs new file mode 100644 index 000000000..dcc35fb23 --- /dev/null +++ b/UnitTests/Views/TabTests.cs @@ -0,0 +1,15 @@ +using Xunit; + +namespace Terminal.Gui.ViewsTests; +public class TabTests { + + [Fact] + public void Constructor_Defaults () + { + Tab tab = new Tab (); + Assert.Equal ("Unamed", tab.DisplayText); + Assert.Null (tab.View); + Assert.Equal (LineStyle.Rounded, tab.BorderStyle); + Assert.True (tab.CanFocus); + } +} diff --git a/UnitTests/Views/TabViewTests.cs b/UnitTests/Views/TabViewTests.cs index 883ac6efa..ea0d21dfd 100644 --- a/UnitTests/Views/TabViewTests.cs +++ b/UnitTests/Views/TabViewTests.cs @@ -6,9 +6,9 @@ using Xunit.Abstractions; namespace Terminal.Gui.ViewsTests; public class TabViewTests { - readonly ITestOutputHelper output; + readonly ITestOutputHelper _output; - public TabViewTests (ITestOutputHelper output) => this.output = output; + public TabViewTests (ITestOutputHelper output) => this._output = output; TabView GetTabView () => GetTabView (out _, out _); @@ -22,8 +22,8 @@ public class TabViewTests { tv.BeginInit (); tv.EndInit (); tv.ColorScheme = new ColorScheme (); - tv.AddTab (tab1 = new Tab ("Tab1", new TextField ("hi") { Width = 2 }), false); - tv.AddTab (tab2 = new Tab ("Tab2", new Label ("hi2")), false); + tv.AddTab (tab1 = new Tab () { DisplayText = "Tab1", View = new TextField("hi") { Width = 2 } }, false); + tv.AddTab (tab2 = new Tab () { DisplayText = "Tab2", View = new Label("hi2") }, false); return tv; } @@ -35,8 +35,8 @@ public class TabViewTests { var tv = new TabView (); Tab tab1; Tab tab2; - tv.AddTab (tab1 = new Tab ("Tab1", new TextField ("hi")), false); - tv.AddTab (tab2 = new Tab ("Tab1", new Label ("hi2")), true); + tv.AddTab (tab1 = new Tab () { DisplayText = "Tab1", View = new TextField("hi") }, false); + tv.AddTab (tab2 = new Tab () { DisplayText = "Tab1", View = new Label("hi2") }, true); Assert.Equal (2, tv.Tabs.Count); Assert.Equal (tab2, tv.SelectedTab); @@ -244,67 +244,67 @@ public class TabViewTests { // Ensures that the tab bar subview gets the bounds of the parent TabView tv.LayoutSubviews (); - // Test two tab names that fit - tab1.Text = "12"; - tab2.Text = "13"; + // Test two tab names that fit + tab1.DisplayText = "12"; + tab2.DisplayText = "13"; tv.Draw (); - TestHelpers.AssertDriverContentsWithFrameAre (@" -┌──┐ -│12│13 -│ └─────┐ + TestHelpers.AssertDriverContentsWithFrameAre (@" +╭──┬──╮ +│12│13│ +│ ╰──┴──╮ │hi │ -└────────┘", output); +└────────┘", _output); tv.SelectedTab = tab2; tv.Draw (); - TestHelpers.AssertDriverContentsWithFrameAre (@" - ┌──┐ - 12│13│ -┌──┘ └──┐ + TestHelpers.AssertDriverContentsWithFrameAre (@" +╭──┬──╮ +│12│13│ +├──╯ ╰──╮ │hi2 │ -└────────┘", output); +└────────┘", _output); - tv.SelectedTab = tab1; - // Test first tab name too long - tab1.Text = "12345678910"; - tab2.Text = "13"; + tv.SelectedTab = tab1; + // Test first tab name too long + tab1.DisplayText = "12345678910"; + tab2.DisplayText = "13"; tv.Draw (); - TestHelpers.AssertDriverContentsWithFrameAre (@" -┌───────┐ + TestHelpers.AssertDriverContentsWithFrameAre (@" +╭───────╮ │1234567│ -│ └► +│ ╰► │hi │ -└────────┘", output); +└────────┘", _output); //switch to tab2 tv.SelectedTab = tab2; tv.Draw (); - TestHelpers.AssertDriverContentsWithFrameAre (@" -┌──┐ + TestHelpers.AssertDriverContentsWithFrameAre (@" +╭──╮ │13│ -◄ └─────┐ +◄ ╰─────╮ │hi2 │ -└────────┘", output); +└────────┘", _output); - // now make both tabs too long - tab1.Text = "12345678910"; - tab2.Text = "abcdefghijklmnopq"; + // now make both tabs too long + tab1.DisplayText = "12345678910"; + tab2.DisplayText = "abcdefghijklmnopq"; tv.Draw (); - TestHelpers.AssertDriverContentsWithFrameAre (@" -┌───────┐ + TestHelpers.AssertDriverContentsWithFrameAre (@" +╭───────╮ │abcdefg│ -◄ └┐ +◄ ╰╮ │hi2 │ -└────────┘", output); +└────────┘", _output); } [Fact] @@ -320,44 +320,44 @@ public class TabViewTests { // Ensures that the tab bar subview gets the bounds of the parent TabView tv.LayoutSubviews (); - // Test two tab names that fit - tab1.Text = "12"; - tab2.Text = "13"; + // Test two tab names that fit + tab1.DisplayText = "12"; + tab2.DisplayText = "13"; tv.Draw (); - TestHelpers.AssertDriverContentsWithFrameAre (@" -│12│13 -│ └─────┐ + TestHelpers.AssertDriverContentsWithFrameAre (@" +│12│13│ +│ ╰──┴──╮ │hi │ │ │ -└────────┘", output); +└────────┘", _output); tv.SelectedTab = tab2; tv.Draw (); - TestHelpers.AssertDriverContentsWithFrameAre (@" - 12│13│ -┌──┘ └──┐ + TestHelpers.AssertDriverContentsWithFrameAre (@" +│12│13│ +├──╯ ╰──╮ │hi2 │ │ │ -└────────┘", output); +└────────┘", _output); tv.SelectedTab = tab1; - // Test first tab name too long - tab1.Text = "12345678910"; - tab2.Text = "13"; + // Test first tab name too long + tab1.DisplayText = "12345678910"; + tab2.DisplayText = "13"; tv.Draw (); TestHelpers.AssertDriverContentsWithFrameAre (@" │1234567│ -│ └► +│ ╰► │hi │ │ │ -└────────┘", output); +└────────┘", _output); //switch to tab2 tv.SelectedTab = tab2; @@ -365,23 +365,23 @@ public class TabViewTests { TestHelpers.AssertDriverContentsWithFrameAre (@" │13│ -◄ └─────┐ +◄ ╰─────╮ │hi2 │ │ │ -└────────┘", output); +└────────┘", _output); - // now make both tabs too long - tab1.Text = "12345678910"; - tab2.Text = "abcdefghijklmnopq"; + // now make both tabs too long + tab1.DisplayText = "12345678910"; + tab2.DisplayText = "abcdefghijklmnopq"; tv.Draw (); TestHelpers.AssertDriverContentsWithFrameAre (@" │abcdefg│ -◄ └┐ +◄ ╰╮ │hi2 │ │ │ -└────────┘", output); +└────────┘", _output); } [Fact] @@ -395,12 +395,12 @@ public class TabViewTests { tv.Draw (); - TestHelpers.AssertDriverContentsWithFrameAre (@" -┌─┐ + TestHelpers.AssertDriverContentsWithFrameAre (@" +╭─╮ │T│ -│ └► +│ ╰► │hi│ -└──┘", output); +└──┘", _output); } [Fact] @@ -418,10 +418,10 @@ public class TabViewTests { TestHelpers.AssertDriverContentsWithFrameAre (@" │T│ -│ └► +│ ╰► │hi│ │ │ -└──┘", output); +└──┘", _output); } [Fact] @@ -435,12 +435,12 @@ public class TabViewTests { tv.Draw (); - TestHelpers.AssertDriverContentsWithFrameAre (@" -┌┐ + TestHelpers.AssertDriverContentsWithFrameAre (@" +╭╮ ││ -│└► +│╰► │h│ -└─┘", output); +└─┘", _output); } [Fact] @@ -458,10 +458,10 @@ public class TabViewTests { TestHelpers.AssertDriverContentsWithFrameAre (@" ││ -│└► +│╰► │h│ │ │ -└─┘", output); +└─┘", _output); } [Fact] @@ -477,31 +477,31 @@ public class TabViewTests { // Ensures that the tab bar subview gets the bounds of the parent TabView tv.LayoutSubviews (); - // Test two tab names that fit - tab1.Text = "12"; - tab2.Text = "13"; + // Test two tab names that fit + tab1.DisplayText = "12"; + tab2.DisplayText = "13"; tv.Draw (); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌────────┐ │hi │ -│ ┌─────┘ -│12│13 -└──┘ ", output); +│ ╭──┬──╯ +│12│13│ +╰──┴──╯ ", _output); - // Test first tab name too long - tab1.Text = "12345678910"; - tab2.Text = "13"; + // Test first tab name too long + tab1.DisplayText = "12345678910"; + tab2.DisplayText = "13"; tv.Draw (); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌────────┐ │hi │ -│ ┌► +│ ╭► │1234567│ -└───────┘ ", output); +╰───────╯ ", _output); //switch to tab2 tv.SelectedTab = tab2; @@ -510,23 +510,23 @@ public class TabViewTests { TestHelpers.AssertDriverContentsWithFrameAre (@" ┌────────┐ │hi2 │ -◄ ┌─────┘ +◄ ╭─────╯ │13│ -└──┘ ", output); +╰──╯ ", _output); - // now make both tabs too long - tab1.Text = "12345678910"; - tab2.Text = "abcdefghijklmnopq"; + // now make both tabs too long + tab1.DisplayText = "12345678910"; + tab2.DisplayText = "abcdefghijklmnopq"; tv.Draw (); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌────────┐ │hi2 │ -◄ ┌┘ +◄ ╭╯ │abcdefg│ -└───────┘ ", output); - } +╰───────╯ ", _output); + } [Fact] [AutoInitShutdown] @@ -541,9 +541,9 @@ public class TabViewTests { // Ensures that the tab bar subview gets the bounds of the parent TabView tv.LayoutSubviews (); - // Test two tab names that fit - tab1.Text = "12"; - tab2.Text = "13"; + // Test two tab names that fit + tab1.DisplayText = "12"; + tab2.DisplayText = "13"; tv.Draw (); @@ -551,8 +551,8 @@ public class TabViewTests { ┌────────┐ │hi │ │ │ -│ ┌─────┘ -│12│13 ", output); +│ ╭──┬──╯ +│12│13│ ", _output); tv.SelectedTab = tab2; @@ -562,14 +562,14 @@ public class TabViewTests { ┌────────┐ │hi2 │ │ │ -└──┐ ┌──┘ - 12│13│ ", output); +├──╮ ╭──╯ +│12│13│ ", _output); tv.SelectedTab = tab1; - // Test first tab name too long - tab1.Text = "12345678910"; - tab2.Text = "13"; + // Test first tab name too long + tab1.DisplayText = "12345678910"; + tab2.DisplayText = "13"; tv.Draw (); @@ -577,8 +577,8 @@ public class TabViewTests { ┌────────┐ │hi │ │ │ -│ ┌► -│1234567│ ", output); +│ ╭► +│1234567│ ", _output); //switch to tab2 tv.SelectedTab = tab2; @@ -588,12 +588,12 @@ public class TabViewTests { ┌────────┐ │hi2 │ │ │ -◄ ┌─────┘ -│13│ ", output); +◄ ╭─────╯ +│13│ ", _output); - // now make both tabs too long - tab1.Text = "12345678910"; - tab2.Text = "abcdefghijklmnopq"; + // now make both tabs too long + tab1.DisplayText = "12345678910"; + tab2.DisplayText = "abcdefghijklmnopq"; tv.Draw (); @@ -601,8 +601,8 @@ public class TabViewTests { ┌────────┐ │hi2 │ │ │ -◄ ┌┘ -│abcdefg│ ", output); +◄ ╭╯ +│abcdefg│ ", _output); } [Fact] @@ -621,10 +621,10 @@ public class TabViewTests { TestHelpers.AssertDriverContentsWithFrameAre (@" ┌──┐ │hi│ -│ ┌► +│ ╭► │T│ -└─┘ ", output); - } +╰─╯ ", _output); + } [Fact] [AutoInitShutdown] @@ -643,8 +643,8 @@ public class TabViewTests { ┌──┐ │hi│ │ │ -│ ┌► -│T│ ", output); +│ ╭► +│T│ ", _output); } [Fact] @@ -663,10 +663,10 @@ public class TabViewTests { TestHelpers.AssertDriverContentsWithFrameAre (@" ┌─┐ │h│ -│┌► +│╭► ││ -└┘ ", output); - } +╰╯ ", _output); + } [Fact] [AutoInitShutdown] @@ -685,8 +685,8 @@ public class TabViewTests { ┌─┐ │h│ │ │ -│┌► -││ ", output); +│╭► +││ ", _output); } [Fact] @@ -699,28 +699,28 @@ public class TabViewTests { tv.LayoutSubviews (); - tab1.Text = "Tab0"; - tab2.Text = "Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables"; + tab1.DisplayText = "Tab0"; + tab2.DisplayText = "Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables"; tv.Draw (); - TestHelpers.AssertDriverContentsWithFrameAre (@" -┌────┐ + TestHelpers.AssertDriverContentsWithFrameAre (@" +╭────╮ │Tab0│ -│ └─────────────► +│ ╰─────────────► │hi │ -└──────────────────┘", output); +└──────────────────┘", _output); tv.SelectedTab = tab2; tv.Draw (); - TestHelpers.AssertDriverContentsWithFrameAre (@" -┌──────────────┐ + TestHelpers.AssertDriverContentsWithFrameAre (@" +╭──────────────╮ │Les Misérables│ -◄ └───┐ +◄ ╰───╮ │hi2 │ -└──────────────────┘", output); +└──────────────────┘", _output); } [Fact] @@ -735,17 +735,17 @@ public class TabViewTests { tv.LayoutSubviews (); - tab1.Text = "Tab0"; - tab2.Text = "Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables"; + tab1.DisplayText = "Tab0"; + tab2.DisplayText = "Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables"; tv.Draw (); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌──────────────────┐ │hi │ -│ ┌─────────────► +│ ╭─────────────► │Tab0│ -└────┘ ", output); +╰────╯ ", _output); tv.SelectedTab = tab2; @@ -754,10 +754,10 @@ public class TabViewTests { TestHelpers.AssertDriverContentsWithFrameAre (@" ┌──────────────────┐ │hi2 │ -◄ ┌───┘ +◄ ╭───╯ │Les Misérables│ -└──────────────┘ ", output); - } +╰──────────────╯ ", _output); + } [Fact] [AutoInitShutdown] @@ -772,82 +772,402 @@ public class TabViewTests { tv.Draw (); - var tabRow = tv.Subviews [0]; - Assert.Equal ("TabRowView", tabRow.GetType ().Name); + var tabRow = tv.Subviews [0]; + Assert.Equal ("TabRowView", tabRow.GetType ().Name); - TestHelpers.AssertDriverContentsAre (@" -┌────┐ -│Tab1│Tab2 -│ └─────────────┐ + TestHelpers.AssertDriverContentsAre (@" +╭────┬────╮ +│Tab1│Tab2│ +│ ╰────┴────────╮ │hi │ └──────────────────┘ -", output); +", _output); - Tab clicked = null; + Tab clicked = null; + tv.TabClicked += (s, e) => { + clicked = e.Tab; + }; - tv.TabClicked += (s, e) => { - clicked = e.Tab; - }; + Application.Top.Add (tv); + Application.Begin (Application.Top); - // Waving mouse around does not trigger click - for (var i = 0; i < 100; i++) { - tabRow.MouseEvent (new MouseEvent { - X = i, + MouseEventEventArgs args; + + // Waving mouse around does not trigger click + for (int i = 0; i < 100; i++) { + args = new MouseEventEventArgs (new MouseEvent { + X = i, + Y = 1, + Flags = MouseFlags.ReportMousePosition + }); + Application.OnMouseEvent (args); + Application.Refresh (); + Assert.Null (clicked); + Assert.Equal (tab1, tv.SelectedTab); + } + + args = new MouseEventEventArgs (new MouseEvent { + X = 3, Y = 1, - Flags = MouseFlags.ReportMousePosition + Flags = MouseFlags.Button1Clicked }); - - Assert.Null (clicked); + Application.OnMouseEvent (args); + Application.Refresh (); + Assert.Equal (tab1, clicked); Assert.Equal (tab1, tv.SelectedTab); + + // Click to tab2 + args = new MouseEventEventArgs (new MouseEvent { + X = 6, + Y = 1, + Flags = MouseFlags.Button1Clicked + }); + Application.OnMouseEvent (args); + Application.Refresh (); + Assert.Equal (tab2, clicked); + Assert.Equal (tab2, tv.SelectedTab); + + // cancel navigation + tv.TabClicked += (s, e) => { + clicked = e.Tab; + e.MouseEvent.Handled = true; + }; + + args = new MouseEventEventArgs (new MouseEvent { + X = 3, + Y = 1, + Flags = MouseFlags.Button1Clicked + }); + Application.OnMouseEvent (args); + Application.Refresh (); + // Tab 1 was clicked but event handler blocked navigation + Assert.Equal (tab1, clicked); + Assert.Equal (tab2, tv.SelectedTab); + + args = new MouseEventEventArgs (new MouseEvent { + X = 12, + Y = 1, + Flags = MouseFlags.Button1Clicked + }); + Application.OnMouseEvent (args); + Application.Refresh (); + // Clicking beyond last tab should raise event with null Tab + Assert.Null (clicked); + Assert.Equal (tab2, tv.SelectedTab); } - tabRow.MouseEvent (new MouseEvent { - X = 3, - Y = 1, - Flags = MouseFlags.Button1Clicked - }); + [Fact, AutoInitShutdown] + public void MouseClick_Right_Left_Arrows_ChangesTab () + { + var tv = GetTabView (out var tab1, out var tab2, false); - Assert.Equal (tab1, clicked); - Assert.Equal (tab1, tv.SelectedTab); + tv.Width = 7; + tv.Height = 5; - // Click to tab2 - tabRow.MouseEvent (new MouseEvent { - X = 7, - Y = 1, - Flags = MouseFlags.Button1Clicked - }); + tv.LayoutSubviews (); - Assert.Equal (tab2, clicked); - Assert.Equal (tab2, tv.SelectedTab); + tv.Draw (); - // cancel navigation - tv.TabClicked += (s, e) => { - clicked = e.Tab; - e.MouseEvent.Handled = true; - }; + var tabRow = tv.Subviews [0]; + Assert.Equal ("TabRowView", tabRow.GetType ().Name); - tabRow.MouseEvent (new MouseEvent { - X = 3, - Y = 1, - Flags = MouseFlags.Button1Clicked - }); + TestHelpers.AssertDriverContentsAre (@" +╭────╮ +│Tab1│ +│ ╰► +│hi │ +└─────┘ +", _output); - // Tab 1 was clicked but event handler blocked navigation - Assert.Equal (tab1, clicked); - Assert.Equal (tab2, tv.SelectedTab); + Tab clicked = null; - tabRow.MouseEvent (new MouseEvent { - X = 10, - Y = 1, - Flags = MouseFlags.Button1Clicked - }); + tv.TabClicked += (s, e) => { + clicked = e.Tab; + }; - // Clicking beyond last tab should raise event with null Tab - Assert.Null (clicked); - Assert.Equal (tab2, tv.SelectedTab); + Tab oldChanged = null; + Tab newChanged = null; - } + tv.SelectedTabChanged += (s, e) => { + oldChanged = e.OldTab; + newChanged = e.NewTab; + }; + + Application.Top.Add (tv); + Application.Begin (Application.Top); + + // Click the right arrow + var args = new MouseEventEventArgs (new MouseEvent { + X = 6, + Y = 2, + Flags = MouseFlags.Button1Clicked + }); + Application.OnMouseEvent (args); + Application.Refresh (); + Assert.Null (clicked); + Assert.Equal (tab1, oldChanged); + Assert.Equal (tab2, newChanged); + Assert.Equal (tab2, tv.SelectedTab); + TestHelpers.AssertDriverContentsAre (@" +╭────╮ +│Tab2│ +◄ ╰╮ +│hi2 │ +└─────┘ +", _output); + + // Click the left arrow + args = new MouseEventEventArgs (new MouseEvent { + X = 0, + Y = 2, + Flags = MouseFlags.Button1Clicked + }); + Application.OnMouseEvent (args); + Application.Refresh (); + Assert.Null (clicked); + Assert.Equal (tab2, oldChanged); + Assert.Equal (tab1, newChanged); + Assert.Equal (tab1, tv.SelectedTab); + TestHelpers.AssertDriverContentsAre (@" +╭────╮ +│Tab1│ +│ ╰► +│hi │ +└─────┘ +", _output); + } + + [Fact, AutoInitShutdown] + public void MouseClick_Right_Left_Arrows_ChangesTab_With_Border () + { + var tv = GetTabView (out var tab1, out var tab2, false); + + tv.Width = 9; + tv.Height = 7; + + Assert.Equal (LineStyle.None, tv.BorderStyle); + tv.BorderStyle = LineStyle.Single; + + tv.LayoutSubviews (); + + tv.Draw (); + + var tabRow = tv.Subviews [0]; + Assert.Equal ("TabRowView", tabRow.GetType ().Name); + + TestHelpers.AssertDriverContentsAre (@" +┌───────┐ +│╭────╮ │ +││Tab1│ │ +││ ╰►│ +││hi ││ +│└─────┘│ +└───────┘ +", _output); + + Tab clicked = null; + + tv.TabClicked += (s, e) => { + clicked = e.Tab; + }; + + Tab oldChanged = null; + Tab newChanged = null; + + tv.SelectedTabChanged += (s, e) => { + oldChanged = e.OldTab; + newChanged = e.NewTab; + }; + + Application.Top.Add (tv); + Application.Begin (Application.Top); + + // Click the right arrow + var args = new MouseEventEventArgs (new MouseEvent { + X = 7, + Y = 3, + Flags = MouseFlags.Button1Clicked + }); + Application.OnMouseEvent (args); + Application.Refresh (); + Assert.Null (clicked); + Assert.Equal (tab1, oldChanged); + Assert.Equal (tab2, newChanged); + Assert.Equal (tab2, tv.SelectedTab); + TestHelpers.AssertDriverContentsAre (@" +┌───────┐ +│╭────╮ │ +││Tab2│ │ +│◄ ╰╮│ +││hi2 ││ +│└─────┘│ +└───────┘ +", _output); + + // Click the left arrow + args = new MouseEventEventArgs (new MouseEvent { + X = 1, + Y = 3, + Flags = MouseFlags.Button1Clicked + }); + Application.OnMouseEvent (args); + Application.Refresh (); + Assert.Null (clicked); + Assert.Equal (tab2, oldChanged); + Assert.Equal (tab1, newChanged); + Assert.Equal (tab1, tv.SelectedTab); + TestHelpers.AssertDriverContentsAre (@" +┌───────┐ +│╭────╮ │ +││Tab1│ │ +││ ╰►│ +││hi ││ +│└─────┘│ +└───────┘ +", _output); + } + + [Fact] + public void EnsureValidScrollOffsets_TabScrollOffset () + { + var tv = GetTabView (out var tab1, out var tab2); + + // Make tab width small to force only one tab visible at once + tv.Width = 4; + + tv.SelectedTab = tab1; + Assert.Equal (0, tv.TabScrollOffset); + + tv.TabScrollOffset = 10; + tv.SelectedTab = tab2; + Assert.Equal (1, tv.TabScrollOffset); + + tv.TabScrollOffset = -1; + tv.SelectedTab = tab1; + Assert.Equal (0, tv.TabScrollOffset); + + // Shutdown must be called to safely clean up Application if Init has been called + Application.Shutdown (); + } + + [Fact, AutoInitShutdown] + public void ProcessKey_Down_Up_Right_Left_Home_End_PageDown_PageUp () + { + var tv = GetTabView (out var tab1, out var tab2, false); + + tv.Width = 7; + tv.Height = 5; + + var btn = new Button ("Ok") { Y = Pos.Bottom (tv) + 1, Width = 7 }; + + var top = Application.Top; + top.Add (tv, btn); + Application.Begin (top); + + // Is the selected tab view hosting focused + Assert.Equal (tab1, tv.SelectedTab); + Assert.Equal (tv, top.Focused); + Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + Assert.Equal (tv.SelectedTab.View, top.Focused.MostFocused); + + // Press the cursor up key to focus the selected tab + var args = new Key (Key.CursorUp); + Application.OnKeyDown (args); + Application.Refresh (); + // Is the selected tab focused + Assert.Equal (tab1, tv.SelectedTab); + Assert.Equal (tv, top.Focused); + Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + + Tab oldChanged = null; + Tab newChanged = null; + + tv.SelectedTabChanged += (s, e) => { + oldChanged = e.OldTab; + newChanged = e.NewTab; + }; + + // Press the cursor right key to select the next tab + args = new Key (Key.CursorRight); + Application.OnKeyDown (args); + Application.Refresh (); + Assert.Equal (tab1, oldChanged); + Assert.Equal (tab2, newChanged); + Assert.Equal (tab2, tv.SelectedTab); + Assert.Equal (tv, top.Focused); + Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + + // Press the cursor down key to focused the selected tab view hosting + args =new Key (Key.CursorDown); + Application.OnKeyDown (args); + Application.Refresh (); + Assert.Equal (tab2, tv.SelectedTab); + Assert.Equal (tv, top.Focused); + Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + // The tab view hosting is a label which can't be focused + // and the View container is the focused one + Assert.Equal (tv.Subviews [1], top.Focused.MostFocused); + + // Press the cursor up key to focus the selected tab + args = new Key (Key.CursorUp); + Application.OnKeyDown (args); + Application.Refresh (); + // Is the selected tab focused + Assert.Equal (tab2, tv.SelectedTab); + Assert.Equal (tv, top.Focused); + Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + + // Press the cursor left key to select the previous tab + args = new Key(Key.CursorLeft); + Application.OnKeyDown (args); + Application.Refresh (); + Assert.Equal (tab2, oldChanged); + Assert.Equal (tab1, newChanged); + Assert.Equal (tab1, tv.SelectedTab); + Assert.Equal (tv, top.Focused); + Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + + // Press the end key to select the last tab + args = new Key(Key.End); + Application.OnKeyDown (args); + Application.Refresh (); + Assert.Equal (tab1, oldChanged); + Assert.Equal (tab2, newChanged); + Assert.Equal (tab2, tv.SelectedTab); + Assert.Equal (tv, top.Focused); + Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + + // Press the home key to select the first tab + args = new Key(Key.Home); + Application.OnKeyDown (args); + Application.Refresh (); + Assert.Equal (tab2, oldChanged); + Assert.Equal (tab1, newChanged); + Assert.Equal (tab1, tv.SelectedTab); + Assert.Equal (tv, top.Focused); + Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + + // Press the page down key to select the next set of tabs + args = new Key(Key.PageDown); + Application.OnKeyDown (args); + Application.Refresh (); + Assert.Equal (tab1, oldChanged); + Assert.Equal (tab2, newChanged); + Assert.Equal (tab2, tv.SelectedTab); + Assert.Equal (tv, top.Focused); + Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + + // Press the page up key to select the previous set of tabs + args = new Key(Key.PageUp); + Application.OnKeyDown (args); + Application.Refresh (); + Assert.Equal (tab2, oldChanged); + Assert.Equal (tab1, newChanged); + Assert.Equal (tab1, tv.SelectedTab); + Assert.Equal (tv, top.Focused); + Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + } void InitFakeDriver () { diff --git a/UnitTests/Views/TileViewTests.cs b/UnitTests/Views/TileViewTests.cs index 00f088779..496662ee2 100644 --- a/UnitTests/Views/TileViewTests.cs +++ b/UnitTests/Views/TileViewTests.cs @@ -4,565 +4,565 @@ using System.Linq; using Xunit; using Xunit.Abstractions; -namespace Terminal.Gui.ViewsTests { - public class TileViewTests { - readonly ITestOutputHelper output; +namespace Terminal.Gui.ViewsTests; +public class TileViewTests { + readonly ITestOutputHelper _output; - public TileViewTests (ITestOutputHelper output) - { - this.output = output; - } + public TileViewTests (ITestOutputHelper output) + { + this._output = output; + } - [Fact, AutoInitShutdown] - public void TestTileView_Vertical () - { - var tileView = Get11By3TileView (out var line); + [Fact, AutoInitShutdown] + public void TestTileView_Vertical () + { + var tileView = Get11By3TileView (out var line); - tileView.Draw (); + tileView.Draw (); - string looksLike = - @" + string looksLike = + @" 11111│22222 11111│22222 │ "; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // Keyboard movement on splitter should have no effect if it is not focused - tileView.NewKeyDownEvent (new (KeyCode.CursorRight)); - tileView.SetNeedsDisplay (); - tileView.Draw (); - TestHelpers.AssertDriverContentsAre (looksLike, output); + // Keyboard movement on splitter should have no effect if it is not focused + tileView.NewKeyDownEvent (new (KeyCode.CursorRight)); + tileView.SetNeedsDisplay (); + tileView.Draw (); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - } - [Fact, AutoInitShutdown] - public void TestTileView_Vertical_WithBorder () - { - var tileView = Get11By3TileView (out var line, true); + } + [Fact, AutoInitShutdown] + public void TestTileView_Vertical_WithBorder () + { + var tileView = Get11By3TileView (out var line, true); - tileView.Draw (); + tileView.Draw (); - string looksLike = - @" + string looksLike = + @" ┌────┬────┐ │1111│2222│ └────┴────┘"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // Keyboard movement on splitter should have no effect if it is not focused - tileView.NewKeyDownEvent (new (KeyCode.CursorRight)); - tileView.SetNeedsDisplay (); - tileView.Draw (); - TestHelpers.AssertDriverContentsAre (looksLike, output); + // Keyboard movement on splitter should have no effect if it is not focused + tileView.NewKeyDownEvent (new (KeyCode.CursorRight)); + tileView.SetNeedsDisplay (); + tileView.Draw (); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - } - [Fact, AutoInitShutdown] - public void TestTileView_Vertical_Focused () - { - var tileView = Get11By3TileView (out var line); - tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); + } + [Fact, AutoInitShutdown] + public void TestTileView_Vertical_Focused () + { + var tileView = Get11By3TileView (out var line); + tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); - tileView.Draw (); + tileView.Draw (); - string looksLike = - @" + string looksLike = + @" 11111│22222 11111◊22222 │ "; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // Now while focused move the splitter 1 unit right - line.NewKeyDownEvent (new (KeyCode.CursorRight)); - tileView.Draw (); + // Now while focused move the splitter 1 unit right + line.NewKeyDownEvent (new (KeyCode.CursorRight)); + tileView.Draw (); - looksLike = - @" + looksLike = + @" 111111│2222 111111◊2222 │ "; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // and 2 to the left - line.NewKeyDownEvent (new (KeyCode.CursorLeft)); - line.NewKeyDownEvent (new (KeyCode.CursorLeft)); - tileView.Draw (); + // and 2 to the left + line.NewKeyDownEvent (new (KeyCode.CursorLeft)); + line.NewKeyDownEvent (new (KeyCode.CursorLeft)); + tileView.Draw (); - looksLike = - @" + looksLike = + @" 1111│222222 1111◊222222 │ "; - TestHelpers.AssertDriverContentsAre (looksLike, output); - } + TestHelpers.AssertDriverContentsAre (looksLike, _output); + } - [Fact, AutoInitShutdown] - public void TestTileView_Vertical_Focused_WithBorder () - { - var tileView = Get11By3TileView (out var line, true); - tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); + [Fact, AutoInitShutdown] + public void TestTileView_Vertical_Focused_WithBorder () + { + var tileView = Get11By3TileView (out var line, true); + tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); - tileView.Draw (); + tileView.Draw (); - string looksLike = - @" + string looksLike = + @" ┌────┬────┐ │1111◊2222│ └────┴────┘"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // Now while focused move the splitter 1 unit right - line.NewKeyDownEvent (new (KeyCode.CursorRight)); - tileView.Draw (); + // Now while focused move the splitter 1 unit right + line.NewKeyDownEvent (new (KeyCode.CursorRight)); + tileView.Draw (); - looksLike = - @" + looksLike = + @" ┌─────┬───┐ │11111◊222│ └─────┴───┘"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // and 2 to the left - line.NewKeyDownEvent (new (KeyCode.CursorLeft)); - line.NewKeyDownEvent (new (KeyCode.CursorLeft)); - tileView.Draw (); + // and 2 to the left + line.NewKeyDownEvent (new (KeyCode.CursorLeft)); + line.NewKeyDownEvent (new (KeyCode.CursorLeft)); + tileView.Draw (); - looksLike = - @" + looksLike = + @" ┌───┬─────┐ │111◊22222│ └───┴─────┘"; - TestHelpers.AssertDriverContentsAre (looksLike, output); - } + TestHelpers.AssertDriverContentsAre (looksLike, _output); + } - [Fact, AutoInitShutdown] - public void TestTileView_Vertical_Focused_50PercentSplit () - { - var tileView = Get11By3TileView (out var line); - tileView.SetSplitterPos (0, Pos.Percent (50)); - Assert.IsType (tileView.SplitterDistances.ElementAt (0)); - tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); + [Fact, AutoInitShutdown] + public void TestTileView_Vertical_Focused_50PercentSplit () + { + var tileView = Get11By3TileView (out var line); + tileView.SetSplitterPos (0, Pos.Percent (50)); + Assert.IsType (tileView.SplitterDistances.ElementAt (0)); + tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); - tileView.Draw (); + tileView.Draw (); - string looksLike = - @" + string looksLike = + @" 11111│22222 11111◊22222 │ "; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // Now while focused move the splitter 1 unit right - line.NewKeyDownEvent (new (KeyCode.CursorRight)); - tileView.Draw (); + // Now while focused move the splitter 1 unit right + line.NewKeyDownEvent (new (KeyCode.CursorRight)); + tileView.Draw (); - looksLike = - @" + looksLike = + @" 111111│2222 111111◊2222 │ "; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // Even when moving the splitter location it should stay a Percentage based one - Assert.IsType (tileView.SplitterDistances.ElementAt (0)); + // Even when moving the splitter location it should stay a Percentage based one + Assert.IsType (tileView.SplitterDistances.ElementAt (0)); - // and 2 to the left - line.NewKeyDownEvent (new (KeyCode.CursorLeft)); - line.NewKeyDownEvent (new (KeyCode.CursorLeft)); - tileView.Draw (); + // and 2 to the left + line.NewKeyDownEvent (new (KeyCode.CursorLeft)); + line.NewKeyDownEvent (new (KeyCode.CursorLeft)); + tileView.Draw (); - looksLike = - @" + looksLike = + @" 1111│222222 1111◊222222 │ "; - TestHelpers.AssertDriverContentsAre (looksLike, output); - // Even when moving the splitter location it should stay a Percentage based one - Assert.IsType (tileView.SplitterDistances.ElementAt (0)); - } + TestHelpers.AssertDriverContentsAre (looksLike, _output); + // Even when moving the splitter location it should stay a Percentage based one + Assert.IsType (tileView.SplitterDistances.ElementAt (0)); + } - [Fact, AutoInitShutdown] - public void TestTileView_Horizontal () - { - var tileView = Get11By3TileView (out var line); - tileView.Orientation = Orientation.Horizontal; - tileView.Draw (); + [Fact, AutoInitShutdown] + public void TestTileView_Horizontal () + { + var tileView = Get11By3TileView (out var line); + tileView.Orientation = Orientation.Horizontal; + tileView.Draw (); - string looksLike = - @" + string looksLike = + @" 11111111111 ─────────── 22222222222"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // Keyboard movement on splitter should have no effect if it is not focused - var handled = tileView.NewKeyDownEvent (new (KeyCode.CursorDown)); - Assert.False (handled); - tileView.SetNeedsDisplay (); - tileView.Draw (); - TestHelpers.AssertDriverContentsAre (looksLike, output); - } + // Keyboard movement on splitter should have no effect if it is not focused + var handled = tileView.NewKeyDownEvent (new (KeyCode.CursorDown)); + Assert.False (handled); + tileView.SetNeedsDisplay (); + tileView.Draw (); + TestHelpers.AssertDriverContentsAre (looksLike, _output); + } - [Fact, AutoInitShutdown] - public void TestTileView_Vertical_View1MinSize_Absolute () - { - var tileView = Get11By3TileView (out var line); - tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); - tileView.Tiles.ElementAt (0).MinSize = 6; + [Fact, AutoInitShutdown] + public void TestTileView_Vertical_View1MinSize_Absolute () + { + var tileView = Get11By3TileView (out var line); + tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); + tileView.Tiles.ElementAt (0).MinSize = 6; - // distance is too small (below 6) - Assert.False (tileView.SetSplitterPos (0, 2)); + // distance is too small (below 6) + Assert.False (tileView.SetSplitterPos (0, 2)); - // Should stay where it was originally at (50%) - Assert.Equal (Pos.Percent (50), tileView.SplitterDistances.ElementAt (0)); + // Should stay where it was originally at (50%) + Assert.Equal (Pos.Percent (50), tileView.SplitterDistances.ElementAt (0)); - tileView.Draw (); + tileView.Draw (); - // so should ignore the 2 distance and stick to 6 - string looksLike = - @" + // so should ignore the 2 distance and stick to 6 + string looksLike = + @" 11111│22222 11111◊22222 │ "; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // Keyboard movement on splitter should have no effect because it - // would take us below the minimum splitter size - line.NewKeyDownEvent (new (KeyCode.CursorLeft)); - tileView.SetNeedsDisplay (); - tileView.Draw (); - TestHelpers.AssertDriverContentsAre (looksLike, output); + // Keyboard movement on splitter should have no effect because it + // would take us below the minimum splitter size + line.NewKeyDownEvent (new (KeyCode.CursorLeft)); + tileView.SetNeedsDisplay (); + tileView.Draw (); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // but we can continue to move the splitter right if we want - line.NewKeyDownEvent (new (KeyCode.CursorRight)); - tileView.SetNeedsDisplay (); - tileView.Draw (); + // but we can continue to move the splitter right if we want + line.NewKeyDownEvent (new (KeyCode.CursorRight)); + tileView.SetNeedsDisplay (); + tileView.Draw (); - looksLike = - @" + looksLike = + @" 111111│2222 111111◊2222 │ "; - TestHelpers.AssertDriverContentsAre (looksLike, output); - } + TestHelpers.AssertDriverContentsAre (looksLike, _output); + } - [Fact, AutoInitShutdown] - public void TestTileView_Vertical_View1MinSize_Absolute_WithBorder () - { - var tileView = Get11By3TileView (out var line, true); - tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); - tileView.Tiles.ElementAt (0).MinSize = 5; + [Fact, AutoInitShutdown] + public void TestTileView_Vertical_View1MinSize_Absolute_WithBorder () + { + var tileView = Get11By3TileView (out var line, true); + tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); + tileView.Tiles.ElementAt (0).MinSize = 5; - // distance is too small (below 5) - Assert.False (tileView.SetSplitterPos (0, 2)); + // distance is too small (below 5) + Assert.False (tileView.SetSplitterPos (0, 2)); - // Should stay where it was originally at (50%) - Assert.Equal (Pos.Percent (50), tileView.SplitterDistances.ElementAt (0)); + // Should stay where it was originally at (50%) + Assert.Equal (Pos.Percent (50), tileView.SplitterDistances.ElementAt (0)); - tileView.Draw (); + tileView.Draw (); - // so should ignore the 2 distance and stick to 5 - string looksLike = - @" + // so should ignore the 2 distance and stick to 5 + string looksLike = + @" ┌────┬────┐ │1111◊2222│ └────┴────┘"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // Keyboard movement on splitter should have no effect because it - // would take us below the minimum splitter size - line.NewKeyDownEvent (new (KeyCode.CursorLeft)); - tileView.SetNeedsDisplay (); - tileView.Draw (); - TestHelpers.AssertDriverContentsAre (looksLike, output); + // Keyboard movement on splitter should have no effect because it + // would take us below the minimum splitter size + line.NewKeyDownEvent (new (KeyCode.CursorLeft)); + tileView.SetNeedsDisplay (); + tileView.Draw (); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // but we can continue to move the splitter right if we want - line.NewKeyDownEvent (new (KeyCode.CursorRight)); - tileView.SetNeedsDisplay (); - tileView.Draw (); + // but we can continue to move the splitter right if we want + line.NewKeyDownEvent (new (KeyCode.CursorRight)); + tileView.SetNeedsDisplay (); + tileView.Draw (); - looksLike = - @" + looksLike = + @" ┌─────┬───┐ │11111◊222│ └─────┴───┘"; - TestHelpers.AssertDriverContentsAre (looksLike, output); - } + TestHelpers.AssertDriverContentsAre (looksLike, _output); + } - [Fact, AutoInitShutdown] - public void TestTileView_Vertical_View2MinSize_Absolute () - { - var tileView = Get11By3TileView (out var line); - tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); - tileView.Tiles.ElementAt (1).MinSize = 6; + [Fact, AutoInitShutdown] + public void TestTileView_Vertical_View2MinSize_Absolute () + { + var tileView = Get11By3TileView (out var line); + tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); + tileView.Tiles.ElementAt (1).MinSize = 6; - // distance leaves too little space for view2 (less than 6 would remain) - Assert.False (tileView.SetSplitterPos (0, 8)); + // distance leaves too little space for view2 (less than 6 would remain) + Assert.False (tileView.SetSplitterPos (0, 8)); - // Should stay where it was originally at (50%) - Assert.Equal (Pos.Percent (50), tileView.SplitterDistances.ElementAt (0)); + // Should stay where it was originally at (50%) + Assert.Equal (Pos.Percent (50), tileView.SplitterDistances.ElementAt (0)); - tileView.Draw (); + tileView.Draw (); - // so should ignore the 2 distance and stick to 6 - string looksLike = - @" + // so should ignore the 2 distance and stick to 6 + string looksLike = + @" 11111│22222 11111◊22222 │ "; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // Keyboard movement on splitter should have no effect because it - // would take us below the minimum splitter size - line.NewKeyDownEvent (new (KeyCode.CursorRight)); - tileView.SetNeedsDisplay (); - tileView.Draw (); - TestHelpers.AssertDriverContentsAre (looksLike, output); + // Keyboard movement on splitter should have no effect because it + // would take us below the minimum splitter size + line.NewKeyDownEvent (new (KeyCode.CursorRight)); + tileView.SetNeedsDisplay (); + tileView.Draw (); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // but we can continue to move the splitter left if we want - line.NewKeyDownEvent (new (KeyCode.CursorLeft)); - tileView.SetNeedsDisplay (); - tileView.Draw (); + // but we can continue to move the splitter left if we want + line.NewKeyDownEvent (new (KeyCode.CursorLeft)); + tileView.SetNeedsDisplay (); + tileView.Draw (); - looksLike = - @" + looksLike = + @" 1111│222222 1111◊222222 │ "; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - } - [Fact, AutoInitShutdown] - public void TestTileView_Vertical_View2MinSize_Absolute_WithBorder () - { - var tileView = Get11By3TileView (out var line, true); - tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); - tileView.Tiles.ElementAt (1).MinSize = 5; + } + [Fact, AutoInitShutdown] + public void TestTileView_Vertical_View2MinSize_Absolute_WithBorder () + { + var tileView = Get11By3TileView (out var line, true); + tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); + tileView.Tiles.ElementAt (1).MinSize = 5; - // distance leaves too little space for view2 (less than 5 would remain) - Assert.False (tileView.SetSplitterPos (0, 8)); + // distance leaves too little space for view2 (less than 5 would remain) + Assert.False (tileView.SetSplitterPos (0, 8)); - // Should stay where it was originally at (50%) - Assert.Equal (Pos.Percent (50), tileView.SplitterDistances.ElementAt (0)); + // Should stay where it was originally at (50%) + Assert.Equal (Pos.Percent (50), tileView.SplitterDistances.ElementAt (0)); - tileView.Draw (); + tileView.Draw (); - // so should ignore the 2 distance and stick to 6 - string looksLike = - @" + // so should ignore the 2 distance and stick to 6 + string looksLike = + @" ┌────┬────┐ │1111◊2222│ └────┴────┘"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // Keyboard movement on splitter should have no effect because it - // would take us below the minimum splitter size - line.NewKeyDownEvent (new (KeyCode.CursorRight)); - tileView.SetNeedsDisplay (); - tileView.Draw (); - TestHelpers.AssertDriverContentsAre (looksLike, output); + // Keyboard movement on splitter should have no effect because it + // would take us below the minimum splitter size + line.NewKeyDownEvent (new (KeyCode.CursorRight)); + tileView.SetNeedsDisplay (); + tileView.Draw (); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // but we can continue to move the splitter left if we want - line.NewKeyDownEvent (new (KeyCode.CursorLeft)); - tileView.SetNeedsDisplay (); - tileView.Draw (); + // but we can continue to move the splitter left if we want + line.NewKeyDownEvent (new (KeyCode.CursorLeft)); + tileView.SetNeedsDisplay (); + tileView.Draw (); - looksLike = - @" + looksLike = + @" ┌───┬─────┐ │111◊22222│ └───┴─────┘"; - TestHelpers.AssertDriverContentsAre (looksLike, output); - } + TestHelpers.AssertDriverContentsAre (looksLike, _output); + } - [Fact, AutoInitShutdown] - public void TestTileView_InsertPanelAtStart () - { - var tileView = Get11By3TileView (out var line, true); - tileView.InsertTile (0); + [Fact, AutoInitShutdown] + public void TestTileView_InsertPanelAtStart () + { + var tileView = Get11By3TileView (out var line, true); + tileView.InsertTile (0); - tileView.Draw (); + tileView.Draw (); - // so should ignore the 2 distance and stick to 6 - string looksLike = - @" + // so should ignore the 2 distance and stick to 6 + string looksLike = + @" ┌──┬───┬──┐ │ │111│22│ └──┴───┴──┘"; - TestHelpers.AssertDriverContentsAre (looksLike, output); - } + TestHelpers.AssertDriverContentsAre (looksLike, _output); + } - [Fact, AutoInitShutdown] - public void TestTileView_InsertPanelMiddle () - { - var tileView = Get11By3TileView (out var line, true); - tileView.InsertTile (1); + [Fact, AutoInitShutdown] + public void TestTileView_InsertPanelMiddle () + { + var tileView = Get11By3TileView (out var line, true); + tileView.InsertTile (1); - tileView.Draw (); + tileView.Draw (); - // so should ignore the 2 distance and stick to 6 - string looksLike = - @" + // so should ignore the 2 distance and stick to 6 + string looksLike = + @" ┌──┬───┬──┐ │11│ │22│ └──┴───┴──┘"; - TestHelpers.AssertDriverContentsAre (looksLike, output); - } + TestHelpers.AssertDriverContentsAre (looksLike, _output); + } - [Fact, AutoInitShutdown] - public void TestTileView_InsertPanelAtEnd () - { - var tileView = Get11By3TileView (out var line, true); - tileView.InsertTile (2); + [Fact, AutoInitShutdown] + public void TestTileView_InsertPanelAtEnd () + { + var tileView = Get11By3TileView (out var line, true); + tileView.InsertTile (2); - tileView.Draw (); + tileView.Draw (); - // so should ignore the 2 distance and stick to 6 - string looksLike = - @" + // so should ignore the 2 distance and stick to 6 + string looksLike = + @" ┌──┬───┬──┐ │11│222│ │ └──┴───┴──┘"; - TestHelpers.AssertDriverContentsAre (looksLike, output); - } + TestHelpers.AssertDriverContentsAre (looksLike, _output); + } - [Fact, AutoInitShutdown] - public void TestTileView_Horizontal_Focused () - { - var tileView = Get11By3TileView (out var line); + [Fact, AutoInitShutdown] + public void TestTileView_Horizontal_Focused () + { + var tileView = Get11By3TileView (out var line); - tileView.Orientation = Orientation.Horizontal; - tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); + tileView.Orientation = Orientation.Horizontal; + tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); - Assert.True (line.HasFocus); + Assert.True (line.HasFocus); - tileView.Draw (); + tileView.Draw (); - string looksLike = - @" + string looksLike = + @" 11111111111 ─────◊───── 22222222222"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // Now move splitter line down - tileView.NewKeyDownEvent (new (KeyCode.CursorDown)); + // Now move splitter line down + tileView.NewKeyDownEvent (new (KeyCode.CursorDown)); - tileView.Draw (); - looksLike = - @" + tileView.Draw (); + looksLike = + @" 11111111111 11111111111 ─────◊─────"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // And 2 up - line.NewKeyDownEvent (new (KeyCode.CursorUp)); - line.NewKeyDownEvent (new (KeyCode.CursorUp)); - tileView.SetNeedsDisplay (); - tileView.Draw (); - looksLike = - @" + // And 2 up + line.NewKeyDownEvent (new (KeyCode.CursorUp)); + line.NewKeyDownEvent (new (KeyCode.CursorUp)); + tileView.SetNeedsDisplay (); + tileView.Draw (); + looksLike = + @" ─────◊───── 22222222222 22222222222"; - TestHelpers.AssertDriverContentsAre (looksLike, output); - } + TestHelpers.AssertDriverContentsAre (looksLike, _output); + } - [Fact, AutoInitShutdown] - public void TestTileView_Horizontal_View1MinSize_Absolute () - { - var tileView = Get11By3TileView (out var line); - tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); + [Fact, AutoInitShutdown] + public void TestTileView_Horizontal_View1MinSize_Absolute () + { + var tileView = Get11By3TileView (out var line); + tileView.NewKeyDownEvent (new (tileView.ToggleResizable)); - tileView.Orientation = Orientation.Horizontal; - tileView.Tiles.ElementAt (0).MinSize = 1; + tileView.Orientation = Orientation.Horizontal; + tileView.Tiles.ElementAt (0).MinSize = 1; - // 0 should not be allowed because it brings us below minimum size of View1 - Assert.False (tileView.SetSplitterPos (0, 0)); - // position should remain where it was, at 50% - Assert.Equal (Pos.Percent (50f), tileView.SplitterDistances.ElementAt (0)); + // 0 should not be allowed because it brings us below minimum size of View1 + Assert.False (tileView.SetSplitterPos (0, 0)); + // position should remain where it was, at 50% + Assert.Equal (Pos.Percent (50f), tileView.SplitterDistances.ElementAt (0)); - tileView.Draw (); + tileView.Draw (); - string looksLike = - @" + string looksLike = + @" 11111111111 ─────◊───── 22222222222"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // Now move splitter line down (allowed - line.NewKeyDownEvent (new (KeyCode.CursorDown)); - tileView.Draw (); - looksLike = - @" + // Now move splitter line down (allowed + line.NewKeyDownEvent (new (KeyCode.CursorDown)); + tileView.Draw (); + looksLike = + @" 11111111111 11111111111 ─────◊─────"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); - // And up 2 (only 1 is allowed because of minimum size of 1 on view1) - line.NewKeyDownEvent (new (KeyCode.CursorUp)); - line.NewKeyDownEvent (new (KeyCode.CursorUp)); + // And up 2 (only 1 is allowed because of minimum size of 1 on view1) + line.NewKeyDownEvent (new (KeyCode.CursorUp)); + line.NewKeyDownEvent (new (KeyCode.CursorUp)); - tileView.SetNeedsDisplay (); - tileView.Draw (); - looksLike = - @" + tileView.SetNeedsDisplay (); + tileView.Draw (); + looksLike = + @" 11111111111 ─────◊───── 22222222222"; - TestHelpers.AssertDriverContentsAre (looksLike, output); - } + TestHelpers.AssertDriverContentsAre (looksLike, _output); + } - [Fact, AutoInitShutdown] - public void TestTileView_CannotSetSplitterPosToFuncEtc () - { - var tileView = Get11By3TileView (); + [Fact, AutoInitShutdown] + public void TestTileView_CannotSetSplitterPosToFuncEtc () + { + var tileView = Get11By3TileView (); - var ex = Assert.Throws (() => tileView.SetSplitterPos (0, Pos.Right (tileView))); - Assert.Equal ("Only Percent and Absolute values are supported. Passed value was PosView", ex.Message); + var ex = Assert.Throws (() => tileView.SetSplitterPos (0, Pos.Right (tileView))); + Assert.Equal ("Only Percent and Absolute values are supported. Passed value was PosView", ex.Message); - ex = Assert.Throws (() => tileView.SetSplitterPos (0, Pos.Function (() => 1))); - Assert.Equal ("Only Percent and Absolute values are supported. Passed value was PosFunc", ex.Message); + ex = Assert.Throws (() => tileView.SetSplitterPos (0, Pos.Function (() => 1))); + Assert.Equal ("Only Percent and Absolute values are supported. Passed value was PosFunc", ex.Message); - // Also not allowed because this results in a PosCombine - ex = Assert.Throws (() => tileView.SetSplitterPos (0, Pos.Percent (50) - 1)); - Assert.Equal ("Only Percent and Absolute values are supported. Passed value was PosCombine", ex.Message); - } + // Also not allowed because this results in a PosCombine + ex = Assert.Throws (() => tileView.SetSplitterPos (0, Pos.Percent (50) - 1)); + Assert.Equal ("Only Percent and Absolute values are supported. Passed value was PosCombine", ex.Message); + } - [Fact, AutoInitShutdown] - public void TestNestedContainer2LeftAnd1Right_RendersNicely () - { - var tileView = GetNestedContainer2Left1Right (false); + [Fact, AutoInitShutdown] + public void TestNestedContainer2LeftAnd1Right_RendersNicely () + { + var tileView = GetNestedContainer2Left1Right (false); - Assert.Equal (20, tileView.Frame.Width); - Assert.Equal (10, tileView.Tiles.ElementAt (0).ContentView.Frame.Width); - Assert.Equal (9, tileView.Tiles.ElementAt (1).ContentView.Frame.Width); + Assert.Equal (20, tileView.Frame.Width); + Assert.Equal (10, tileView.Tiles.ElementAt (0).ContentView.Frame.Width); + Assert.Equal (9, tileView.Tiles.ElementAt (1).ContentView.Frame.Width); - Assert.IsType (tileView.Tiles.ElementAt (0).ContentView); - var left = (TileView)tileView.Tiles.ElementAt (0).ContentView; - Assert.Same (left.SuperView, tileView); + Assert.IsType (tileView.Tiles.ElementAt (0).ContentView); + var left = (TileView)tileView.Tiles.ElementAt (0).ContentView; + Assert.Same (left.SuperView, tileView); - Assert.Equal (2, left.Tiles.ElementAt (0).ContentView.Subviews.Count); - Assert.IsType