From c4dc3fee29f9776b538d2f214adbd4fddf9b0be6 Mon Sep 17 00:00:00 2001 From: Tig Kindel Date: Sun, 7 Jan 2024 23:32:50 -0700 Subject: [PATCH] Fixed TabView --- Terminal.Gui/View/Layout/ViewLayout.cs | 22 +- Terminal.Gui/Views/ScrollBarView.cs | 1530 ++++++++++++------------ Terminal.Gui/Views/TextField.cs | 1 - UICatalog/Scenarios/TabViewExample.cs | 2 +- UICatalog/Scenarios/Text.cs | 2 +- UnitTests/Dialogs/DialogTests.cs | 46 +- UnitTests/View/ViewTests.cs | 16 +- UnitTests/Views/ScrollViewTests.cs | 7 +- UnitTests/Views/TabViewTests.cs | 2 +- 9 files changed, 825 insertions(+), 803 deletions(-) diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index d373ddc79..111441b84 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -66,7 +66,7 @@ public partial class View { _y = _frame.Y; _width = _frame.Width; _height = _frame.Height; - + // TODO: Figure out if the below can be optimized. if (IsInitialized /*|| LayoutStyle == LayoutStyle.Absolute*/) { LayoutFrames (); @@ -615,10 +615,14 @@ public partial class View { // First try SuperView.Bounds, then Application.Top, then Driver // Finally, if none of those are valid, use int.MaxValue (for Unit tests). - SetRelativeLayout (SuperView?.Bounds ?? Application.Top?.Bounds ?? Application.Driver?.Bounds ?? new Rect (0, 0, int.MaxValue, int.MaxValue)); + var relativeBounds = SuperView is { IsInitialized: true } ? SuperView.Bounds : + ((Application.Top != null && Application.Top.IsInitialized) ? Application.Top.Bounds : + Application.Driver?.Bounds ?? + new Rect (0, 0, int.MaxValue, int.MaxValue)); + SetRelativeLayout (relativeBounds); // TODO: Determine what, if any of the below is actually needed here. - if (IsInitialized/* || LayoutStyle == LayoutStyle.Absolute*/) { + if (IsInitialized) { SetFrameToFitText (); LayoutFrames (); TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey (); @@ -874,16 +878,16 @@ public partial class View { // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making // the view LayoutStyle.Absolute. _frame = r; - if (X is Pos.PosAbsolute) { + if (_x is Pos.PosAbsolute) { _x = Frame.X; } - if (Y is Pos.PosAbsolute) { + if (_y is Pos.PosAbsolute) { _y = Frame.Y; } - if (Width is Dim.DimAbsolute) { + if (_width is Dim.DimAbsolute) { _width = Frame.Width; } - if (Height is Dim.DimAbsolute) { + if (_height is Dim.DimAbsolute) { _height = Frame.Height; } @@ -894,7 +898,7 @@ public partial class View { SetNeedsLayout (); //SetNeedsDisplay (); } - + // BUGBUG: Why is this AFTER setting Frame? Seems duplicative. if (!SetFrameToFitText ()) { TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey (); @@ -1148,7 +1152,7 @@ public partial class View { void LayoutSubview (View v, Rect contentArea) { //if (v.LayoutStyle == LayoutStyle.Computed) { - v.SetRelativeLayout (contentArea); + v.SetRelativeLayout (contentArea); //} v.LayoutSubviews (); diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index 8932bd198..c6171f8d5 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -8,816 +8,824 @@ using System; using System.Text; -namespace Terminal.Gui { +namespace Terminal.Gui; + +/// +/// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical +/// +/// +/// +/// The scrollbar is drawn to be a representation of the Size, assuming that the +/// scroll position is set at Position. +/// +/// +/// If the region to display the scrollbar is larger than three characters, +/// arrow indicators are drawn. +/// +/// +public class ScrollBarView : View { + bool _autoHideScrollBars = true; + View _contentBottomRightCorner; + bool _hosted; + bool _keepContentAlwaysInViewport = true; + + int _lastLocation = -1; + ScrollBarView _otherScrollBarView; + int _posBarOffset; + int _posBottomTee; + int _posLeftTee; + int _posRightTee; + + int _posTopTee; + bool _showScrollIndicator; + int _size, _position; + bool _vertical; + /// - /// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical + /// Initializes a new instance of the class using + /// layout. /// - /// - /// - /// The scrollbar is drawn to be a representation of the Size, assuming that the - /// scroll position is set at Position. - /// - /// - /// If the region to display the scrollbar is larger than three characters, - /// arrow indicators are drawn. - /// - /// - public class ScrollBarView : View { - bool _vertical; - int _size, _position; - bool _showScrollIndicator; - bool _keepContentAlwaysInViewport = true; - bool _autoHideScrollBars = true; - bool _hosted; - ScrollBarView _otherScrollBarView; - View _contentBottomRightCorner; + /// Frame for the scrollbar. + public ScrollBarView (Rect rect) : this (rect, 0, 0, false) { } - bool _showBothScrollIndicator => OtherScrollBarView?._showScrollIndicator == true && _showScrollIndicator; + /// + /// Initializes a new instance of the class using + /// layout. + /// + /// Frame for the scrollbar. + /// The size that this scrollbar represents. Sets the property. + /// The position within this scrollbar. Sets the property. + /// + /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. Sets the + /// property. + /// + public ScrollBarView (Rect rect, int size, int position, bool isVertical) : base (rect) => SetInitialProperties (size, position, isVertical); - /// - /// Initializes a new instance of the class using layout. - /// - /// Frame for the scrollbar. - public ScrollBarView (Rect rect) : this (rect, 0, 0, false) { } + /// + /// Initializes a new instance of the class using + /// layout. + /// + public ScrollBarView () : this (0, 0, false) { } - /// - /// Initializes a new instance of the class using layout. - /// - /// Frame for the scrollbar. - /// The size that this scrollbar represents. Sets the property. - /// The position within this scrollbar. Sets the property. - /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. Sets the property. - public ScrollBarView (Rect rect, int size, int position, bool isVertical) : base (rect) - { - SetInitialProperties (size, position, isVertical); + /// + /// Initializes a new instance of the class using + /// layout. + /// + /// The size that this scrollbar represents. + /// The position within this scrollbar. + /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. + public ScrollBarView (int size, int position, bool isVertical) => SetInitialProperties (size, position, isVertical); + + /// + /// Initializes a new instance of the class using + /// layout. + /// + /// The view that will host this scrollbar. + /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. + /// + /// If set to true (default) will have the other scrollbar, otherwise will + /// have only one. + /// + public ScrollBarView (View host, bool isVertical, bool showBothScrollIndicator = true) : this (0, 0, isVertical) + { + if (host == null) { + throw new ArgumentNullException ("The host parameter can't be null."); } - - /// - /// Initializes a new instance of the class using layout. - /// - public ScrollBarView () : this (0, 0, false) { } - - /// - /// Initializes a new instance of the class using layout. - /// - /// The size that this scrollbar represents. - /// The position within this scrollbar. - /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. - public ScrollBarView (int size, int position, bool isVertical) : base () - { - SetInitialProperties (size, position, isVertical); + if (host.SuperView == null) { + throw new ArgumentNullException ("The host SuperView parameter can't be null."); } - - /// - /// Initializes a new instance of the class using layout. - /// - /// The view that will host this scrollbar. - /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. - /// If set to true (default) will have the other scrollbar, otherwise will have only one. - public ScrollBarView (View host, bool isVertical, bool showBothScrollIndicator = true) : this (0, 0, isVertical) - { - if (host == null) { - throw new ArgumentNullException ("The host parameter can't be null."); - } else if (host.SuperView == null) { - throw new ArgumentNullException ("The host SuperView parameter can't be null."); - } - _hosted = true; - ColorScheme = host.ColorScheme; - X = isVertical ? Pos.Right (host) - 1 : Pos.Left (host); - Y = isVertical ? Pos.Top (host) : Pos.Bottom (host) - 1; - Host = host; - CanFocus = false; - Enabled = host.Enabled; - Visible = host.Visible; - //Host.CanFocusChanged += Host_CanFocusChanged; - Host.EnabledChanged += Host_EnabledChanged; - Host.VisibleChanged += Host_VisibleChanged; - Host.SuperView.Add (this); - AutoHideScrollBars = true; - if (showBothScrollIndicator) { - OtherScrollBarView = new ScrollBarView (0, 0, !isVertical) { - Id = "OtherScrollBarView", - ColorScheme = host.ColorScheme, - Host = host, - CanFocus = false, - Enabled = host.Enabled, - Visible = host.Visible, - OtherScrollBarView = this - }; - OtherScrollBarView._hosted = true; - OtherScrollBarView.X = OtherScrollBarView.IsVertical ? Pos.Right (host) - 1 : Pos.Left (host); - OtherScrollBarView.Y = OtherScrollBarView.IsVertical ? Pos.Top (host) : Pos.Bottom (host) - 1; - OtherScrollBarView.Host.SuperView.Add (OtherScrollBarView); - OtherScrollBarView.ShowScrollIndicator = true; - } - ShowScrollIndicator = true; - CreateBottomRightCorner (); - ClearOnVisibleFalse = false; - } - - private void CreateBottomRightCorner () - { - if (Host != null && (_contentBottomRightCorner == null && OtherScrollBarView == null - || (_contentBottomRightCorner == null && OtherScrollBarView != null && OtherScrollBarView._contentBottomRightCorner == null))) { - - _contentBottomRightCorner = new View () { - Id = "contentBottomRightCorner", - Visible = Host.Visible, - ClearOnVisibleFalse = false, - ColorScheme = ColorScheme - }; - if (_hosted) { - Host.SuperView.Add (_contentBottomRightCorner); - } else { - Host.Add (_contentBottomRightCorner); - } - _contentBottomRightCorner.X = Pos.Right (Host) - 1; - _contentBottomRightCorner.Y = Pos.Bottom (Host) - 1; - _contentBottomRightCorner.Width = 1; - _contentBottomRightCorner.Height = 1; - _contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick; - _contentBottomRightCorner.DrawContent += _contentBottomRightCorner_DrawContent; - } - } - - private void _contentBottomRightCorner_DrawContent (object sender, DrawEventArgs e) - { - Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ()); - } - - private void Host_VisibleChanged (object sender, EventArgs e) - { - if (!Host.Visible) { - Visible = Host.Visible; - if (_otherScrollBarView != null) { - _otherScrollBarView.Visible = Visible; - } - _contentBottomRightCorner.Visible = Visible; - } else { - ShowHideScrollBars (); - } - } - - private void Host_EnabledChanged (object sender, EventArgs e) - { - Enabled = Host.Enabled; - if (_otherScrollBarView != null) { - _otherScrollBarView.Enabled = Enabled; - } - _contentBottomRightCorner.Enabled = Enabled; - } - - //private void Host_CanFocusChanged () - //{ - // CanFocus = Host.CanFocus; - // if (otherScrollBarView != null) { - // otherScrollBarView.CanFocus = CanFocus; - // } - //} - - void ContentBottomRightCorner_MouseClick (object sender, MouseEventEventArgs me) - { - if (me.MouseEvent.Flags == MouseFlags.WheeledDown || me.MouseEvent.Flags == MouseFlags.WheeledUp - || me.MouseEvent.Flags == MouseFlags.WheeledRight || me.MouseEvent.Flags == MouseFlags.WheeledLeft) { - - MouseEvent (me.MouseEvent); - } else if (me.MouseEvent.Flags == MouseFlags.Button1Clicked) { - Host.SetFocus (); - } - - me.Handled = true; - } - - void SetInitialProperties (int size, int position, bool isVertical) - { - Id = "ScrollBarView"; - _vertical = isVertical; - this._position = position; - this._size = size; - WantContinuousButtonPressed = true; - ClearOnVisibleFalse = false; - - Added += (s, e) => CreateBottomRightCorner (); - - Initialized += (s, e) => { - SetWidthHeight (); - SetRelativeLayout (SuperView?.Frame ?? Host?.Frame ?? Frame); - if (Id == "OtherScrollBarView" || OtherScrollBarView == null) { - // Only do this once if both scrollbars are enabled - ShowHideScrollBars (); - } - SetPosition (position); + _hosted = true; + ColorScheme = host.ColorScheme; + X = isVertical ? Pos.Right (host) - 1 : Pos.Left (host); + Y = isVertical ? Pos.Top (host) : Pos.Bottom (host) - 1; + Host = host; + CanFocus = false; + Enabled = host.Enabled; + Visible = host.Visible; + //Host.CanFocusChanged += Host_CanFocusChanged; + Host.EnabledChanged += Host_EnabledChanged; + Host.VisibleChanged += Host_VisibleChanged; + Host.SuperView.Add (this); + AutoHideScrollBars = true; + if (showBothScrollIndicator) { + OtherScrollBarView = new ScrollBarView (0, 0, !isVertical) { + Id = "OtherScrollBarView", + ColorScheme = host.ColorScheme, + Host = host, + CanFocus = false, + Enabled = host.Enabled, + Visible = host.Visible, + OtherScrollBarView = this }; + OtherScrollBarView._hosted = true; + OtherScrollBarView.X = OtherScrollBarView.IsVertical ? Pos.Right (host) - 1 : Pos.Left (host); + OtherScrollBarView.Y = OtherScrollBarView.IsVertical ? Pos.Top (host) : Pos.Bottom (host) - 1; + OtherScrollBarView.Host.SuperView.Add (OtherScrollBarView); + OtherScrollBarView.ShowScrollIndicator = true; } + ShowScrollIndicator = true; + CreateBottomRightCorner (); + ClearOnVisibleFalse = false; + } - /// - /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. - /// - public bool IsVertical { - get => _vertical; - set { - _vertical = value; - if (IsInitialized) { - SetWidthHeight (); - } + bool _showBothScrollIndicator => OtherScrollBarView?._showScrollIndicator == true && _showScrollIndicator; + + /// + /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. + /// + public bool IsVertical { + get => _vertical; + set { + _vertical = value; + if (IsInitialized) { + SetWidthHeight (); } } + } - /// - /// The size of content the scrollbar represents. - /// - /// The size. - /// The is typically the size of the virtual content. E.g. when a Scrollbar is - /// part of a the Size is set to the appropriate dimension of . - public int Size { - get => _size; - set { - _size = value; - if (IsInitialized) { - SetRelativeLayout (SuperView?.Frame ?? Host.Frame); - ShowHideScrollBars (false); - SetNeedsDisplay (); - } + /// + /// The size of content the scrollbar represents. + /// + /// The size. + /// + /// The is typically the size of the virtual content. E.g. when a Scrollbar is + /// part of a the Size is set to the appropriate dimension of . + /// + public int Size { + get => _size; + set { + _size = value; + if (IsInitialized) { + SetRelativeLayout (SuperView?.Frame ?? Host.Frame); + ShowHideScrollBars (false); + SetNeedsDisplay (); } } + } - /// - /// This event is raised when the position on the scrollbar has changed. - /// - public event EventHandler ChangedPosition; - - /// - /// The position, relative to , to set the scrollbar at. - /// - /// The position. - public int Position { - get => _position; - set { - _position = value; - if (IsInitialized) { - // We're not initialized so we can't do anything fancy. Just cache value. - SetPosition (value); - } + /// + /// The position, relative to , to set the scrollbar at. + /// + /// The position. + public int Position { + get => _position; + set { + _position = value; + if (IsInitialized) { + // We're not initialized so we can't do anything fancy. Just cache value. + SetPosition (value); } } + } - // Helper to assist Initialized event handler - private void SetPosition (int newPosition) - { - if (CanScroll (newPosition - _position, out int max, _vertical)) { - if (max == newPosition - _position) { - _position = newPosition; + // BUGBUG: v2 - for consistency this should be named "Parent" not "Host" + /// + /// Get or sets the view that host this + /// + public View Host { get; internal set; } + + /// + /// Represent a vertical or horizontal ScrollBarView other than this. + /// + public ScrollBarView OtherScrollBarView { + get => _otherScrollBarView; + set { + if (value != null && (value.IsVertical && _vertical || !value.IsVertical && !_vertical)) { + throw new ArgumentException ($"There is already a {(_vertical ? "vertical" : "horizontal")} ScrollBarView."); + } + _otherScrollBarView = value; + } + } + + // BUGBUG: v2 - Why can't we get rid of this and just use Visible? + /// + /// Gets or sets the visibility for the vertical or horizontal scroll indicator. + /// + /// true if show vertical or horizontal scroll indicator; otherwise, false. + public bool ShowScrollIndicator { + get => _showScrollIndicator; + set { + //if (value == showScrollIndicator) { + // return; + //} + + _showScrollIndicator = value; + if (IsInitialized) { + SetNeedsLayout (); + if (value) { + Visible = true; } else { - _position = Math.Max (_position + max, 0); + Visible = false; + Position = 0; } - } else if (max < 0) { - _position = Math.Max (_position + max, 0); + SetWidthHeight (); + } + } + } + + /// + /// Get or sets if the view-port is kept always visible in the area of this + /// + public bool KeepContentAlwaysInViewport { + get => _keepContentAlwaysInViewport; + set { + if (_keepContentAlwaysInViewport != value) { + _keepContentAlwaysInViewport = value; + var pos = 0; + if (value && !_vertical && _position + Host.Bounds.Width > _size) { + pos = _size - Host.Bounds.Width + (_showBothScrollIndicator ? 1 : 0); + } + if (value && _vertical && _position + Host.Bounds.Height > _size) { + pos = _size - Host.Bounds.Height + (_showBothScrollIndicator ? 1 : 0); + } + if (pos != 0) { + Position = pos; + } + if (OtherScrollBarView != null && OtherScrollBarView._keepContentAlwaysInViewport != value) { + OtherScrollBarView.KeepContentAlwaysInViewport = value; + } + if (pos == 0) { + Refresh (); + } + } + } + } + + /// + /// If true the vertical/horizontal scroll bars won't be showed if it's not needed. + /// + public bool AutoHideScrollBars { + get => _autoHideScrollBars; + set { + if (_autoHideScrollBars != value) { + _autoHideScrollBars = value; + SetNeedsDisplay (); + } + } + } + + void CreateBottomRightCorner () + { + if (Host != null && + (_contentBottomRightCorner == null && OtherScrollBarView == null || + _contentBottomRightCorner == null && OtherScrollBarView != null && OtherScrollBarView._contentBottomRightCorner == null)) { + + _contentBottomRightCorner = new View { + Id = "contentBottomRightCorner", + Visible = Host.Visible, + ClearOnVisibleFalse = false, + ColorScheme = ColorScheme + }; + if (_hosted) { + Host.SuperView.Add (_contentBottomRightCorner); } else { - _position = Math.Max (newPosition, 0); + Host.Add (_contentBottomRightCorner); } - OnChangedPosition (); - SetNeedsDisplay (); + _contentBottomRightCorner.X = Pos.Right (Host) - 1; + _contentBottomRightCorner.Y = Pos.Bottom (Host) - 1; + _contentBottomRightCorner.Width = 1; + _contentBottomRightCorner.Height = 1; + _contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick; + _contentBottomRightCorner.DrawContent += _contentBottomRightCorner_DrawContent; } + } - // BUGBUG: v2 - for consistency this should be named "Parent" not "Host" - /// - /// Get or sets the view that host this - /// - public View Host { get; internal set; } + void _contentBottomRightCorner_DrawContent (object sender, DrawEventArgs e) => Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ()); - /// - /// Represent a vertical or horizontal ScrollBarView other than this. - /// - public ScrollBarView OtherScrollBarView { - get => _otherScrollBarView; - set { - if (value != null && (value.IsVertical && _vertical || !value.IsVertical && !_vertical)) { - throw new ArgumentException ($"There is already a {(_vertical ? "vertical" : "horizontal")} ScrollBarView."); - } - _otherScrollBarView = value; + void Host_VisibleChanged (object sender, EventArgs e) + { + if (!Host.Visible) { + Visible = Host.Visible; + if (_otherScrollBarView != null) { + _otherScrollBarView.Visible = Visible; } - } - - // BUGBUG: v2 - Why can't we get rid of this and just use Visible? - /// - /// Gets or sets the visibility for the vertical or horizontal scroll indicator. - /// - /// true if show vertical or horizontal scroll indicator; otherwise, false. - public bool ShowScrollIndicator { - get => _showScrollIndicator; - set { - //if (value == showScrollIndicator) { - // return; - //} - - _showScrollIndicator = value; - if (IsInitialized) { - SetNeedsLayout (); - if (value) { - Visible = true; - } else { - Visible = false; - Position = 0; - } - SetWidthHeight (); - } - } - } - - /// - /// Get or sets if the view-port is kept always visible in the area of this - /// - public bool KeepContentAlwaysInViewport { - get { return _keepContentAlwaysInViewport; } - set { - if (_keepContentAlwaysInViewport != value) { - _keepContentAlwaysInViewport = value; - int pos = 0; - if (value && !_vertical && _position + Host.Bounds.Width > _size) { - pos = _size - Host.Bounds.Width + (_showBothScrollIndicator ? 1 : 0); - } - if (value && _vertical && _position + Host.Bounds.Height > _size) { - pos = _size - Host.Bounds.Height + (_showBothScrollIndicator ? 1 : 0); - } - if (pos != 0) { - Position = pos; - } - if (OtherScrollBarView != null && OtherScrollBarView._keepContentAlwaysInViewport != value) { - OtherScrollBarView.KeepContentAlwaysInViewport = value; - } - if (pos == 0) { - Refresh (); - } - } - } - } - - /// - /// If true the vertical/horizontal scroll bars won't be showed if it's not needed. - /// - public bool AutoHideScrollBars { - get => _autoHideScrollBars; - set { - if (_autoHideScrollBars != value) { - _autoHideScrollBars = value; - SetNeedsDisplay (); - } - } - } - - /// - /// Virtual method to invoke the action event. - /// - public virtual void OnChangedPosition () - { - ChangedPosition?.Invoke (this, EventArgs.Empty); - } - - /// - /// Only used for a hosted view that will update and redraw the scrollbars. - /// - public virtual void Refresh () - { + _contentBottomRightCorner.Visible = Visible; + } else { ShowHideScrollBars (); } + } - void ShowHideScrollBars (bool redraw = true) - { - if (!_hosted || (_hosted && !_autoHideScrollBars)) { - if (_contentBottomRightCorner != null && _contentBottomRightCorner.Visible) { - _contentBottomRightCorner.Visible = false; - } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null && _otherScrollBarView._contentBottomRightCorner.Visible) { - _otherScrollBarView._contentBottomRightCorner.Visible = false; - } - return; - } + void Host_EnabledChanged (object sender, EventArgs e) + { + Enabled = Host.Enabled; + if (_otherScrollBarView != null) { + _otherScrollBarView.Enabled = Enabled; + } + _contentBottomRightCorner.Enabled = Enabled; + } - var pending = CheckBothScrollBars (this); - if (_otherScrollBarView != null) { - CheckBothScrollBars (_otherScrollBarView, pending); - } + //private void Host_CanFocusChanged () + //{ + // CanFocus = Host.CanFocus; + // if (otherScrollBarView != null) { + // otherScrollBarView.CanFocus = CanFocus; + // } + //} + void ContentBottomRightCorner_MouseClick (object sender, MouseEventEventArgs me) + { + if (me.MouseEvent.Flags == MouseFlags.WheeledDown || + me.MouseEvent.Flags == MouseFlags.WheeledUp || + me.MouseEvent.Flags == MouseFlags.WheeledRight || + me.MouseEvent.Flags == MouseFlags.WheeledLeft) { + + MouseEvent (me.MouseEvent); + } else if (me.MouseEvent.Flags == MouseFlags.Button1Clicked) { + Host.SetFocus (); + } + + me.Handled = true; + } + + void SetInitialProperties (int size, int position, bool isVertical) + { + Id = "ScrollBarView"; + _vertical = isVertical; + _position = position; + _size = size; + WantContinuousButtonPressed = true; + ClearOnVisibleFalse = false; + + Added += (s, e) => CreateBottomRightCorner (); + + Initialized += (s, e) => { SetWidthHeight (); - SetRelativeLayout (SuperView?.Frame ?? Host.Frame); - if (_otherScrollBarView != null) { - OtherScrollBarView.SetRelativeLayout (SuperView?.Frame ?? Host.Frame); + SetRelativeLayout (SuperView?.Frame ?? Host?.Frame ?? Frame); + // BUGBUG: We're not supposed to use Id internally! + if (Id == "OtherScrollBarView" || OtherScrollBarView == null) { + // Only do this once if both scrollbars are enabled + ShowHideScrollBars (); } + SetPosition (position); + }; + } - if (_showBothScrollIndicator) { - if (_contentBottomRightCorner != null) { - _contentBottomRightCorner.Visible = true; - } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) { - _otherScrollBarView._contentBottomRightCorner.Visible = true; - } - } else if (!_showScrollIndicator) { - if (_contentBottomRightCorner != null) { - _contentBottomRightCorner.Visible = false; - } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) { - _otherScrollBarView._contentBottomRightCorner.Visible = false; - } - if (Application.MouseGrabView != null && Application.MouseGrabView == this) { - Application.UngrabMouse (); - } - } else if (_contentBottomRightCorner != null) { + /// + /// This event is raised when the position on the scrollbar has changed. + /// + public event EventHandler ChangedPosition; + + // Helper to assist Initialized event handler + void SetPosition (int newPosition) + { + if (CanScroll (newPosition - _position, out var max, _vertical)) { + if (max == newPosition - _position) { + _position = newPosition; + } else { + _position = Math.Max (_position + max, 0); + } + } else if (max < 0) { + _position = Math.Max (_position + max, 0); + } else { + _position = Math.Max (newPosition, 0); + } + OnChangedPosition (); + SetNeedsDisplay (); + } + + /// + /// Virtual method to invoke the action event. + /// + public virtual void OnChangedPosition () => ChangedPosition?.Invoke (this, EventArgs.Empty); + + /// + /// Only used for a hosted view that will update and redraw the scrollbars. + /// + public virtual void Refresh () => ShowHideScrollBars (); + + void ShowHideScrollBars (bool redraw = true) + { + if (!_hosted || _hosted && !_autoHideScrollBars) { + if (_contentBottomRightCorner != null && _contentBottomRightCorner.Visible) { + _contentBottomRightCorner.Visible = false; + } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null && _otherScrollBarView._contentBottomRightCorner.Visible) { + _otherScrollBarView._contentBottomRightCorner.Visible = false; + } + return; + } + + var pending = CheckBothScrollBars (this); + if (_otherScrollBarView != null) { + CheckBothScrollBars (_otherScrollBarView, pending); + } + + SetWidthHeight (); + SetRelativeLayout (SuperView?.Frame ?? Host.Frame); + if (_otherScrollBarView != null) { + OtherScrollBarView.SetRelativeLayout (SuperView?.Frame ?? Host.Frame); + } + + if (_showBothScrollIndicator) { + if (_contentBottomRightCorner != null) { + _contentBottomRightCorner.Visible = true; + } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) { + _otherScrollBarView._contentBottomRightCorner.Visible = true; + } + } else if (!_showScrollIndicator) { + if (_contentBottomRightCorner != null) { _contentBottomRightCorner.Visible = false; } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) { _otherScrollBarView._contentBottomRightCorner.Visible = false; } - if (Host?.Visible == true && _showScrollIndicator && !Visible) { - Visible = true; - } - if (Host?.Visible == true && _otherScrollBarView?._showScrollIndicator == true && !_otherScrollBarView.Visible) { - _otherScrollBarView.Visible = true; - } - - if (!redraw) { - return; - } - - if (_showScrollIndicator) { - Draw (); - } - if (_otherScrollBarView != null && _otherScrollBarView._showScrollIndicator) { - _otherScrollBarView.Draw (); - } - if (_contentBottomRightCorner != null && _contentBottomRightCorner.Visible) { - _contentBottomRightCorner.Draw (); - } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null && _otherScrollBarView._contentBottomRightCorner.Visible) { - _otherScrollBarView._contentBottomRightCorner.Draw (); - } - } - - bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false) - { - int barsize = scrollBarView._vertical ? scrollBarView.Bounds.Height : scrollBarView.Bounds.Width; - - if (barsize == 0 || barsize >= scrollBarView._size) { - if (scrollBarView._showScrollIndicator) { - scrollBarView.ShowScrollIndicator = false; - } - if (scrollBarView.Visible) { - scrollBarView.Visible = false; - } - } else if (barsize > 0 && barsize == scrollBarView._size && scrollBarView.OtherScrollBarView != null && pending) { - if (scrollBarView._showScrollIndicator) { - scrollBarView.ShowScrollIndicator = false; - } - if (scrollBarView.Visible) { - scrollBarView.Visible = false; - } - if (scrollBarView.OtherScrollBarView != null && scrollBarView._showBothScrollIndicator) { - scrollBarView.OtherScrollBarView.ShowScrollIndicator = false; - } - if (scrollBarView.OtherScrollBarView.Visible) { - scrollBarView.OtherScrollBarView.Visible = false; - } - } else if (barsize > 0 && barsize == _size && scrollBarView.OtherScrollBarView != null && !pending) { - pending = true; - } else { - if (scrollBarView.OtherScrollBarView != null && pending) { - if (!scrollBarView._showBothScrollIndicator) { - scrollBarView.OtherScrollBarView.ShowScrollIndicator = true; - } - if (!scrollBarView.OtherScrollBarView.Visible) { - scrollBarView.OtherScrollBarView.Visible = true; - } - } - if (!scrollBarView._showScrollIndicator) { - scrollBarView.ShowScrollIndicator = true; - } - if (!scrollBarView.Visible) { - scrollBarView.Visible = true; - } - } - - return pending; - } - - // BUGBUG: v2 - rationalize this with View.SetMinWidthHeight - void SetWidthHeight () - { - // BUGBUG: v2 - If Host is also the ScrollBarView's superview, this is all bogus because it's not - // supported that a view can reference it's superview's Dims. This code also assumes the host does - // not have a margin/borderframe/padding. - if (!IsInitialized) { - return; - } - - if (_showBothScrollIndicator) { - Width = _vertical ? 1 : Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1; - Height = _vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1; - - _otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1; - _otherScrollBarView.Height = _otherScrollBarView._vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1; - } else if (_showScrollIndicator) { - Width = _vertical ? 1 : Host != SuperView ? Dim.Width (Host) : Dim.Fill (); - Height = _vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () : 1; - } else if (_otherScrollBarView?._showScrollIndicator == true) { - _otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : Host != SuperView ? Dim.Width (Host) : Dim.Fill () - 0; - _otherScrollBarView.Height = _otherScrollBarView._vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () - 0 : 1; - } - } - - int _posTopTee; - int _posLeftTee; - int _posBottomTee; - int _posRightTee; - - /// - public override void OnDrawContent (Rect contentArea) - { - if (ColorScheme == null || ((!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible)) { - if ((!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible) { - ShowHideScrollBars (false); - } - return; - } - - if (Size == 0 || (_vertical && Bounds.Height == 0) || (!_vertical && Bounds.Width == 0)) { - return; - } - - Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ()); - - if (_vertical) { - if (Bounds.Right < Bounds.Width - 1) { - return; - } - - var col = Bounds.Width - 1; - var bh = Bounds.Height; - Rune special; - - if (bh < 4) { - var by1 = _position * bh / Size; - var by2 = (_position + bh) * bh / Size; - - Move (col, 0); - if (Bounds.Height == 1) { - Driver.AddRune (CM.Glyphs.Diamond); - } else { - Driver.AddRune (CM.Glyphs.UpArrow); - } - if (Bounds.Height == 3) { - Move (col, 1); - Driver.AddRune (CM.Glyphs.Diamond); - } - if (Bounds.Height > 1) { - Move (col, Bounds.Height - 1); - Driver.AddRune (CM.Glyphs.DownArrow); - } - } else { - bh -= 2; - var by1 = KeepContentAlwaysInViewport ? _position * bh / Size : _position * bh / (Size + bh); - var by2 = KeepContentAlwaysInViewport ? Math.Min (((_position + bh) * bh / Size) + 1, bh - 1) : (_position + bh) * bh / (Size + bh); - if (KeepContentAlwaysInViewport && by1 == by2) { - by1 = Math.Max (by1 - 1, 0); - } - - Move (col, 0); - Driver.AddRune (CM.Glyphs.UpArrow); - - bool hasTopTee = false; - bool hasDiamond = false; - bool hasBottomTee = false; - for (int y = 0; y < bh; y++) { - Move (col, y + 1); - if ((y < by1 || y > by2) && ((_position > 0 && !hasTopTee) || (hasTopTee && hasBottomTee))) { - special = CM.Glyphs.Stipple; - } else { - if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond) { - hasDiamond = true; - special = CM.Glyphs.Diamond; - } else { - if (y == by1 && !hasTopTee) { - hasTopTee = true; - _posTopTee = y; - special = CM.Glyphs.TopTee; - } else if ((_position == 0 && y == bh - 1 || y >= by2 || by2 == 0) && !hasBottomTee) { - hasBottomTee = true; - _posBottomTee = y; - special = CM.Glyphs.BottomTee; - } else { - special = CM.Glyphs.VLine; - } - } - } - Driver.AddRune (special); - } - if (!hasTopTee) { - Move (col, Bounds.Height - 2); - Driver.AddRune (CM.Glyphs.TopTee); - } - Move (col, Bounds.Height - 1); - Driver.AddRune (CM.Glyphs.DownArrow); - } - } else { - if (Bounds.Bottom < Bounds.Height - 1) { - return; - } - - var row = Bounds.Height - 1; - var bw = Bounds.Width; - Rune special; - - if (bw < 4) { - var bx1 = _position * bw / Size; - var bx2 = (_position + bw) * bw / Size; - - Move (0, row); - Driver.AddRune (CM.Glyphs.LeftArrow); - Driver.AddRune (CM.Glyphs.RightArrow); - } else { - bw -= 2; - var bx1 = KeepContentAlwaysInViewport ? _position * bw / Size : _position * bw / (Size + bw); - var bx2 = KeepContentAlwaysInViewport ? Math.Min (((_position + bw) * bw / Size) + 1, bw - 1) : (_position + bw) * bw / (Size + bw); - if (KeepContentAlwaysInViewport && bx1 == bx2) { - bx1 = Math.Max (bx1 - 1, 0); - } - - Move (0, row); - Driver.AddRune (CM.Glyphs.LeftArrow); - - bool hasLeftTee = false; - bool hasDiamond = false; - bool hasRightTee = false; - for (int x = 0; x < bw; x++) { - if ((x < bx1 || x >= bx2 + 1) && ((_position > 0 && !hasLeftTee) || (hasLeftTee && hasRightTee))) { - special = CM.Glyphs.Stipple; - } else { - if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond) { - hasDiamond = true; - special = CM.Glyphs.Diamond; - } else { - if (x == bx1 && !hasLeftTee) { - hasLeftTee = true; - _posLeftTee = x; - special = CM.Glyphs.LeftTee; - } else if ((_position == 0 && x == bw - 1 || x >= bx2 || bx2 == 0) && !hasRightTee) { - hasRightTee = true; - _posRightTee = x; - special = CM.Glyphs.RightTee; - } else { - special = CM.Glyphs.HLine; - } - } - } - Driver.AddRune (special); - } - if (!hasLeftTee) { - Move (Bounds.Width - 2, row); - Driver.AddRune (CM.Glyphs.LeftTee); - } - - Driver.AddRune (CM.Glyphs.RightArrow); - } - } - } - - int _lastLocation = -1; - int _posBarOffset; - - /// - public override bool MouseEvent (MouseEvent mouseEvent) - { - if (mouseEvent.Flags != MouseFlags.Button1Pressed && mouseEvent.Flags != MouseFlags.Button1DoubleClicked && - !mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && - mouseEvent.Flags != MouseFlags.Button1Released && mouseEvent.Flags != MouseFlags.WheeledDown && - mouseEvent.Flags != MouseFlags.WheeledUp && mouseEvent.Flags != MouseFlags.WheeledRight && - mouseEvent.Flags != MouseFlags.WheeledLeft && mouseEvent.Flags != MouseFlags.Button1TripleClicked) { - - return false; - } - - if (!Host.CanFocus) { - return true; - } - if (Host?.HasFocus == false) { - Host.SetFocus (); - } - - int location = _vertical ? mouseEvent.Y : mouseEvent.X; - int barsize = _vertical ? Bounds.Height : Bounds.Width; - int posTopLeftTee = _vertical ? _posTopTee + 1 : _posLeftTee + 1; - int posBottomRightTee = _vertical ? _posBottomTee + 1 : _posRightTee + 1; - barsize -= 2; - var pos = Position; - - if (mouseEvent.Flags != MouseFlags.Button1Released - && (Application.MouseGrabView == null || Application.MouseGrabView != this)) { - Application.GrabMouse (this); - } else if (mouseEvent.Flags == MouseFlags.Button1Released && Application.MouseGrabView != null && Application.MouseGrabView == this) { - _lastLocation = -1; + if (Application.MouseGrabView != null && Application.MouseGrabView == this) { Application.UngrabMouse (); - return true; } - if (_showScrollIndicator && (mouseEvent.Flags == MouseFlags.WheeledDown || mouseEvent.Flags == MouseFlags.WheeledUp || - mouseEvent.Flags == MouseFlags.WheeledRight || mouseEvent.Flags == MouseFlags.WheeledLeft)) { - - return Host.MouseEvent (mouseEvent); - } - - if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == 0) { - if (pos > 0) { - Position = pos - 1; - } - } else if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == barsize + 1) { - if (CanScroll (1, out _, _vertical)) { - Position = pos + 1; - } - } else if (location > 0 && location < barsize + 1) { - //var b1 = pos * (Size > 0 ? barsize / Size : 0); - //var b2 = Size > 0 - // ? (KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size) - // : 0; - //if (KeepContentAlwaysInViewport && b1 == b2) { - // b1 = Math.Max (b1 - 1, 0); - //} - - if (_lastLocation > -1 || (location >= posTopLeftTee && location <= posBottomRightTee - && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))) { - if (_lastLocation == -1) { - _lastLocation = location; - _posBarOffset = _keepContentAlwaysInViewport ? Math.Max (location - posTopLeftTee, 1) : 0; - return true; - } - - if (location > _lastLocation) { - if (location - _posBarOffset < barsize) { - var np = ((location - _posBarOffset) * Size / barsize) + (Size / barsize); - if (CanScroll (np - pos, out int nv, _vertical)) { - Position = pos + nv; - } - } else if (CanScroll (Size - pos, out int nv, _vertical)) { - Position = Math.Min (pos + nv, Size); - } - } else if (location < _lastLocation) { - if (location - _posBarOffset > 0) { - var np = ((location - _posBarOffset) * Size / barsize) - (Size / barsize); - if (CanScroll (np - pos, out int nv, _vertical)) { - Position = pos + nv; - } - } else { - Position = 0; - } - } else if (location - _posBarOffset >= barsize && posBottomRightTee - posTopLeftTee >= 3 && CanScroll (Size - pos, out int nv, _vertical)) { - Position = Math.Min (pos + nv, Size); - } else if (location - _posBarOffset >= barsize - 1 && posBottomRightTee - posTopLeftTee <= 3 && CanScroll (Size - pos, out nv, _vertical)) { - Position = Math.Min (pos + nv, Size); - } else if (location - _posBarOffset <= 0 && posBottomRightTee - posTopLeftTee <= 3) { - Position = 0; - } - } else if (location > posBottomRightTee) { - if (CanScroll (barsize, out int nv, _vertical)) { - Position = pos + nv; - } - } else if (location < posTopLeftTee) { - if (CanScroll (-barsize, out int nv, _vertical)) { - Position = pos + nv; - } - } else if (location == 1 && posTopLeftTee <= 3) { - Position = 0; - } else if (location == barsize) { - if (CanScroll (Size - pos, out int nv, _vertical)) { - Position = Math.Min (pos + nv, Size); - } - } - } - - return true; + } else if (_contentBottomRightCorner != null) { + _contentBottomRightCorner.Visible = false; + } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) { + _otherScrollBarView._contentBottomRightCorner.Visible = false; + } + if (Host?.Visible == true && _showScrollIndicator && !Visible) { + Visible = true; + } + if (Host?.Visible == true && _otherScrollBarView?._showScrollIndicator == true && !_otherScrollBarView.Visible) { + _otherScrollBarView.Visible = true; } - internal bool CanScroll (int n, out int max, bool isVertical = false) - { - if (Host?.Bounds.IsEmpty != false) { - max = 0; - return false; + if (!redraw) { + return; + } + + if (_showScrollIndicator) { + Draw (); + } + if (_otherScrollBarView != null && _otherScrollBarView._showScrollIndicator) { + _otherScrollBarView.Draw (); + } + if (_contentBottomRightCorner != null && _contentBottomRightCorner.Visible) { + _contentBottomRightCorner.Draw (); + } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null && _otherScrollBarView._contentBottomRightCorner.Visible) { + _otherScrollBarView._contentBottomRightCorner.Draw (); + } + } + + bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false) + { + var barsize = scrollBarView._vertical ? scrollBarView.Bounds.Height : scrollBarView.Bounds.Width; + + if (barsize == 0 || barsize >= scrollBarView._size) { + if (scrollBarView._showScrollIndicator) { + scrollBarView.ShowScrollIndicator = false; } - int s = GetBarsize (isVertical); - var newSize = Math.Max (Math.Min (_size - s, _position + n), 0); - max = _size > s + newSize ? (newSize == 0 ? -_position : n) : _size - (s + _position) - 1; - if (_size >= s + newSize && max != 0) { - return true; + if (scrollBarView.Visible) { + scrollBarView.Visible = false; } + } else if (barsize > 0 && barsize == scrollBarView._size && scrollBarView.OtherScrollBarView != null && pending) { + if (scrollBarView._showScrollIndicator) { + scrollBarView.ShowScrollIndicator = false; + } + if (scrollBarView.Visible) { + scrollBarView.Visible = false; + } + if (scrollBarView.OtherScrollBarView != null && scrollBarView._showBothScrollIndicator) { + scrollBarView.OtherScrollBarView.ShowScrollIndicator = false; + } + if (scrollBarView.OtherScrollBarView.Visible) { + scrollBarView.OtherScrollBarView.Visible = false; + } + } else if (barsize > 0 && barsize == _size && scrollBarView.OtherScrollBarView != null && !pending) { + pending = true; + } else { + if (scrollBarView.OtherScrollBarView != null && pending) { + if (!scrollBarView._showBothScrollIndicator) { + scrollBarView.OtherScrollBarView.ShowScrollIndicator = true; + } + if (!scrollBarView.OtherScrollBarView.Visible) { + scrollBarView.OtherScrollBarView.Visible = true; + } + } + if (!scrollBarView._showScrollIndicator) { + scrollBarView.ShowScrollIndicator = true; + } + if (!scrollBarView.Visible) { + scrollBarView.Visible = true; + } + } + + return pending; + } + + // BUGBUG: v2 - rationalize this with View.SetMinWidthHeight + void SetWidthHeight () + { + // BUGBUG: v2 - If Host is also the ScrollBarView's superview, this is all bogus because it's not + // supported that a view can reference it's superview's Dims. This code also assumes the host does + // not have a margin/borderframe/padding. + if (!IsInitialized) { + return; + } + + if (_showBothScrollIndicator) { + Width = _vertical ? 1 : Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1; + Height = _vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1; + + _otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1; + _otherScrollBarView.Height = _otherScrollBarView._vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1; + } else if (_showScrollIndicator) { + Width = _vertical ? 1 : Host != SuperView ? Dim.Width (Host) : Dim.Fill (); + Height = _vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () : 1; + } else if (_otherScrollBarView?._showScrollIndicator == true) { + _otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : Host != SuperView ? Dim.Width (Host) : Dim.Fill () - 0; + _otherScrollBarView.Height = _otherScrollBarView._vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () - 0 : 1; + } + } + + /// + public override void OnDrawContent (Rect contentArea) + { + if (ColorScheme == null || (!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible) { + if ((!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible) { + ShowHideScrollBars (false); + } + return; + } + + if (Size == 0 || _vertical && Bounds.Height == 0 || !_vertical && Bounds.Width == 0) { + return; + } + + Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ()); + + if (_vertical) { + if (Bounds.Right < Bounds.Width - 1) { + return; + } + + var col = Bounds.Width - 1; + var bh = Bounds.Height; + Rune special; + + if (bh < 4) { + var by1 = _position * bh / Size; + var by2 = (_position + bh) * bh / Size; + + Move (col, 0); + if (Bounds.Height == 1) { + Driver.AddRune (Glyphs.Diamond); + } else { + Driver.AddRune (Glyphs.UpArrow); + } + if (Bounds.Height == 3) { + Move (col, 1); + Driver.AddRune (Glyphs.Diamond); + } + if (Bounds.Height > 1) { + Move (col, Bounds.Height - 1); + Driver.AddRune (Glyphs.DownArrow); + } + } else { + bh -= 2; + var by1 = KeepContentAlwaysInViewport ? _position * bh / Size : _position * bh / (Size + bh); + var by2 = KeepContentAlwaysInViewport ? Math.Min ((_position + bh) * bh / Size + 1, bh - 1) : (_position + bh) * bh / (Size + bh); + if (KeepContentAlwaysInViewport && by1 == by2) { + by1 = Math.Max (by1 - 1, 0); + } + + Move (col, 0); + Driver.AddRune (Glyphs.UpArrow); + + var hasTopTee = false; + var hasDiamond = false; + var hasBottomTee = false; + for (var y = 0; y < bh; y++) { + Move (col, y + 1); + if ((y < by1 || y > by2) && (_position > 0 && !hasTopTee || hasTopTee && hasBottomTee)) { + special = Glyphs.Stipple; + } else { + if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond) { + hasDiamond = true; + special = Glyphs.Diamond; + } else { + if (y == by1 && !hasTopTee) { + hasTopTee = true; + _posTopTee = y; + special = Glyphs.TopTee; + } else if ((_position == 0 && y == bh - 1 || y >= by2 || by2 == 0) && !hasBottomTee) { + hasBottomTee = true; + _posBottomTee = y; + special = Glyphs.BottomTee; + } else { + special = Glyphs.VLine; + } + } + } + Driver.AddRune (special); + } + if (!hasTopTee) { + Move (col, Bounds.Height - 2); + Driver.AddRune (Glyphs.TopTee); + } + Move (col, Bounds.Height - 1); + Driver.AddRune (Glyphs.DownArrow); + } + } else { + if (Bounds.Bottom < Bounds.Height - 1) { + return; + } + + var row = Bounds.Height - 1; + var bw = Bounds.Width; + Rune special; + + if (bw < 4) { + var bx1 = _position * bw / Size; + var bx2 = (_position + bw) * bw / Size; + + Move (0, row); + Driver.AddRune (Glyphs.LeftArrow); + Driver.AddRune (Glyphs.RightArrow); + } else { + bw -= 2; + var bx1 = KeepContentAlwaysInViewport ? _position * bw / Size : _position * bw / (Size + bw); + var bx2 = KeepContentAlwaysInViewport ? Math.Min ((_position + bw) * bw / Size + 1, bw - 1) : (_position + bw) * bw / (Size + bw); + if (KeepContentAlwaysInViewport && bx1 == bx2) { + bx1 = Math.Max (bx1 - 1, 0); + } + + Move (0, row); + Driver.AddRune (Glyphs.LeftArrow); + + var hasLeftTee = false; + var hasDiamond = false; + var hasRightTee = false; + for (var x = 0; x < bw; x++) { + if ((x < bx1 || x >= bx2 + 1) && (_position > 0 && !hasLeftTee || hasLeftTee && hasRightTee)) { + special = Glyphs.Stipple; + } else { + if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond) { + hasDiamond = true; + special = Glyphs.Diamond; + } else { + if (x == bx1 && !hasLeftTee) { + hasLeftTee = true; + _posLeftTee = x; + special = Glyphs.LeftTee; + } else if ((_position == 0 && x == bw - 1 || x >= bx2 || bx2 == 0) && !hasRightTee) { + hasRightTee = true; + _posRightTee = x; + special = Glyphs.RightTee; + } else { + special = Glyphs.HLine; + } + } + } + Driver.AddRune (special); + } + if (!hasLeftTee) { + Move (Bounds.Width - 2, row); + Driver.AddRune (Glyphs.LeftTee); + } + + Driver.AddRune (Glyphs.RightArrow); + } + } + } + + /// + public override bool MouseEvent (MouseEvent mouseEvent) + { + if (mouseEvent.Flags != MouseFlags.Button1Pressed && + mouseEvent.Flags != MouseFlags.Button1DoubleClicked && + !mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && + mouseEvent.Flags != MouseFlags.Button1Released && + mouseEvent.Flags != MouseFlags.WheeledDown && + mouseEvent.Flags != MouseFlags.WheeledUp && + mouseEvent.Flags != MouseFlags.WheeledRight && + mouseEvent.Flags != MouseFlags.WheeledLeft && + mouseEvent.Flags != MouseFlags.Button1TripleClicked) { + return false; } - int GetBarsize (bool isVertical) - { - if (Host?.Bounds.IsEmpty != false) { - return 0; + if (!Host.CanFocus) { + return true; + } + if (Host?.HasFocus == false) { + Host.SetFocus (); + } + + var location = _vertical ? mouseEvent.Y : mouseEvent.X; + var barsize = _vertical ? Bounds.Height : Bounds.Width; + var posTopLeftTee = _vertical ? _posTopTee + 1 : _posLeftTee + 1; + var posBottomRightTee = _vertical ? _posBottomTee + 1 : _posRightTee + 1; + barsize -= 2; + var pos = Position; + + if (mouseEvent.Flags != MouseFlags.Button1Released && (Application.MouseGrabView == null || Application.MouseGrabView != this)) { + Application.GrabMouse (this); + } else if (mouseEvent.Flags == MouseFlags.Button1Released && Application.MouseGrabView != null && Application.MouseGrabView == this) { + _lastLocation = -1; + Application.UngrabMouse (); + return true; + } + if (_showScrollIndicator && + (mouseEvent.Flags == MouseFlags.WheeledDown || + mouseEvent.Flags == MouseFlags.WheeledUp || + mouseEvent.Flags == MouseFlags.WheeledRight || + mouseEvent.Flags == MouseFlags.WheeledLeft)) { + + return Host.MouseEvent (mouseEvent); + } + + if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == 0) { + if (pos > 0) { + Position = pos - 1; + } + } else if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == barsize + 1) { + if (CanScroll (1, out _, _vertical)) { + Position = pos + 1; + } + } else if (location > 0 && location < barsize + 1) { + //var b1 = pos * (Size > 0 ? barsize / Size : 0); + //var b2 = Size > 0 + // ? (KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size) + // : 0; + //if (KeepContentAlwaysInViewport && b1 == b2) { + // b1 = Math.Max (b1 - 1, 0); + //} + + if (_lastLocation > -1 || location >= posTopLeftTee && location <= posBottomRightTee && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { + if (_lastLocation == -1) { + _lastLocation = location; + _posBarOffset = _keepContentAlwaysInViewport ? Math.Max (location - posTopLeftTee, 1) : 0; + return true; + } + + if (location > _lastLocation) { + if (location - _posBarOffset < barsize) { + var np = (location - _posBarOffset) * Size / barsize + Size / barsize; + if (CanScroll (np - pos, out var nv, _vertical)) { + Position = pos + nv; + } + } else if (CanScroll (Size - pos, out var nv, _vertical)) { + Position = Math.Min (pos + nv, Size); + } + } else if (location < _lastLocation) { + if (location - _posBarOffset > 0) { + var np = (location - _posBarOffset) * Size / barsize - Size / barsize; + if (CanScroll (np - pos, out var nv, _vertical)) { + Position = pos + nv; + } + } else { + Position = 0; + } + } else if (location - _posBarOffset >= barsize && posBottomRightTee - posTopLeftTee >= 3 && CanScroll (Size - pos, out var nv, _vertical)) { + Position = Math.Min (pos + nv, Size); + } else if (location - _posBarOffset >= barsize - 1 && posBottomRightTee - posTopLeftTee <= 3 && CanScroll (Size - pos, out nv, _vertical)) { + Position = Math.Min (pos + nv, Size); + } else if (location - _posBarOffset <= 0 && posBottomRightTee - posTopLeftTee <= 3) { + Position = 0; + } + } else if (location > posBottomRightTee) { + if (CanScroll (barsize, out var nv, _vertical)) { + Position = pos + nv; + } + } else if (location < posTopLeftTee) { + if (CanScroll (-barsize, out var nv, _vertical)) { + Position = pos + nv; + } + } else if (location == 1 && posTopLeftTee <= 3) { + Position = 0; + } else if (location == barsize) { + if (CanScroll (Size - pos, out var nv, _vertical)) { + Position = Math.Min (pos + nv, Size); + } } - return isVertical ? - (KeepContentAlwaysInViewport ? Host.Bounds.Height + (_showBothScrollIndicator ? -2 : -1) : 0) : - (KeepContentAlwaysInViewport ? Host.Bounds.Width + (_showBothScrollIndicator ? -2 : -1) : 0); } - /// - public override bool OnEnter (View view) - { - Application.Driver.SetCursorVisibility (CursorVisibility.Invisible); - - return base.OnEnter (view); - } + return true; } -} + + internal bool CanScroll (int n, out int max, bool isVertical = false) + { + if (Host?.Bounds.IsEmpty != false) { + max = 0; + return false; + } + var s = GetBarsize (isVertical); + var newSize = Math.Max (Math.Min (_size - s, _position + n), 0); + max = _size > s + newSize ? newSize == 0 ? -_position : n : _size - (s + _position) - 1; + if (_size >= s + newSize && max != 0) { + return true; + } + return false; + } + + int GetBarsize (bool isVertical) + { + if (Host?.Bounds.IsEmpty != false) { + return 0; + } + return isVertical ? + KeepContentAlwaysInViewport ? Host.Bounds.Height + (_showBothScrollIndicator ? -2 : -1) : 0 : + KeepContentAlwaysInViewport ? Host.Bounds.Width + (_showBothScrollIndicator ? -2 : -1) : 0; + } + + /// + public override bool OnEnter (View view) + { + Application.Driver.SetCursorVisibility (CursorVisibility.Invisible); + + return base.OnEnter (view); + } +} \ No newline at end of file diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 024942f11..f8354eee4 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -227,7 +227,6 @@ namespace Terminal.Gui { if (Frame.Height > 1) { Height = 1; } - Adjust (); } diff --git a/UICatalog/Scenarios/TabViewExample.cs b/UICatalog/Scenarios/TabViewExample.cs index 2ef1bb235..e847afb23 100644 --- a/UICatalog/Scenarios/TabViewExample.cs +++ b/UICatalog/Scenarios/TabViewExample.cs @@ -59,7 +59,7 @@ namespace UICatalog.Scenarios { }; tabView.AddTab (new Tab ("Tab1", new Label ("hodor!")), false); - tabView.AddTab (new Tab ("Tab2", new Label ("durdur")), 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 ( diff --git a/UICatalog/Scenarios/Text.cs b/UICatalog/Scenarios/Text.cs index 48320ee4b..b58007e11 100644 --- a/UICatalog/Scenarios/Text.cs +++ b/UICatalog/Scenarios/Text.cs @@ -233,7 +233,7 @@ namespace UICatalog.Scenarios { }; var appendAutocompleteTextField = new TextField () { X = Pos.Right (labelAppendAutocomplete), - Y = labelAppendAutocomplete.Y, + Y = Pos.Bottom (labelAppendAutocomplete), Width = Dim.Fill () }; appendAutocompleteTextField.Autocomplete = new AppendAutocomplete (appendAutocompleteTextField); diff --git a/UnitTests/Dialogs/DialogTests.cs b/UnitTests/Dialogs/DialogTests.cs index 1e97ccdd5..32a81a43c 100644 --- a/UnitTests/Dialogs/DialogTests.cs +++ b/UnitTests/Dialogs/DialogTests.cs @@ -181,18 +181,33 @@ namespace Terminal.Gui.DialogTests { // This is because of PostionTopLevels and EnsureVisibleBounds Assert.Equal (new Point (3, 2), d.Frame.Location); - Assert.Equal (new Size (17, 8), d.Frame.Size); + // #3127: Before + // Assert.Equal (new Size (17, 8), d.Frame.Size); + // TestHelpers.AssertDriverContentsWithFrameAre (@" + //╔══════════════════╗ + //║ ║ + //║ ┌───────────────┐ + //║ │ │ + //║ │ │ + //║ │ │ + //║ │ │ + //║ │ │ + //║ │ │ + //╚══└───────────────┘", output); + + // #3127: After: Because Toplevel is now Width/Height = Dim.Filll + Assert.Equal (new Size (15, 6), d.Frame.Size); TestHelpers.AssertDriverContentsWithFrameAre (@" ╔══════════════════╗ ║ ║ -║ ┌───────────────┐ -║ │ │ -║ │ │ -║ │ │ -║ │ │ -║ │ │ -║ │ │ -╚══└───────────────┘", output); +║ ┌─────────────┐ ║ +║ │ │ ║ +║ │ │ ║ +║ │ │ ║ +║ │ │ ║ +║ └─────────────┘ ║ +║ ║ +╚══════════════════╝", output); } else if (iterations > 0) { Application.RequestStop (); @@ -971,20 +986,11 @@ namespace Terminal.Gui.DialogTests { Application.Refresh (); Assert.Equal (new Rect (10, 0, 6, 1), btn.Frame); Assert.Equal (new Rect (0, 0, 6, 1), btn.Bounds); - // #3127: Before: This test was clearly wrong before. The math above is correct, but the result is wrong. - // var expected = @$" - //┌──────────────────┐ - //│┌────────────────┐│ - //││23456789 {b}││ - //│└────────────────┘│ - //└──────────────────┘"; - // #3127: After: This test was clearly wrong before. The math above is correct, but the result is wrong. - // See also `PosDimFunction` in SetRelativeLayoutTests.cs var expected = @$" ┌──────────────────┐ │┌────────────────┐│ -││012345678 {b}││ +││23456789 {b}││ │└────────────────┘│ └──────────────────┘"; @@ -998,7 +1004,7 @@ namespace Terminal.Gui.DialogTests { expected = @$" ┌──────────────────┐ │┌────────────────┐│ -││012345678 {b}││ +││23456789 {b}││ │└────────────────┘│ └──────────────────┘"; _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); diff --git a/UnitTests/View/ViewTests.cs b/UnitTests/View/ViewTests.cs index 6e6debc5a..d008c81e1 100644 --- a/UnitTests/View/ViewTests.cs +++ b/UnitTests/View/ViewTests.cs @@ -265,20 +265,20 @@ namespace Terminal.Gui.ViewTests { int tc = 0, wc = 0, v1c = 0, v2c = 0, sv1c = 0; winAddedToTop.Added += (s, e) => { - Assert.Equal (e.Parent.Bounds.Width, winAddedToTop.Frame.Width); - Assert.Equal (e.Parent.Bounds.Height, winAddedToTop.Frame.Height); + Assert.Equal (e.Parent.Frame.Width, winAddedToTop.Frame.Width); + Assert.Equal (e.Parent.Frame.Height, winAddedToTop.Frame.Height); }; v1AddedToWin.Added += (s, e) => { - Assert.Equal (e.Parent.Bounds.Width, v1AddedToWin.Frame.Width); - Assert.Equal (e.Parent.Bounds.Height, v1AddedToWin.Frame.Height); + Assert.Equal (e.Parent.Frame.Width, v1AddedToWin.Frame.Width); + Assert.Equal (e.Parent.Frame.Height, v1AddedToWin.Frame.Height); }; v2AddedToWin.Added += (s, e) => { - Assert.Equal (e.Parent.Bounds.Width, v2AddedToWin.Frame.Width); - Assert.Equal (e.Parent.Bounds.Height, v2AddedToWin.Frame.Height); + Assert.Equal (e.Parent.Frame.Width, v2AddedToWin.Frame.Width); + Assert.Equal (e.Parent.Frame.Height, v2AddedToWin.Frame.Height); }; svAddedTov1.Added += (s, e) => { - Assert.Equal (e.Parent.Bounds.Width, svAddedTov1.Frame.Width); - Assert.Equal (e.Parent.Bounds.Height, svAddedTov1.Frame.Height); + Assert.Equal (e.Parent.Frame.Width, svAddedTov1.Frame.Width); + Assert.Equal (e.Parent.Frame.Height, svAddedTov1.Frame.Height); }; top.Initialized += (s, e) => { diff --git a/UnitTests/Views/ScrollViewTests.cs b/UnitTests/Views/ScrollViewTests.cs index c5cf6bc1e..0b55e5538 100644 --- a/UnitTests/Views/ScrollViewTests.cs +++ b/UnitTests/Views/ScrollViewTests.cs @@ -188,6 +188,8 @@ namespace Terminal.Gui.ViewsTests { Application.Top.Add (sv); Application.Begin (Application.Top); + Assert.Equal (new Rect (0, 0, 10, 10), sv.Bounds); + Assert.False (sv.AutoHideScrollBars); Assert.True (sv.ShowHorizontalScrollIndicator); Assert.True (sv.ShowVerticalScrollIndicator); @@ -206,7 +208,9 @@ namespace Terminal.Gui.ViewsTests { ", output); sv.ShowHorizontalScrollIndicator = false; + Assert.Equal (new Rect (0, 0, 10, 10), sv.Bounds); sv.ShowVerticalScrollIndicator = true; + Assert.Equal (new Rect (0, 0, 10, 10), sv.Bounds); Assert.False (sv.AutoHideScrollBars); Assert.False (sv.ShowHorizontalScrollIndicator); @@ -220,6 +224,7 @@ namespace Terminal.Gui.ViewsTests { │ │ │ + │ ┴ ▼ ", output); @@ -241,7 +246,7 @@ namespace Terminal.Gui.ViewsTests { -◄├─────┤► +◄├──────┤► ", output); sv.ShowHorizontalScrollIndicator = false; diff --git a/UnitTests/Views/TabViewTests.cs b/UnitTests/Views/TabViewTests.cs index ea049a299..f62b95789 100644 --- a/UnitTests/Views/TabViewTests.cs +++ b/UnitTests/Views/TabViewTests.cs @@ -33,7 +33,7 @@ namespace Terminal.Gui.ViewsTests { tv.BeginInit (); tv.EndInit (); tv.ColorScheme = new ColorScheme (); - tv.AddTab (tab1 = new Tab ("Tab1", new TextField ("hi")), false); + tv.AddTab (tab1 = new Tab ("Tab1", new TextField ("hi") { Width = 2 }), false); tv.AddTab (tab2 = new Tab ("Tab2", new Label ("hi2")), false); return tv; }