diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs index 0950f872bb..6aa2088e5a 100644 --- a/Terminal.Gui/Drawing/Thickness.cs +++ b/Terminal.Gui/Drawing/Thickness.cs @@ -236,7 +236,7 @@ public Rectangle GetInside (Rectangle rect) } /// - /// Gets the total width of the left and right sides of the rectangle. Sets the width of the left and rigth sides + /// Gets the total width of the left and right sides of the rectangle. Sets the width of the left and right sides /// of the rectangle to half the specified value. /// public int Horizontal diff --git a/Terminal.Gui/Input/Responder.cs b/Terminal.Gui/Input/Responder.cs deleted file mode 100644 index 43cc082403..0000000000 --- a/Terminal.Gui/Input/Responder.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Reflection; - -namespace Terminal.Gui; - -/// Responder base class implemented by objects that want to participate on keyboard and mouse input. -public class Responder : IDisposable -{ - private bool _disposedValue; - - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resource. - - public void Dispose () - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Disposing?.Invoke (this, EventArgs.Empty); - Dispose (true); - GC.SuppressFinalize (this); -#if DEBUG_IDISPOSABLE - WasDisposed = true; - - foreach (Responder instance in Instances.Where (x => x.WasDisposed).ToList ()) - { - Instances.Remove (instance); - } -#endif - } - - /// Event raised when has been called to signal that this object is being disposed. - [CanBeNull] - public event EventHandler Disposing; - - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// If disposing equals true, the method has been called directly or indirectly by a user's code. Managed and - /// unmanaged resources can be disposed. If disposing equals false, the method has been called by the runtime from - /// inside the finalizer and you should not reference other objects. Only unmanaged resources can be disposed. - /// - /// - protected virtual void Dispose (bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - // TODO: dispose managed state (managed objects) - } - - _disposedValue = true; - } - } - - // TODO: v2 - nuke this - /// Utilty function to determine is overridden in the . - /// The view. - /// The method name. - /// if it's overridden, otherwise. - internal static bool IsOverridden (Responder subclass, string method) - { - MethodInfo m = subclass.GetType () - .GetMethod ( - method, - BindingFlags.Instance - | BindingFlags.Public - | BindingFlags.NonPublic - | BindingFlags.DeclaredOnly - ); - - if (m is null) - { - return false; - } - - return m.GetBaseDefinition ().DeclaringType != m.DeclaringType; - } - -#if DEBUG_IDISPOSABLE - /// For debug purposes to verify objects are being disposed properly - public bool WasDisposed; - - /// For debug purposes to verify objects are being disposed properly - public int DisposedCount = 0; - - /// For debug purposes - public static List Instances = new (); - - /// For debug purposes - public Responder () { Instances.Add (this); } -#endif -} diff --git a/Terminal.Gui/View/View.Content.cs b/Terminal.Gui/View/View.Content.cs index 5f2818c783..d1bfafc794 100644 --- a/Terminal.Gui/View/View.Content.cs +++ b/Terminal.Gui/View/View.Content.cs @@ -218,6 +218,7 @@ public ViewportSettings ViewportSettings { // Force set Viewport to cause settings to be applied as needed SetViewport (Viewport); + SetScrollBarsKeepContentInAllViewport (_viewportSettings); } } } @@ -325,6 +326,10 @@ private void SetViewport (Rectangle viewport) Size = newSize }; + OnViewportChanged (new (IsInitialized ? Viewport : Rectangle.Empty, oldViewport)); + + return; + void ApplySettings (ref Rectangle newViewport) { if (!ViewportSettings.HasFlag (ViewportSettings.AllowXGreaterThanContentWidth)) diff --git a/Terminal.Gui/View/View.Drawing.Clipping.cs b/Terminal.Gui/View/View.Drawing.Clipping.cs index 2996751f93..30f8b703c6 100644 --- a/Terminal.Gui/View/View.Drawing.Clipping.cs +++ b/Terminal.Gui/View/View.Drawing.Clipping.cs @@ -104,7 +104,7 @@ public static void SetClip (Region? region) if (this is Adornment adornment && adornment.Thickness != Thickness.Empty) { // Ensure adornments can't draw outside their thickness - frameRegion.Exclude (adornment.Thickness.GetInside (Frame)); + frameRegion.Exclude (adornment.Thickness.GetInside (FrameToScreen())); } SetClip (frameRegion); diff --git a/Terminal.Gui/View/View.Layout.cs b/Terminal.Gui/View/View.Layout.cs index f317b5bcb1..67e18955e3 100644 --- a/Terminal.Gui/View/View.Layout.cs +++ b/Terminal.Gui/View/View.Layout.cs @@ -96,11 +96,23 @@ private bool SetFrame (in Rectangle frame) SetNeedsLayout (); // BUGBUG: When SetFrame is called from Frame_set, this event gets raised BEFORE OnResizeNeeded. Is that OK? - OnViewportChanged (new (IsInitialized ? Viewport : Rectangle.Empty, oldViewport)); - + OnFrameChanged (in frame); + FrameChanged?.Invoke (this, new (in frame)); return true; } + /// + /// Called when changes. + /// + /// The new Frame. + protected virtual void OnFrameChanged (in Rectangle frame) { } + + /// + /// Raised when the changes. This event is raised after the has been + /// updated. + /// + public event EventHandler>? FrameChanged; + /// Gets the with a screen-relative location. /// The location and size of the view in screen-relative coordinates. public virtual Rectangle FrameToScreen () diff --git a/Terminal.Gui/View/View.ScrollBars.cs b/Terminal.Gui/View/View.ScrollBars.cs new file mode 100644 index 0000000000..d3e56344fd --- /dev/null +++ b/Terminal.Gui/View/View.ScrollBars.cs @@ -0,0 +1,215 @@ +#nullable enable +namespace Terminal.Gui; + +public partial class View +{ + private Lazy _horizontalScrollBar; + private Lazy _verticalScrollBar; + + /// + /// Initializes the ScrollBars of the View. Called by the constructor. + /// + private void SetupScrollBars () + { + _horizontalScrollBar = new ( + () => + { + var scrollBar = new ScrollBar + { + Orientation = Orientation.Horizontal, + X = 0, + Y = Pos.AnchorEnd (), + Width = Dim.Fill ( + Dim.Func ( + () => + { + if (_verticalScrollBar.IsValueCreated) + { + return _verticalScrollBar.Value.Visible ? 1 : 0; + } + + return 0; + })), + Size = GetContentSize ().Width, + Visible = false + }; + + Padding?.Add (scrollBar); + + scrollBar.Initialized += (_, _) => + { + Padding!.Thickness = Padding.Thickness with + { + Bottom = scrollBar.Visible ? Padding.Thickness.Bottom + 1 : 0 + }; + + scrollBar.ContentPositionChanged += (_, args) => + { + Viewport = Viewport with + { + X = Math.Min ( + args.CurrentValue, + GetContentSize ().Width - (Viewport.Width)) + }; + }; + + scrollBar.VisibleChanged += (_, _) => + { + Padding.Thickness = Padding.Thickness with + { + Bottom = scrollBar.Visible + ? Padding.Thickness.Bottom + 1 + : Padding.Thickness.Bottom - 1 + }; + }; + }; + + return scrollBar; + }); + + _verticalScrollBar = new ( + () => + { + var scrollBar = new ScrollBar + { + Orientation = Orientation.Vertical, + X = Pos.AnchorEnd (), + Y = Pos.Func (() => Padding.Thickness.Top), + Height = Dim.Fill ( + Dim.Func ( + () => + { + if (_horizontalScrollBar.IsValueCreated) + { + return _horizontalScrollBar.Value.Visible ? 1 : 0; + } + + return 0; + })), + Size = GetContentSize ().Height, + Visible = false + }; + + Padding?.Add (scrollBar); + + scrollBar.Initialized += (_, _) => + { + if (Padding is { }) + { + Padding.Thickness = Padding.Thickness with + { + Right = scrollBar.Visible ? Padding.Thickness.Right + 1 : 0 + }; + + scrollBar.ContentPositionChanged += (_, args) => + { + Viewport = Viewport with + { + Y = Math.Min ( + args.CurrentValue, + GetContentSize ().Height - (Viewport.Height)) + }; + }; + + scrollBar.VisibleChanged += (_, _) => + { + Padding.Thickness = Padding.Thickness with + { + Right = scrollBar.Visible + ? Padding.Thickness.Right + 1 + : Padding.Thickness.Right - 1 + }; + }; + } + }; + + return scrollBar; + }); + + ViewportChanged += (_, _) => + { + if (_verticalScrollBar.IsValueCreated) + { + _verticalScrollBar.Value.ContentPosition = Viewport.Y; + } + + if (_horizontalScrollBar.IsValueCreated) + { + _horizontalScrollBar.Value.ContentPosition = Viewport.X; + } + }; + + ContentSizeChanged += (_, _) => + { + if (_verticalScrollBar.IsValueCreated) + { + _verticalScrollBar.Value.Size = GetContentSize ().Height; + } + if (_horizontalScrollBar.IsValueCreated) + { + _horizontalScrollBar.Value.Size = GetContentSize ().Width; + } + }; + } + + /// + /// + public ScrollBar HorizontalScrollBar => _horizontalScrollBar.Value; + + /// + /// + public ScrollBar VerticalScrollBar => _verticalScrollBar.Value; + + /// + /// Clean up the ScrollBars of the View. Called by View.Dispose. + /// + private void DisposeScrollBars () + { + if (_horizontalScrollBar.IsValueCreated) + { + Padding?.Remove (_horizontalScrollBar.Value); + _horizontalScrollBar.Value.Dispose (); + } + + if (_verticalScrollBar.IsValueCreated) + { + Padding?.Remove (_verticalScrollBar.Value); + _verticalScrollBar.Value.Dispose (); + } + } + + private void SetScrollBarsKeepContentInAllViewport (ViewportSettings viewportSettings) + { + if (viewportSettings == ViewportSettings.None) + { + _horizontalScrollBar.Value.KeepContentInAllViewport = true; + _verticalScrollBar.Value.KeepContentInAllViewport = true; + } + else if (viewportSettings.HasFlag (ViewportSettings.AllowNegativeX)) + { + _horizontalScrollBar.Value.AutoHide = false; + } + else if (viewportSettings.HasFlag (ViewportSettings.AllowNegativeY)) + { + _verticalScrollBar.Value.AutoHide = false; + } + else if (viewportSettings.HasFlag (ViewportSettings.AllowNegativeLocation)) + { + _horizontalScrollBar.Value.AutoHide = false; + _verticalScrollBar.Value.AutoHide = false; + } + else if (viewportSettings.HasFlag (ViewportSettings.AllowXGreaterThanContentWidth)) + { + _horizontalScrollBar.Value.KeepContentInAllViewport = false; + } + else if (viewportSettings.HasFlag (ViewportSettings.AllowYGreaterThanContentHeight)) + { + _verticalScrollBar.Value.KeepContentInAllViewport = false; + } + else if (viewportSettings.HasFlag (ViewportSettings.AllowLocationGreaterThanContentSize)) + { + _horizontalScrollBar.Value.KeepContentInAllViewport = false; + _verticalScrollBar.Value.KeepContentInAllViewport = false; + } + } +} diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index 00e6df6ad4..7af099f79c 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -74,8 +74,10 @@ namespace Terminal.Gui; /// To flag the entire view for redraw call . /// /// -/// The method is called when the size or layout of a view has changed. The will -/// cause to be called on the next so there is normally no reason to direclty call +/// The method is called when the size or layout of a view has changed. The +/// will +/// cause to be called on the next so there is normally +/// no reason to direclty call /// see . /// /// @@ -107,7 +109,7 @@ namespace Terminal.Gui; #endregion API Docs -public partial class View : Responder, ISupportInitializeNotification +public partial class View : IDisposable, ISupportInitializeNotification { #region Constructors and Initialization @@ -135,6 +137,10 @@ public partial class View : Responder, ISupportInitializeNotification /// public View () { +#if DEBUG_IDISPOSABLE + Instances.Add (this); +#endif + SetupAdornments (); SetupCommands (); @@ -144,6 +150,8 @@ public View () //SetupMouse (); SetupText (); + + SetupScrollBars (); } /// @@ -359,9 +367,9 @@ public virtual bool Visible VisibleChanged?.Invoke (this, EventArgs.Empty); SetNeedsLayout (); - SuperView?.SetNeedsLayout(); + SuperView?.SetNeedsLayout (); SetNeedsDraw (); - SuperView?.SetNeedsDraw(); + SuperView?.SetNeedsDraw (); } } @@ -522,13 +530,22 @@ protected bool OnTitleChanging (ref string newTitle) /// public override string ToString () { return $"{GetType ().Name}({Id}){Frame}"; } - /// - protected override void Dispose (bool disposing) + private bool _disposedValue; + + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// If disposing equals true, the method has been called directly or indirectly by a user's code. Managed and + /// unmanaged resources can be disposed. If disposing equals false, the method has been called by the runtime from + /// inside the finalizer and you should not reference other objects. Only unmanaged resources can be disposed. + /// + /// + protected virtual void Dispose (bool disposing) { LineCanvas.Dispose (); DisposeKeyboard (); DisposeAdornments (); + DisposeScrollBars (); for (int i = InternalSubviews.Count - 1; i >= 0; i--) { @@ -537,7 +554,49 @@ protected override void Dispose (bool disposing) subview.Dispose (); } - base.Dispose (disposing); + if (!_disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects) + } + + _disposedValue = true; + } + Debug.Assert (InternalSubviews.Count == 0); } + + /// + /// Riased when the is being disposed. + /// + public event EventHandler? Disposing; + + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resource. + public void Dispose () + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Disposing?.Invoke (this, EventArgs.Empty); + Dispose (true); + GC.SuppressFinalize (this); +#if DEBUG_IDISPOSABLE + WasDisposed = true; + + foreach (View instance in Instances.Where (x => x.WasDisposed).ToList ()) + { + Instances.Remove (instance); + } +#endif + } + +#if DEBUG_IDISPOSABLE + /// For debug purposes to verify objects are being disposed properly + public bool WasDisposed { get; set; } + + /// For debug purposes to verify objects are being disposed properly + public int DisposedCount { get; set; } = 0; + + /// For debug purposes + public static List Instances { get; set; } = []; +#endif } diff --git a/Terminal.Gui/View/ViewportSettings.cs b/Terminal.Gui/View/ViewportSettings.cs index f7e8488c12..c3cc51ac07 100644 --- a/Terminal.Gui/View/ViewportSettings.cs +++ b/Terminal.Gui/View/ViewportSettings.cs @@ -97,10 +97,27 @@ public enum ViewportSettings /// If set will clear only the portion of the content /// area that is visible within the . This is useful for views that have a /// content area larger than the Viewport and want the area outside the content to be visually distinct. - /// - /// must be set for this setting to work (clipping beyond the visible area must be - /// disabled). - /// + /// must be set for this setting to work (clipping beyond the visible area must be + /// disabled). + /// + ClearContentOnly = 32, + + /// + /// If set, the vertical scroll bar (see ) will be enabled and automatically made visible + /// when the dimension of the is smaller than the dimension of . + /// + EnableHorizontalScrollBar = 64, + + /// + /// If set, the vertical scroll bar (see ) will be enabled and automatically made visible + /// when the dimension of the is smaller than the dimension of . + /// + EnableVerticalScrollBar = 128, + + /// + /// If set, the horizontal and vertical scroll bars (see cref="View.HorizontalScrollBar"/> and ) + /// will be enabled and automatically made visible when the dimension of the is smaller than the + /// dimension of . /// - ClearContentOnly = 32 + EnableScrollBars = EnableHorizontalScrollBar | EnableVerticalScrollBar } diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs new file mode 100644 index 0000000000..02ef593561 --- /dev/null +++ b/Terminal.Gui/Views/Scroll/Scroll.cs @@ -0,0 +1,397 @@ +#nullable enable + +using System.ComponentModel; + +namespace Terminal.Gui; + +/// +/// Indicates the size of scrollable content and provides a visible element, referred to as the "ScrollSlider" that +/// that is sized to +/// show the proportion of the scrollable content to the size of the . The ScrollSlider +/// can be dragged with the mouse. A Scroll can be oriented either vertically or horizontally and is used within a +/// . +/// +/// +/// +/// By default, this view cannot be focused and does not support keyboard. +/// +/// +public class Scroll : View, IOrientation, IDesignable +{ + internal readonly ScrollSlider _slider; + + /// + public Scroll () + { + _slider = new (); + Add (_slider); + _slider.FrameChanged += OnSliderOnFrameChanged; + + CanFocus = false; + + _orientationHelper = new (this); // Do not use object initializer! + _orientationHelper.Orientation = Orientation.Vertical; + _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e); + _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e); + + // This sets the width/height etc... + OnOrientationChanged (Orientation); + } + + /// + protected override void OnSubviewLayout (LayoutEventArgs args) + { + if (ViewportDimension < 1) + { + _slider.Size = 1; + + return; + } + + _slider.Size = (int)Math.Clamp (Math.Floor ((double)ViewportDimension * ViewportDimension / (Size)), 1, ViewportDimension); + } + + #region IOrientation members + + private readonly OrientationHelper _orientationHelper; + + /// + public Orientation Orientation + { + get => _orientationHelper.Orientation; + set => _orientationHelper.Orientation = value; + } + + /// + public event EventHandler>? OrientationChanging; + + /// + public event EventHandler>? OrientationChanged; + + /// + public void OnOrientationChanged (Orientation newOrientation) + { + TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; + TextAlignment = Alignment.Center; + VerticalTextAlignment = Alignment.Center; + + X = 0; + Y = 0; + + if (Orientation == Orientation.Vertical) + { + Width = 1; + Height = Dim.Fill (); + } + else + { + Width = Dim.Fill (); + Height = 1; + } + + _slider.Orientation = newOrientation; + } + + #endregion + + /// + /// Gets or sets whether the Scroll will show the percentage the slider + /// takes up within the . + /// + public bool ShowPercent + { + get => _slider.ShowPercent; + set => _slider.ShowPercent = value; + } + + private int ViewportDimension => Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width; + + private int _size; + + /// + /// Gets or sets the total size of the content that can be scrolled. + /// + public int Size + { + get => _size; + set + { + if (value == _size || value < 0) + { + return; + } + + _size = value; + OnSizeChanged (_size); + SizeChanged?.Invoke (this, new (in _size)); + SetNeedsLayout (); + } + } + + /// Called when has changed. + protected virtual void OnSizeChanged (int size) { } + + /// Raised when has changed. + public event EventHandler>? SizeChanged; + + #region SliderPosition + private void OnSliderOnFrameChanged (object? sender, EventArgs args) + { + if (ViewportDimension == 0) + { + return; + } + + int framePos = Orientation == Orientation.Vertical ? args.CurrentValue.Y : args.CurrentValue.X; + SliderPosition = framePos; + } + + /// + /// Gets or sets the position of the start of the Scroll slider, within the Viewport. + /// + public int SliderPosition + { + get => CalculateSliderPosition (_contentPosition); + set => RaiseSliderPositionChangeEvents (value); + } + + private void RaiseSliderPositionChangeEvents (int newSliderPosition) + { + int currentSliderPosition = CalculateSliderPosition (_contentPosition); + + if (/*newSliderPosition > Size - ViewportDimension ||*/ currentSliderPosition == newSliderPosition) + { + return; + } + + if (OnSliderPositionChanging (currentSliderPosition, newSliderPosition)) + { + return; + } + + CancelEventArgs args = new (ref currentSliderPosition, ref newSliderPosition); + SliderPositionChanging?.Invoke (this, args); + + if (args.Cancel) + { + return; + } + + // This sets the slider position and clamps the value + _slider.Position = newSliderPosition; + + ContentPosition = (int)Math.Round ((double)newSliderPosition / (ViewportDimension - _slider.Size) * (Size - ViewportDimension)); + + OnSliderPositionChanged (newSliderPosition); + SliderPositionChanged?.Invoke (this, new (in newSliderPosition)); + } + + /// + /// Called when is changing. Return true to cancel the change. + /// + protected virtual bool OnSliderPositionChanging (int currentSliderPosition, int newSliderPosition) { return false; } + + /// + /// Raised when the is changing. Set to + /// to prevent the position from being changed. + /// + public event EventHandler>? SliderPositionChanging; + + /// Called when has changed. + protected virtual void OnSliderPositionChanged (int position) { } + + /// Raised when the has changed. + public event EventHandler>? SliderPositionChanged; + + private int CalculateSliderPosition (int contentPosition) + { + if (Size - ViewportDimension == 0) + { + return 0; + } + + return (int)Math.Round ((double)contentPosition / (Size - ViewportDimension) * (ViewportDimension - _slider.Size)); + } + + #endregion SliderPosition + + #region ContentPosition + + private int _contentPosition; + + /// + /// Gets or sets the position of the ScrollSlider within the range of 0.... + /// + public int ContentPosition + { + get => _contentPosition; + set + { + if (value == _contentPosition) + { + return; + } + + RaiseContentPositionChangeEvents (value); + } + } + + private void RaiseContentPositionChangeEvents (int newContentPosition) + { + // Clamp the value between 0 and Size - ViewportDimension + newContentPosition = (int)Math.Clamp (newContentPosition, 0, Math.Max (0, Size - ViewportDimension)); + + if (OnContentPositionChanging (_contentPosition, newContentPosition)) + { + return; + } + + CancelEventArgs args = new (ref _contentPosition, ref newContentPosition); + ContentPositionChanging?.Invoke (this, args); + + if (args.Cancel) + { + return; + } + + _contentPosition = newContentPosition; + + SliderPosition = CalculateSliderPosition (_contentPosition); + + OnContentPositionChanged (_contentPosition); + ContentPositionChanged?.Invoke (this, new (in _contentPosition)); + } + + /// + /// Called when is changing. Return true to cancel the change. + /// + protected virtual bool OnContentPositionChanging (int currentPos, int newPos) { return false; } + + /// + /// Raised when the is changing. Set to + /// to prevent the position from being changed. + /// + public event EventHandler>? ContentPositionChanging; + + /// Called when has changed. + protected virtual void OnContentPositionChanged (int position) { } + + /// Raised when the has changed. + public event EventHandler>? ContentPositionChanged; + + #endregion ContentPosition + + /// + protected override bool OnClearingViewport () + { + FillRect (Viewport, Glyphs.Stipple); + + return true; + } + + /// + protected override bool OnMouseClick (MouseEventArgs args) + { + if (!args.IsSingleClicked) + { + return false; + } + + if (Orientation == Orientation.Vertical) + { + // If the position is w/in the slider frame ignore + if (args.Position.Y >= _slider.Frame.Y && args.Position.Y < _slider.Frame.Y + _slider.Frame.Height) + { + return false; + } + + SliderPosition = args.Position.Y; + } + else + { + // If the position is w/in the slider frame ignore + if (args.Position.X >= _slider.Frame.X && args.Position.X < _slider.Frame.X + _slider.Frame.Width) + { + return false; + } + + SliderPosition = args.Position.X; + } + + return true; + } + + /// + /// Gets or sets the amount each mouse hweel event will incremenet/decrement the . + /// + /// + /// The default is 1. + /// + public int Increment { get; set; } = 1; + + /// + protected override bool OnMouseEvent (MouseEventArgs mouseEvent) + { + if (SuperView is null) + { + return false; + } + + if (!mouseEvent.IsWheel) + { + return false; + } + + if (Orientation == Orientation.Vertical) + { + if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledDown)) + { + ContentPosition += Increment; + } + + if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledUp)) + { + ContentPosition -= Increment; + } + } + else + { + if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledRight)) + { + ContentPosition += Increment; + } + + if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledLeft)) + { + ContentPosition -= Increment; + } + } + + return true; + } + + /// + public bool EnableForDesign () + { + OrientationChanged += (sender, args) => + { + if (args.CurrentValue == Orientation.Vertical) + { + Width = 1; + Height = Dim.Fill (); + } + else + { + Width = Dim.Fill (); + Height = 1; + } + }; + + Width = 1; + Height = Dim.Fill (); + Size = 1000; + ContentPosition = 10; + + return true; + } +} diff --git a/Terminal.Gui/Views/Scroll/ScrollBar.cs b/Terminal.Gui/Views/Scroll/ScrollBar.cs new file mode 100644 index 0000000000..7bf0e1ea68 --- /dev/null +++ b/Terminal.Gui/Views/Scroll/ScrollBar.cs @@ -0,0 +1,319 @@ +#nullable enable + +using System.ComponentModel; + +namespace Terminal.Gui; + +/// +/// Provides a visual indicator that content can be scrolled. ScrollBars consist of two buttons, one each for scrolling +/// forward or backwards, a that can be dragged +/// to scroll continuously. ScrollBars can be oriented either horizontally or vertically and support the user dragging +/// and clicking with the mouse to scroll. +/// +/// +/// +/// indicates the number of rows or columns the Scroll has moved from 0. +/// +/// +public class ScrollBar : View, IOrientation, IDesignable +{ + private readonly Scroll _scroll; + private readonly Button _decreaseButton; + private readonly Button _increaseButton; + + /// + public ScrollBar () + { + CanFocus = false; + + _scroll = new (); + _scroll.SliderPositionChanging += OnScrollOnSliderPositionChanging; + _scroll.SliderPositionChanged += OnScrollOnSliderPositionChanged; + _scroll.ContentPositionChanging += OnScrollOnContentPositionChanging; + _scroll.ContentPositionChanged += OnScrollOnContentPositionChanged; + _scroll.SizeChanged += OnScrollOnSizeChanged; + + _decreaseButton = new () + { + CanFocus = false, + NoDecorations = true, + NoPadding = true, + ShadowStyle = ShadowStyle.None, + WantContinuousButtonPressed = true + }; + _decreaseButton.Accepting += OnDecreaseButtonOnAccept; + + _increaseButton = new () + { + CanFocus = false, + NoDecorations = true, + NoPadding = true, + ShadowStyle = ShadowStyle.None, + WantContinuousButtonPressed = true + }; + _increaseButton.Accepting += OnIncreaseButtonOnAccept; + Add (_decreaseButton, _scroll, _increaseButton); + + _orientationHelper = new (this); // Do not use object initializer! + _orientationHelper.Orientation = Orientation.Vertical; + _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e); + _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e); + + // This sets the width/height etc... + OnOrientationChanged (Orientation); + + return; + + void OnDecreaseButtonOnAccept (object? s, CommandEventArgs e) + { + ContentPosition -= Increment; + e.Cancel = true; + } + + void OnIncreaseButtonOnAccept (object? s, CommandEventArgs e) + { + ContentPosition += Increment; + e.Cancel = true; + } + } + + #region IOrientation members + + private readonly OrientationHelper _orientationHelper; + + /// + public Orientation Orientation + { + get => _orientationHelper.Orientation; + set => _orientationHelper.Orientation = value; + } + + /// + public event EventHandler>? OrientationChanging; + + /// + public event EventHandler>? OrientationChanged; + + /// + public void OnOrientationChanged (Orientation newOrientation) + { + TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; + TextAlignment = Alignment.Center; + VerticalTextAlignment = Alignment.Center; + + if (Orientation == Orientation.Vertical) + { + Width = 1; + Height = Dim.Fill (); + } + else + { + Width = Dim.Fill (); + Height = 1; + } + + _scroll.Orientation = newOrientation; + } + + #endregion + + private bool _autoHide = true; + + /// + /// Gets or sets whether will be set to if the dimension of the + /// scroll bar is greater than or equal to . + /// + public bool AutoHide + { + get => _autoHide; + set + { + if (_autoHide != value) + { + _autoHide = value; + + if (!AutoHide) + { + Visible = true; + } + + SetNeedsLayout (); + } + } + } + + /// + protected override void OnFrameChanged (in Rectangle frame) { ShowHide (); } + + private void ShowHide () + { + if (!AutoHide || !IsInitialized) + { + return; + } + + if (Orientation == Orientation.Vertical) + { + Visible = Frame.Height - (_decreaseButton.Frame.Height + _increaseButton.Frame.Height) < Size; + } + else + { + Visible = Frame.Width - (_decreaseButton.Frame.Width + _increaseButton.Frame.Width) < Size; + } + } + + /// + /// Gets or sets whether the Scroll will show the percentage the slider + /// takes up within the . + /// + public bool ShowPercent + { + get => _scroll.ShowPercent; + set => _scroll.ShowPercent = value; + } + + + /// Get or sets if the view-port is kept in all visible area of this . + public bool KeepContentInAllViewport + { + //get => _scroll.KeepContentInAllViewport; + //set => _scroll.KeepContentInAllViewport = value; + get; + set; + } + + /// Gets or sets the position of the slider within the ScrollBar's Viewport. + /// The position. + public int SliderPosition + { + get => _scroll.SliderPosition; + set => _scroll.SliderPosition = value; + } + + private void OnScrollOnSliderPositionChanging (object? sender, CancelEventArgs e) { SliderPositionChanging?.Invoke (this, e); } + private void OnScrollOnSliderPositionChanged (object? sender, EventArgs e) { SliderPositionChanged?.Invoke (this, e); } + + /// + /// Raised when the is changing. Set to + /// to prevent the position from being changed. + /// + public event EventHandler>? SliderPositionChanging; + + /// Raised when the has changed. + public event EventHandler>? SliderPositionChanged; + + + /// + /// Gets or sets the size of the Scroll. This is the total size of the content that can be scrolled through. + /// + public int Size + { + get => _scroll.Size; + set => _scroll.Size = value; + } + + /// + /// Gets or sets the position of the ScrollSlider within the range of 0.... + /// + public int ContentPosition + { + get => _scroll.ContentPosition; + set => _scroll.ContentPosition = value; + } + + private void OnScrollOnContentPositionChanging (object? sender, CancelEventArgs e) { ContentPositionChanging?.Invoke (this, e); } + private void OnScrollOnContentPositionChanged (object? sender, EventArgs e) { ContentPositionChanged?.Invoke (this, e); } + + /// + /// Raised when the is changing. Set to + /// to prevent the position from being changed. + /// + public event EventHandler>? ContentPositionChanging; + + /// Raised when the has changed. + public event EventHandler>? ContentPositionChanged; + + /// Raised when has changed. + public event EventHandler>? SizeChanged; + + private void OnScrollOnSizeChanged (object? sender, EventArgs e) + { + ShowHide (); + SizeChanged?.Invoke (this, e); + } + + /// + /// Gets or sets the amount each click of the increment/decrement buttons and each + /// mouse wheel event will incremenet/decrement the . + /// + /// + /// The default is 1. + /// + public int Increment { get => _scroll.Increment; set => _scroll.Increment = value; } + + /// + protected override void OnSubviewLayout (LayoutEventArgs args) { PositionSubviews (); } + + private void PositionSubviews () + { + if (Orientation == Orientation.Vertical) + { + _decreaseButton.Y = 0; + _decreaseButton.X = 0; + _decreaseButton.Width = Dim.Fill (); + _decreaseButton.Height = 1; + _decreaseButton.Title = Glyphs.UpArrow.ToString (); + _increaseButton.Y = Pos.Bottom (_scroll); + _increaseButton.X = 0; + _increaseButton.Width = Dim.Fill (); + _increaseButton.Height = 1; + _increaseButton.Title = Glyphs.DownArrow.ToString (); + _scroll.X = 0; + _scroll.Y = Pos.Bottom (_decreaseButton); + _scroll.Height = Dim.Fill (1); + _scroll.Width = Dim.Fill (); + } + else + { + _decreaseButton.Y = 0; + _decreaseButton.X = 0; + _decreaseButton.Width = 1; + _decreaseButton.Height = Dim.Fill (); + _decreaseButton.Title = Glyphs.LeftArrow.ToString (); + _increaseButton.Y = 0; + _increaseButton.X = Pos.Right (_scroll); + _increaseButton.Width = 1; + _increaseButton.Height = Dim.Fill (); + _increaseButton.Title = Glyphs.RightArrow.ToString (); + _scroll.Y = 0; + _scroll.X = Pos.Right (_decreaseButton); + _scroll.Width = Dim.Fill (1); + _scroll.Height = Dim.Fill (); + } + } + + /// + public bool EnableForDesign () + { + OrientationChanged += (sender, args) => + { + if (args.CurrentValue == Orientation.Vertical) + { + Width = 1; + Height = Dim.Fill (); + } + else + { + Width = Dim.Fill (); + Height = 1; + } + }; + + Width = 1; + Height = Dim.Fill (); + Size = 200; + SliderPosition = 10; + //ShowPercent = true; + return true; + } +} diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs new file mode 100644 index 0000000000..a3e58beddd --- /dev/null +++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs @@ -0,0 +1,282 @@ +#nullable enable + +using System.Diagnostics; + +namespace Terminal.Gui; + +/// +/// The ScrollSlider can be dragged with the mouse, constrained by the size of the Viewport of it's superview. The ScrollSlider can be +/// oriented either vertically or horizontally. +/// +/// +/// +/// If is set, it will be displayed centered within the slider. Set +/// to automatically have the Text +/// be show what percent the slider is to the Superview's Viewport size. +/// +/// +/// Used to represent the proportion of the visible content to the Viewport in a . +/// +/// +public class ScrollSlider : View, IOrientation, IDesignable +{ + /// + /// Initializes a new instance. + /// + public ScrollSlider () + { + Id = "scrollSlider"; + WantMousePositionReports = true; + + _orientationHelper = new (this); // Do not use object initializer! + _orientationHelper.Orientation = Orientation.Vertical; + _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e); + _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e); + + OnOrientationChanged (Orientation); + + HighlightStyle = HighlightStyle.Hover; + + // Default size is 1 + Size = 1; + } + + #region IOrientation members + private readonly OrientationHelper _orientationHelper; + + /// + public Orientation Orientation + { + get => _orientationHelper.Orientation; + set => _orientationHelper.Orientation = value; + } + + /// + public event EventHandler>? OrientationChanging; + + /// + public event EventHandler>? OrientationChanged; + + /// + public void OnOrientationChanged (Orientation newOrientation) + { + TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; + TextAlignment = Alignment.Center; + VerticalTextAlignment = Alignment.Center; + + // Reset Position to 0 when changing orientation + X = 0; + Y = 0; + + // Reset Size to 1 when changing orientation + if (Orientation == Orientation.Vertical) + { + Width = Dim.Fill (); + Height = 1; + } + else + { + Width = 1; + Height = Dim.Fill (); + } + } + + #endregion + + /// + protected override bool OnClearingViewport () + { + FillRect (Viewport, Glyphs.ContinuousMeterSegment); + + return true; + } + + private bool _showPercent; + + /// + /// Gets or sets whether the ScrollSlider will set to show the percentage the slider + /// takes up within the 's Viewport. + /// + public bool ShowPercent + { + get => _showPercent; + set + { + _showPercent = value; + SetNeedsDraw(); + } + } + + /// + /// Gets or sets the size of the ScrollSlider. This is a helper that simply gets or sets the Width or Height depending on the + /// . The size will be constrained such that the ScrollSlider will not go outside the Viewport of + /// the . The size will never be less than 1. + /// + /// + /// + /// The dimension of the ScrollSlider that is perpendicular to the will be set to + /// + /// + public int Size + { + get + { + if (Orientation == Orientation.Vertical) + { + return Frame.Height; + } + else + { + return Frame.Width; + } + } + set + { + if (Orientation == Orientation.Vertical) + { + Width = Dim.Fill (); + int viewport = Math.Max (1, SuperView?.Viewport.Height ?? 1); + Height = Math.Clamp (value, 1, viewport); + } + else + { + int viewport = Math.Max (1, SuperView?.Viewport.Width ?? 1); + Width = Math.Clamp (value, 1, viewport); + Height = Dim.Fill (); + } + } + } + + /// + /// Gets or sets the position of the ScrollSlider relative to the size of the ScrollSlider's Frame. This is a helper that simply gets or sets the X or Y depending on the + /// . The position will be constrained such that the ScrollSlider will not go outside the Viewport of + /// the . + /// + public int Position + { + get + { + if (Orientation == Orientation.Vertical) + { + return Frame.Y; + } + else + { + return Frame.X; + } + } + set + { + if (Orientation == Orientation.Vertical) + { + int viewport = Math.Max (1, SuperView?.Viewport.Height ?? 1); + Y = Math.Clamp (value, 0, viewport - Frame.Height); + } + else + { + int viewport = Math.Max (1, SuperView?.Viewport.Width ?? 1); + X = Math.Clamp (value, 0, viewport - Frame.Width); + } + } + } + + /// + protected override bool OnDrawingText () + { + if (!ShowPercent) + { + Text = string.Empty; + + return false; + } + + if (SuperView is null) + { + return false; + } + + if (Orientation == Orientation.Vertical) + { + Text = $"{(int)Math.Round ((double)Viewport.Height / SuperView!.GetContentSize ().Height * 100)}%"; + } + else + { + Text = $"{(int)Math.Round ((double)Viewport.Width / SuperView!.GetContentSize ().Width * 100)}%"; + } + + return false; + } + + /// + public override Attribute GetNormalColor () { return base.GetHotNormalColor (); } + + ///// + private int _lastLocation = -1; + + /// + protected override bool OnMouseEvent (MouseEventArgs mouseEvent) + { + if (SuperView is null) + { + return false; + } + + int location = Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; + int offset = _lastLocation > -1 ? location - _lastLocation : 0; + int superViewDimension = Orientation == Orientation.Vertical ? SuperView!.Viewport.Height : SuperView!.Viewport.Width; + + if (mouseEvent.IsPressed || mouseEvent.IsReleased) + { + if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && _lastLocation == -1) + { + if (Application.MouseGrabView != this) + { + Application.GrabMouse (this); + _lastLocation = location; + } + } + else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) + { + if (Orientation == Orientation.Vertical) + { + Y = Frame.Y + offset < 0 + ? 0 + : Frame.Y + offset + Frame.Height > superViewDimension + ? Math.Max (superViewDimension - Frame.Height, 0) + : Frame.Y + offset; + } + else + { + X = Frame.X + offset < 0 + ? 0 + : Frame.X + offset + Frame.Width > superViewDimension + ? Math.Max (superViewDimension - Frame.Width, 0) + : Frame.X + offset; + } + } + else if (mouseEvent.Flags == MouseFlags.Button1Released) + { + _lastLocation = -1; + + if (Application.MouseGrabView == this) + { + Application.UngrabMouse (); + } + } + return true; + } + return false; + + } + + /// + public bool EnableForDesign () + { + Orientation = Orientation.Vertical; + Width = 1; + Height = 10; + ShowPercent = true; + + return true; + } +} diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs deleted file mode 100644 index 8873e5ba78..0000000000 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ /dev/null @@ -1,1086 +0,0 @@ -// -// ScrollBarView.cs: ScrollBarView view. -// -// Authors: -// Miguel de Icaza (miguel@gnome.org) -// - -using System.Diagnostics; - -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 -{ - private bool _autoHideScrollBars = true; - private View _contentBottomRightCorner; - private bool _hosted; - private bool _keepContentAlwaysInViewport = true; - private int _lastLocation = -1; - private ScrollBarView _otherScrollBarView; - private int _posBarOffset; - private int _posBottomTee; - private int _posLeftTee; - private int _posRightTee; - private int _posTopTee; - private bool _showScrollIndicator; - private int _size, _position; - private bool _vertical; - - /// - /// Initializes a new instance of the class. - /// - public ScrollBarView () - { - WantContinuousButtonPressed = true; - - Added += (s, e) => CreateBottomRightCorner (e.SuperView); - Initialized += ScrollBarView_Initialized; - } - - /// - /// Initializes a new instance of the class. - /// - /// 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) - { - if (host is null) - { - throw new ArgumentNullException ("The host parameter can't be null."); - } - - if (host.SuperView is null) - { - throw new ArgumentNullException ("The host SuperView parameter can't be null."); - } - - _hosted = true; - IsVertical = isVertical; - 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; - Initialized += ScrollBarView_Initialized; - - //Host.CanFocusChanged += Host_CanFocusChanged; - Host.EnabledChanged += Host_EnabledChanged; - Host.VisibleChanged += Host_VisibleChanged; - Host.SuperView.Add (this); - AutoHideScrollBars = true; - - if (showBothScrollIndicator) - { - OtherScrollBarView = new ScrollBarView - { - IsVertical = !isVertical, - 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 (Host); - } - - /// 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; - SetNeedsDraw (); - } - } - } - - // 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; } - - /// 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 (); - } - } - } - - /// 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.Viewport.Width > _size) - { - pos = _size - Host.Viewport.Width + (_showBothScrollIndicator ? 1 : 0); - } - - if (value && _vertical && _position + Host.Viewport.Height > _size) - { - pos = _size - Host.Viewport.Height + (_showBothScrollIndicator ? 1 : 0); - } - - if (pos != 0) - { - Position = pos; - } - - if (OtherScrollBarView is { } && OtherScrollBarView._keepContentAlwaysInViewport != value) - { - OtherScrollBarView.KeepContentAlwaysInViewport = value; - } - - if (pos == 0) - { - Refresh (); - } - } - } - } - - /// Represent a vertical or horizontal ScrollBarView other than this. - public ScrollBarView OtherScrollBarView - { - get => _otherScrollBarView; - set - { - if (value is { } && ((value.IsVertical && _vertical) || (!value.IsVertical && !_vertical))) - { - throw new ArgumentException ( - $"There is already a {(_vertical ? "vertical" : "horizontal")} ScrollBarView." - ); - } - - _otherScrollBarView = value; - } - } - - /// The position, relative to , to set the scrollbar at. - /// The position. - public int Position - { - get => _position; - set - { - if (_position == value) - { - return; - } - - SetPosition (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 && Visible; - set - { - //if (value == showScrollIndicator) { - // return; - //} - - _showScrollIndicator = value; - - if (IsInitialized) - { - SetNeedsLayout (); - - if (value) - { - Visible = true; - } - else - { - Visible = false; - Position = 0; - } - - 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.Size ?? Host.Frame.Size); - ShowHideScrollBars (false); - SetNeedsLayout (); - } - } - } - - private bool _showBothScrollIndicator => OtherScrollBarView?.ShowScrollIndicator == true && ShowScrollIndicator; - - /// This event is raised when the position on the scrollbar has changed. - public event EventHandler ChangedPosition; - - /// - protected override bool OnMouseEvent (MouseEventArgs 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.Position.Y : mouseEvent.Position.X; - int barsize = _vertical ? Viewport.Height : Viewport.Width; - int posTopLeftTee = _vertical ? _posTopTee + 1 : _posLeftTee + 1; - int posBottomRightTee = _vertical ? _posBottomTee + 1 : _posRightTee + 1; - barsize -= 2; - int pos = Position; - - if (mouseEvent.Flags != MouseFlags.Button1Released && (Application.MouseGrabView is null || Application.MouseGrabView != this)) - { - Application.GrabMouse (this); - } - else if (mouseEvent.Flags == MouseFlags.Button1Released && Application.MouseGrabView is { } && 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.NewMouseEvent (mouseEvent) == true; - } - - 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) - { - int 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) - { - int 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; - } - - /// Virtual method to invoke the action event. - public virtual void OnChangedPosition () { ChangedPosition?.Invoke (this, EventArgs.Empty); } - - /// - protected override bool OnDrawingContent () - { - if (ColorScheme is null || ((!ShowScrollIndicator || Size == 0) && AutoHideScrollBars && Visible)) - { - if ((!ShowScrollIndicator || Size == 0) && AutoHideScrollBars && Visible) - { - ShowHideScrollBars (false); - } - - return false; - } - - if (Size == 0 || (_vertical && Viewport.Height == 0) || (!_vertical && Viewport.Width == 0)) - { - return false; - } - - SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ()); - - if (_vertical) - { - if (Viewport.Right < Viewport.Width - 1) - { - return true; - } - - int col = Viewport.Width - 1; - int bh = Viewport.Height; - Rune special; - - if (bh < 4) - { - int by1 = _position * bh / Size; - int by2 = (_position + bh) * bh / Size; - - Move (col, 0); - - if (Viewport.Height == 1) - { - Driver.AddRune (Glyphs.Diamond); - } - else - { - Driver.AddRune (Glyphs.UpArrow); - } - - if (Viewport.Height == 3) - { - Move (col, 1); - Driver.AddRune (Glyphs.Diamond); - } - - if (Viewport.Height > 1) - { - Move (col, Viewport.Height - 1); - Driver.AddRune (Glyphs.DownArrow); - } - } - else - { - bh -= 2; - - int by1 = KeepContentAlwaysInViewport - ? _position * bh / Size - : _position * bh / (Size + bh); - - int 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); - } - - AddRune (col, 0, Glyphs.UpArrow); - - var hasTopTee = false; - var hasDiamond = false; - var hasBottomTee = false; - - for (var y = 0; y < bh; y++) - { - - 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; - } - } - } - - AddRune (col, y + 1, special); - } - - if (!hasTopTee) - { - AddRune (col, Viewport.Height - 2, Glyphs.TopTee); - } - - AddRune (col, Viewport.Height - 1, Glyphs.DownArrow); - } - } - else - { - if (Viewport.Bottom < Viewport.Height - 1) - { - return true; - } - - int row = Viewport.Height - 1; - int bw = Viewport.Width; - Rune special; - - if (bw < 4) - { - int bx1 = _position * bw / Size; - int bx2 = (_position + bw) * bw / Size; - - Move (0, row); - Driver.AddRune (Glyphs.LeftArrow); - Driver.AddRune (Glyphs.RightArrow); - } - else - { - bw -= 2; - - int bx1 = KeepContentAlwaysInViewport - ? _position * bw / Size - : _position * bw / (Size + bw); - - int 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 (Viewport.Width - 2, row); - Driver.AddRune (Glyphs.LeftTee); - } - - Driver.AddRune (Glyphs.RightArrow); - } - } - - return false; - } - - - /// Only used for a hosted view that will update and redraw the scrollbars. - public virtual void Refresh () { ShowHideScrollBars (); } - - internal bool CanScroll (int n, out int max, bool isVertical = false) - { - if (Host?.Viewport.IsEmpty != false) - { - max = 0; - - return false; - } - - int s = GetBarsize (isVertical); - int 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; - } - - private bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false) - { - int barsize = scrollBarView._vertical ? scrollBarView.Viewport.Height : scrollBarView.Viewport.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 is { } && pending) - { - if (scrollBarView.ShowScrollIndicator) - { - scrollBarView.ShowScrollIndicator = false; - } - - if (scrollBarView.Visible) - { - scrollBarView.Visible = false; - } - - if (scrollBarView.OtherScrollBarView is { } && scrollBarView._showBothScrollIndicator) - { - scrollBarView.OtherScrollBarView.ShowScrollIndicator = false; - } - - if (scrollBarView.OtherScrollBarView.Visible) - { - scrollBarView.OtherScrollBarView.Visible = false; - } - } - else if (barsize > 0 && barsize == _size && scrollBarView.OtherScrollBarView is { } && !pending) - { - pending = true; - } - else - { - if (scrollBarView.OtherScrollBarView is { } && 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; - } - - private void ContentBottomRightCorner_DrawContent (object sender, DrawEventArgs e) - { - SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ()); - - // I'm forced to do this here because the Clear method is - // changing the color attribute and is different of this one - Driver.FillRect (Driver.Clip.GetBounds()); - e.Cancel = true; - } - - //private void Host_CanFocusChanged () - //{ - // CanFocus = Host.CanFocus; - // if (otherScrollBarView is { }) { - // otherScrollBarView.CanFocus = CanFocus; - // } - //} - - private void ContentBottomRightCorner_MouseClick (object sender, MouseEventArgs me) - { - if (me.Flags == MouseFlags.WheeledDown - || me.Flags == MouseFlags.WheeledUp - || me.Flags == MouseFlags.WheeledRight - || me.Flags == MouseFlags.WheeledLeft) - { - NewMouseEvent (me); - } - else if (me.Flags == MouseFlags.Button1Clicked) - { - Host.SetFocus (); - } - - me.Handled = true; - } - - private void CreateBottomRightCorner (View host) - { - if (Host is null) - { - Host = host; - } - - if (Host != null - && ((_contentBottomRightCorner is null && OtherScrollBarView is null) - || (_contentBottomRightCorner is null && OtherScrollBarView is { } && OtherScrollBarView._contentBottomRightCorner is null))) - { - _contentBottomRightCorner = new ContentBottomRightCorner { Visible = Host.Visible }; - - if (_hosted) - { - Host.SuperView.Add (_contentBottomRightCorner); - _contentBottomRightCorner.X = Pos.Right (Host) - 1; - _contentBottomRightCorner.Y = Pos.Bottom (Host) - 1; - } - else - { - Host.Add (_contentBottomRightCorner); - _contentBottomRightCorner.X = Pos.AnchorEnd (1); - _contentBottomRightCorner.Y = Pos.AnchorEnd (1); - } - - _contentBottomRightCorner.Width = 1; - _contentBottomRightCorner.Height = 1; - _contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick; - _contentBottomRightCorner.DrawingContent += ContentBottomRightCorner_DrawContent; - } - } - - private int GetBarsize (bool isVertical) - { - if (Host?.Viewport.IsEmpty != false) - { - return 0; - } - - return isVertical ? KeepContentAlwaysInViewport - ? Host.Viewport.Height + (_showBothScrollIndicator ? -2 : -1) - : 0 : - KeepContentAlwaysInViewport ? Host.Viewport.Width + (_showBothScrollIndicator ? -2 : -1) : 0; - } - - private void Host_EnabledChanged (object sender, EventArgs e) - { - Enabled = Host.Enabled; - - if (_otherScrollBarView is { }) - { - _otherScrollBarView.Enabled = Enabled; - } - - _contentBottomRightCorner.Enabled = Enabled; - } - - private void Host_VisibleChanged (object sender, EventArgs e) - { - if (!Host.Visible) - { - Visible = Host.Visible; - - if (_otherScrollBarView is { }) - { - _otherScrollBarView.Visible = Visible; - } - - _contentBottomRightCorner.Visible = Visible; - } - else - { - ShowHideScrollBars (); - } - } - - private void ScrollBarView_Initialized (object sender, EventArgs e) - { - SetWidthHeight (); - SetRelativeLayout (SuperView?.Frame.Size ?? Host?.Frame.Size ?? Frame.Size); - - if (OtherScrollBarView is null) - { - // Only do this once if both scrollbars are enabled - ShowHideScrollBars (); - } - - SetPosition (Position); - } - - // Helper to assist Initialized event handler - private void SetPosition (int newPosition) - { - if (!IsInitialized) - { - // We're not initialized so we can't do anything fancy. Just cache value. - _position = newPosition; - - return; - } - - if (newPosition < 0) - { - _position = 0; - SetNeedsDraw (); - - return; - } - else if (CanScroll (newPosition - _position, out int 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 (); - SetNeedsDraw (); - } - - // BUGBUG: v2 - rationalize this with View.SetMinWidthHeight - private 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 || _otherScrollBarView is { IsInitialized: false }) - { - 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; - } - } - - private void ShowHideScrollBars (bool redraw = true) - { - if (!_hosted || (_hosted && !_autoHideScrollBars)) - { - if (_contentBottomRightCorner is { } && _contentBottomRightCorner.Visible) - { - _contentBottomRightCorner.Visible = false; - } - else if (_otherScrollBarView != null - && _otherScrollBarView._contentBottomRightCorner != null - && _otherScrollBarView._contentBottomRightCorner.Visible) - { - _otherScrollBarView._contentBottomRightCorner.Visible = false; - } - - return; - } - - bool pending = CheckBothScrollBars (this); - - if (_otherScrollBarView is { }) - { - CheckBothScrollBars (_otherScrollBarView, pending); - } - - SetWidthHeight (); - SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size); - - if (_otherScrollBarView is { }) - { - OtherScrollBarView.SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size); - } - - if (_showBothScrollIndicator) - { - if (_contentBottomRightCorner is { }) - { - _contentBottomRightCorner.Visible = true; - } - else if (_otherScrollBarView is { } && _otherScrollBarView._contentBottomRightCorner is { }) - { - _otherScrollBarView._contentBottomRightCorner.Visible = true; - } - } - else if (!ShowScrollIndicator) - { - if (_contentBottomRightCorner is { }) - { - _contentBottomRightCorner.Visible = false; - } - else if (_otherScrollBarView is { } && _otherScrollBarView._contentBottomRightCorner is { }) - { - _otherScrollBarView._contentBottomRightCorner.Visible = false; - } - - if (Application.MouseGrabView is { } && Application.MouseGrabView == this) - { - Application.UngrabMouse (); - } - } - else if (_contentBottomRightCorner is { }) - { - _contentBottomRightCorner.Visible = false; - } - else if (_otherScrollBarView is { } && _otherScrollBarView._contentBottomRightCorner is { }) - { - _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 is { } && _otherScrollBarView.ShowScrollIndicator) - { - _otherScrollBarView.Draw (); - } - - if (_contentBottomRightCorner is { } && _contentBottomRightCorner.Visible) - { - _contentBottomRightCorner.Draw (); - } - else if (_otherScrollBarView is { } && _otherScrollBarView._contentBottomRightCorner is { } && _otherScrollBarView._contentBottomRightCorner.Visible) - { - _otherScrollBarView._contentBottomRightCorner.Draw (); - } - } - - internal class ContentBottomRightCorner : View - { - public ContentBottomRightCorner () - { - ColorScheme = ColorScheme; - } - } -} diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs deleted file mode 100644 index aeae539add..0000000000 --- a/Terminal.Gui/Views/ScrollView.cs +++ /dev/null @@ -1,774 +0,0 @@ -// -// ScrollView.cs: ScrollView view. -// -// Authors: -// Miguel de Icaza (miguel@gnome.org) -// -// -// TODO: -// - focus in scrollview -// - focus handling in scrollview to auto scroll to focused view -// - Raise events -// - Perhaps allow an option to not display the scrollbar arrow indicators? - -using System.ComponentModel; - -namespace Terminal.Gui; - -/// -/// Scrollviews are views that present a window into a virtual space where subviews are added. Similar to the iOS -/// UIScrollView. -/// -/// -/// -/// The subviews that are added to this are offset by the -/// property. The view itself is a window into the space represented by the -/// . -/// -/// Use the -/// -public class ScrollView : View -{ - private readonly ContentView _contentView; - private readonly ScrollBarView _horizontal; - private readonly ScrollBarView _vertical; - private bool _autoHideScrollBars = true; - private View _contentBottomRightCorner; - private Point _contentOffset; - private bool _keepContentAlwaysInViewport = true; - private bool _showHorizontalScrollIndicator; - private bool _showVerticalScrollIndicator; - - /// - /// Initializes a new instance of the class. - /// - public ScrollView () - { - _contentView = new ContentView (); - - _vertical = new ScrollBarView - { - X = Pos.AnchorEnd (1), - Y = 0, - Width = 1, - Height = Dim.Fill (_showHorizontalScrollIndicator ? 1 : 0), - Size = 1, - IsVertical = true, - Host = this - }; - - _horizontal = new ScrollBarView - { - X = 0, - Y = Pos.AnchorEnd (1), - Width = Dim.Fill (_showVerticalScrollIndicator ? 1 : 0), - Height = 1, - Size = 1, - IsVertical = false, - Host = this - }; - - _vertical.OtherScrollBarView = _horizontal; - _horizontal.OtherScrollBarView = _vertical; - base.Add (_contentView); - CanFocus = true; - TabStop = TabBehavior.TabGroup; - - MouseEnter += View_MouseEnter; - MouseLeave += View_MouseLeave; - _contentView.MouseEnter += View_MouseEnter; - _contentView.MouseLeave += View_MouseLeave; - - Application.UnGrabbedMouse += Application_UnGrabbedMouse; - - // Things this view knows how to do - AddCommand (Command.ScrollUp, () => ScrollUp (1)); - AddCommand (Command.ScrollDown, () => ScrollDown (1)); - AddCommand (Command.ScrollLeft, () => ScrollLeft (1)); - AddCommand (Command.ScrollRight, () => ScrollRight (1)); - AddCommand (Command.PageUp, () => ScrollUp (Viewport.Height)); - AddCommand (Command.PageDown, () => ScrollDown (Viewport.Height)); - AddCommand (Command.PageLeft, () => ScrollLeft (Viewport.Width)); - AddCommand (Command.PageRight, () => ScrollRight (Viewport.Width)); - AddCommand (Command.Start, () => ScrollUp (GetContentSize ().Height)); - AddCommand (Command.End, () => ScrollDown (GetContentSize ().Height)); - AddCommand (Command.LeftStart, () => ScrollLeft (GetContentSize ().Width)); - AddCommand (Command.RightEnd, () => ScrollRight (GetContentSize ().Width)); - - // Default keybindings for this view - KeyBindings.Add (Key.CursorUp, Command.ScrollUp); - KeyBindings.Add (Key.CursorDown, Command.ScrollDown); - KeyBindings.Add (Key.CursorLeft, Command.ScrollLeft); - KeyBindings.Add (Key.CursorRight, Command.ScrollRight); - - KeyBindings.Add (Key.PageUp, Command.PageUp); - KeyBindings.Add (Key.V.WithAlt, Command.PageUp); - - KeyBindings.Add (Key.PageDown, Command.PageDown); - KeyBindings.Add (Key.V.WithCtrl, Command.PageDown); - - KeyBindings.Add (Key.PageUp.WithCtrl, Command.PageLeft); - KeyBindings.Add (Key.PageDown.WithCtrl, Command.PageRight); - KeyBindings.Add (Key.Home, Command.Start); - KeyBindings.Add (Key.End, Command.End); - KeyBindings.Add (Key.Home.WithCtrl, Command.LeftStart); - KeyBindings.Add (Key.End.WithCtrl, Command.RightEnd); - - Initialized += (s, e) => - { - if (!_vertical.IsInitialized) - { - _vertical.BeginInit (); - _vertical.EndInit (); - } - - if (!_horizontal.IsInitialized) - { - _horizontal.BeginInit (); - _horizontal.EndInit (); - } - - SetContentOffset (_contentOffset); - _contentView.Frame = new Rectangle (ContentOffset, GetContentSize ()); - - // PERF: How about calls to Point.Offset instead? - _vertical.ChangedPosition += delegate { ContentOffset = new Point (ContentOffset.X, _vertical.Position); }; - _horizontal.ChangedPosition += delegate { ContentOffset = new Point (_horizontal.Position, ContentOffset.Y); }; - }; - ContentSizeChanged += ScrollViewContentSizeChanged; - } - - private void ScrollViewContentSizeChanged (object sender, SizeChangedEventArgs e) - { - if (e.Size is null) - { - return; - } - _contentView.Frame = new Rectangle (ContentOffset, e.Size.Value with { Width = e.Size.Value.Width - 1, Height = e.Size.Value.Height - 1 }); - _vertical.Size = e.Size.Value.Height; - _horizontal.Size = e.Size.Value.Width; - } - - private void Application_UnGrabbedMouse (object sender, ViewEventArgs e) - { - var parent = e.View is Adornment adornment ? adornment.Parent : e.View; - - if (parent is { }) - { - var supView = parent.SuperView; - - while (supView is { }) - { - if (supView == _contentView) - { - Application.GrabMouse (this); - - break; - } - - supView = supView.SuperView; - } - } - } - - /// 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; - - if (Subviews.Contains (_vertical)) - { - _vertical.AutoHideScrollBars = value; - } - - if (Subviews.Contains (_horizontal)) - { - _horizontal.AutoHideScrollBars = value; - } - - SetNeedsDraw (); - } - } - } - - /// Represents the top left corner coordinate that is displayed by the scrollview - /// The content offset. - public Point ContentOffset - { - get => _contentOffset; - set - { - if (!IsInitialized) - { - // We're not initialized so we can't do anything fancy. Just cache value. - _contentOffset = new Point (-Math.Abs (value.X), -Math.Abs (value.Y)); - - return; - } - - SetContentOffset (value); - } - } - - ///// Represents the contents of the data shown inside the scrollview - ///// The size of the content. - //public new Size ContentSize - //{ - // get => ContentSize; - // set - // { - // if (GetContentSize () != value) - // { - // ContentSize = value; - // _contentView.Frame = new Rectangle (_contentOffset, value); - // _vertical.Size = GetContentSize ().Height; - // _horizontal.Size = GetContentSize ().Width; - // SetNeedsDraw (); - // } - // } - //} - - /// 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; - _vertical.OtherScrollBarView.KeepContentAlwaysInViewport = value; - _horizontal.OtherScrollBarView.KeepContentAlwaysInViewport = value; - Point p = default; - - if (value && -_contentOffset.X + Viewport.Width > GetContentSize ().Width) - { - p = new Point ( - GetContentSize ().Width - Viewport.Width + (_showVerticalScrollIndicator ? 1 : 0), - -_contentOffset.Y - ); - } - - if (value && -_contentOffset.Y + Viewport.Height > GetContentSize ().Height) - { - if (p == default (Point)) - { - p = new Point ( - -_contentOffset.X, - GetContentSize ().Height - Viewport.Height + (_showHorizontalScrollIndicator ? 1 : 0) - ); - } - else - { - p.Y = GetContentSize ().Height - Viewport.Height + (_showHorizontalScrollIndicator ? 1 : 0); - } - } - - if (p != default (Point)) - { - ContentOffset = p; - } - } - } - } - - /// Gets or sets the visibility for the horizontal scroll indicator. - /// true if show horizontal scroll indicator; otherwise, false. - public bool ShowHorizontalScrollIndicator - { - get => _showHorizontalScrollIndicator; - set - { - if (value != _showHorizontalScrollIndicator) - { - _showHorizontalScrollIndicator = value; - SetNeedsLayout (); - - if (value) - { - _horizontal.OtherScrollBarView = _vertical; - base.Add (_horizontal); - _horizontal.ShowScrollIndicator = value; - _horizontal.AutoHideScrollBars = _autoHideScrollBars; - _horizontal.OtherScrollBarView.ShowScrollIndicator = value; - _horizontal.MouseEnter += View_MouseEnter; - _horizontal.MouseLeave += View_MouseLeave; - } - else - { - base.Remove (_horizontal); - _horizontal.OtherScrollBarView = null; - _horizontal.MouseEnter -= View_MouseEnter; - _horizontal.MouseLeave -= View_MouseLeave; - } - } - - _vertical.Height = Dim.Fill (_showHorizontalScrollIndicator ? 1 : 0); - } - } - - /// Gets or sets the visibility for the vertical scroll indicator. - /// true if show vertical scroll indicator; otherwise, false. - public bool ShowVerticalScrollIndicator - { - get => _showVerticalScrollIndicator; - set - { - if (value != _showVerticalScrollIndicator) - { - _showVerticalScrollIndicator = value; - SetNeedsLayout (); - - if (value) - { - _vertical.OtherScrollBarView = _horizontal; - base.Add (_vertical); - _vertical.ShowScrollIndicator = value; - _vertical.AutoHideScrollBars = _autoHideScrollBars; - _vertical.OtherScrollBarView.ShowScrollIndicator = value; - _vertical.MouseEnter += View_MouseEnter; - _vertical.MouseLeave += View_MouseLeave; - } - else - { - Remove (_vertical); - _vertical.OtherScrollBarView = null; - _vertical.MouseEnter -= View_MouseEnter; - _vertical.MouseLeave -= View_MouseLeave; - } - } - - _horizontal.Width = Dim.Fill (_showVerticalScrollIndicator ? 1 : 0); - } - } - - /// Adds the view to the scrollview. - /// The view to add to the scrollview. - public override View Add (View view) - { - if (view is ScrollBarView.ContentBottomRightCorner) - { - _contentBottomRightCorner = view; - base.Add (view); - } - else - { - if (!IsOverridden (view, "OnMouseEvent")) - { - view.MouseEnter += View_MouseEnter; - view.MouseLeave += View_MouseLeave; - } - - _contentView.Add (view); - } - - SetNeedsLayout (); - return view; - } - - /// - protected override bool OnDrawingContent () - { - SetViewsNeedsDraw (); - - // TODO: It's bad practice for views to always clear a view. It negates clipping. - ClearViewport (); - - if (!string.IsNullOrEmpty (_contentView.Text) || _contentView.Subviews.Count > 0) - { - Region? saved = ClipFrame(); - _contentView.Draw (); - View.SetClip (saved); - } - - DrawScrollBars (); - - return true; - } - - /// - protected override bool OnKeyDown (Key a) - { - if (base.OnKeyDown (a)) - { - return true; - } - - bool? result = InvokeCommands (a, KeyBindingScope.HotKey | KeyBindingScope.Focused); - - if (result is { }) - { - return (bool)result; - } - - return false; - } - - /// - protected override bool OnMouseEvent (MouseEventArgs me) - { - if (!Enabled) - { - // A disabled view should not eat mouse events - return false; - } - - if (me.Flags == MouseFlags.WheeledDown && ShowVerticalScrollIndicator) - { - return ScrollDown (1); - } - else if (me.Flags == MouseFlags.WheeledUp && ShowVerticalScrollIndicator) - { - return ScrollUp (1); - } - else if (me.Flags == MouseFlags.WheeledRight && _showHorizontalScrollIndicator) - { - return ScrollRight (1); - } - else if (me.Flags == MouseFlags.WheeledLeft && ShowVerticalScrollIndicator) - { - return ScrollLeft (1); - } - else if (me.Position.X == _vertical.Frame.X && ShowVerticalScrollIndicator) - { - _vertical.NewMouseEvent (me); - } - else if (me.Position.Y == _horizontal.Frame.Y && ShowHorizontalScrollIndicator) - { - _horizontal.NewMouseEvent (me); - } - else if (IsOverridden (me.View, "OnMouseEvent")) - { - Application.UngrabMouse (); - } - - return me.Handled; - } - - /// - public override Point? PositionCursor () - { - if (InternalSubviews.Count == 0) - { - Move (0, 0); - - return null; // Don't show the cursor - } - return base.PositionCursor (); - } - - /// Removes the view from the scrollview. - /// The view to remove from the scrollview. - public override View Remove (View view) - { - if (view is null) - { - return view; - } - - SetNeedsDraw (); - View container = view?.SuperView; - - if (container == this) - { - base.Remove (view); - } - else - { - container?.Remove (view); - } - - if (_contentView.InternalSubviews.Count < 1) - { - CanFocus = false; - } - - return view; - } - - /// Removes all widgets from this container. - public override void RemoveAll () { _contentView.RemoveAll (); } - - /// Scrolls the view down. - /// true, if left was scrolled, false otherwise. - /// Number of lines to scroll. - public bool ScrollDown (int lines) - { - if (_vertical.CanScroll (lines, out _, true)) - { - ContentOffset = new Point (_contentOffset.X, _contentOffset.Y - lines); - - return true; - } - - return false; - } - - /// Scrolls the view to the left - /// true, if left was scrolled, false otherwise. - /// Number of columns to scroll by. - public bool ScrollLeft (int cols) - { - if (_contentOffset.X < 0) - { - ContentOffset = new Point (Math.Min (_contentOffset.X + cols, 0), _contentOffset.Y); - - return true; - } - - return false; - } - - /// Scrolls the view to the right. - /// true, if right was scrolled, false otherwise. - /// Number of columns to scroll by. - public bool ScrollRight (int cols) - { - if (_horizontal.CanScroll (cols, out _)) - { - ContentOffset = new Point (_contentOffset.X - cols, _contentOffset.Y); - - return true; - } - - return false; - } - - /// Scrolls the view up. - /// true, if left was scrolled, false otherwise. - /// Number of lines to scroll. - public bool ScrollUp (int lines) - { - if (_contentOffset.Y < 0) - { - ContentOffset = new Point (_contentOffset.X, Math.Min (_contentOffset.Y + lines, 0)); - - return true; - } - - return false; - } - - /// - protected override void Dispose (bool disposing) - { - if (!_showVerticalScrollIndicator) - { - // It was not added to SuperView, so it won't get disposed automatically - _vertical?.Dispose (); - } - - if (!_showHorizontalScrollIndicator) - { - // It was not added to SuperView, so it won't get disposed automatically - _horizontal?.Dispose (); - } - - Application.UnGrabbedMouse -= Application_UnGrabbedMouse; - - base.Dispose (disposing); - } - - private void DrawScrollBars () - { - if (_autoHideScrollBars) - { - ShowHideScrollBars (); - } - else - { - if (ShowVerticalScrollIndicator) - { - Region? saved = View.SetClipToScreen (); - _vertical.Draw (); - View.SetClip (saved); - } - - if (ShowHorizontalScrollIndicator) - { - Region? saved = View.SetClipToScreen (); - _horizontal.Draw (); - View.SetClip (saved); - } - - if (ShowVerticalScrollIndicator && ShowHorizontalScrollIndicator) - { - SetContentBottomRightCornerVisibility (); - Region? saved = View.SetClipToScreen (); - _contentBottomRightCorner.Draw (); - View.SetClip (saved); - } - } - } - - private void SetContentBottomRightCornerVisibility () - { - if (_showHorizontalScrollIndicator && _showVerticalScrollIndicator) - { - _contentBottomRightCorner.Visible = true; - } - else if (_horizontal.IsAdded || _vertical.IsAdded) - { - _contentBottomRightCorner.Visible = false; - } - } - - private void SetContentOffset (Point offset) - { - // INTENT: Unclear intent. How about a call to Offset? - _contentOffset = new Point (-Math.Abs (offset.X), -Math.Abs (offset.Y)); - _contentView.Frame = new Rectangle (_contentOffset, GetContentSize ()); - int p = Math.Max (0, -_contentOffset.Y); - - if (_vertical.Position != p) - { - _vertical.Position = Math.Max (0, -_contentOffset.Y); - } - - p = Math.Max (0, -_contentOffset.X); - - if (_horizontal.Position != p) - { - _horizontal.Position = Math.Max (0, -_contentOffset.X); - } - SetNeedsDraw (); - } - - private void SetViewsNeedsDraw () - { - foreach (View view in _contentView.Subviews) - { - view.SetNeedsDraw (); - } - } - - private void ShowHideScrollBars () - { - bool v = false, h = false; - var p = false; - - if (GetContentSize () is { } && (Viewport.Height == 0 || Viewport.Height > GetContentSize ().Height)) - { - if (ShowVerticalScrollIndicator) - { - ShowVerticalScrollIndicator = false; - } - - v = false; - } - else if (GetContentSize () is { } && Viewport.Height > 0 && Viewport.Height == GetContentSize ().Height) - { - p = true; - } - else - { - if (!ShowVerticalScrollIndicator) - { - ShowVerticalScrollIndicator = true; - } - - v = true; - } - - if (GetContentSize () is { } && (Viewport.Width == 0 || Viewport.Width > GetContentSize ().Width)) - { - if (ShowHorizontalScrollIndicator) - { - ShowHorizontalScrollIndicator = false; - } - - h = false; - } - else if (GetContentSize () is { } && Viewport.Width > 0 && Viewport.Width == GetContentSize ().Width && p) - { - if (ShowHorizontalScrollIndicator) - { - ShowHorizontalScrollIndicator = false; - } - - h = false; - - if (ShowVerticalScrollIndicator) - { - ShowVerticalScrollIndicator = false; - } - - v = false; - } - else - { - if (p) - { - if (!ShowVerticalScrollIndicator) - { - ShowVerticalScrollIndicator = true; - } - - v = true; - } - - if (!ShowHorizontalScrollIndicator) - { - ShowHorizontalScrollIndicator = true; - } - - h = true; - } - - Dim dim = Dim.Fill (h ? 1 : 0); - - if (!_vertical.Height.Equals (dim)) - { - _vertical.Height = dim; - } - - dim = Dim.Fill (v ? 1 : 0); - - if (!_horizontal.Width.Equals (dim)) - { - _horizontal.Width = dim; - } - - if (v) - { - _vertical.SetRelativeLayout (Viewport.Size); - _vertical.Draw (); - } - - if (h) - { - _horizontal.SetRelativeLayout (Viewport.Size); - _horizontal.Draw (); - } - - SetContentBottomRightCornerVisibility (); - - if (v && h) - { - _contentBottomRightCorner.SetRelativeLayout (Viewport.Size); - _contentBottomRightCorner.Draw (); - } - } - - private void View_MouseEnter (object sender, CancelEventArgs e) { Application.GrabMouse (this); } - - private void View_MouseLeave (object sender, EventArgs e) - { - if (Application.MouseGrabView is { } && Application.MouseGrabView != this && Application.MouseGrabView != _vertical && Application.MouseGrabView != _horizontal) - { - Application.UngrabMouse (); - } - } - - // The ContentView is the view that contains the subviews and content that are being scrolled - // The ContentView is the size of the ContentSize and is offset by the ContentOffset - private class ContentView : View - { - public ContentView () { CanFocus = true; } - } -} diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs index 27d17fa6bc..ff37d513c0 100644 --- a/Terminal.Gui/Views/Slider.cs +++ b/Terminal.Gui/Views/Slider.cs @@ -47,7 +47,7 @@ private void SetInitialProperties ( _options = options ?? new List> (); - _orientationHelper = new (this); + _orientationHelper = new (this); // Do not use object initializer! _orientationHelper.Orientation = _config._sliderOrientation = orientation; _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e); _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e); diff --git a/UICatalog/Scenarios/ASCIICustomButton.cs b/UICatalog/Scenarios/ASCIICustomButton.cs deleted file mode 100644 index d7e181eae1..0000000000 --- a/UICatalog/Scenarios/ASCIICustomButton.cs +++ /dev/null @@ -1,400 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using JetBrains.Annotations; -using Terminal.Gui; - -namespace UICatalog.Scenarios; - -[ScenarioMetadata ("ASCIICustomButtonTest", "ASCIICustomButton sample")] -[ScenarioCategory ("Controls")] -public class ASCIICustomButtonTest : Scenario -{ - private static bool _smallerWindow; - private MenuItem _miSmallerWindow; - private ScrollViewTestWindow _scrollViewTestWindow; - - public override void Main () - { - _smallerWindow = false; - - Application.Init (); - Toplevel top = new (); - - var menu = new MenuBar - { - Menus = - [ - new MenuBarItem ( - "_Window Size", - new [] - { - _miSmallerWindow = - new MenuItem ( - "Smaller Window", - "", - ChangeWindowSize - ) - { - CheckType = MenuItemCheckStyle - .Checked - }, - null, - new MenuItem ( - "Quit", - "", - () => Application.RequestStop (), - null, - null, - (KeyCode)Application.QuitKey - ) - } - ) - ] - }; - - _scrollViewTestWindow = new ScrollViewTestWindow { Y = Pos.Bottom (menu) }; - - top.Add (menu, _scrollViewTestWindow); - Application.Run (top); - top.Dispose (); - - Application.Shutdown (); - - return; - - void ChangeWindowSize () - { - _smallerWindow = (bool)(_miSmallerWindow.Checked = !_miSmallerWindow.Checked); - top.Remove (_scrollViewTestWindow); - _scrollViewTestWindow.Dispose (); - - _scrollViewTestWindow = new ScrollViewTestWindow (); - top.Add (_scrollViewTestWindow); - } - } - - public class ASCIICustomButton : Button - { - private FrameView _border; - private Label _fill; - public string Description => $"Description of: {Id}"; - - public void CustomInitialize () - { - _border = new FrameView { Width = Width, Height = Height }; - - var fillText = new StringBuilder (); - - for (var i = 0; i < Viewport.Height; i++) - { - if (i > 0) - { - fillText.AppendLine (""); - } - - for (var j = 0; j < Viewport.Width; j++) - { - fillText.Append ("█"); - } - } - - _fill = new Label { Visible = false, CanFocus = false, Text = fillText.ToString () }; - - var title = new Label { X = Pos.Center (), Y = Pos.Center (), Text = Text }; - - _border.MouseClick += This_MouseClick; - _fill.MouseClick += This_MouseClick; - title.MouseClick += This_MouseClick; - - Add (_border, _fill, title); - } - - protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedView) - { - if (newHasFocus) - { - _border.Visible = false; - _fill.Visible = true; - PointerEnter?.Invoke (this); - } - else - { - _border.Visible = true; - _fill.Visible = false; - } - } - - public event Action PointerEnter; - private void This_MouseClick (object sender, MouseEventArgs obj) { NewMouseEvent (obj); } - } - - public class ScrollViewTestWindow : Window - { - private const int BUTTON_HEIGHT = 3; - private const int BUTTON_WIDTH = 25; - private const int BUTTONS_ON_PAGE = 7; - - private readonly List