#nullable enable using System.ComponentModel; namespace Terminal.Gui; /// /// Indicates the position and size of scrollable content. The indicator can be dragged with the mouse. Can be /// oriented either vertically or horizontally. Used within a . /// /// /// /// By default, this view cannot be focused and does not support keyboard. /// /// public class Scroll : View { /// public Scroll () { _slider = new (); Add (_slider); WantContinuousButtonPressed = true; CanFocus = false; Orientation = Orientation.Vertical; Width = Dim.Auto (DimAutoStyle.Content, 1); Height = Dim.Auto (DimAutoStyle.Content, 1); } internal readonly ScrollSlider _slider; private Orientation _orientation; private int _position; private int _size; /// public override void EndInit () { base.EndInit (); AdjustScroll (); } /// /// Gets or sets if the Scroll is oriented vertically or horizontally. /// public Orientation Orientation { get => _orientation; set { _orientation = value; AdjustScroll (); } } /// /// Gets or sets the position of the start of the Scroll slider, relative to . /// public int Position { get => _position; set { if (value == _position || value < 0) { return; } if (SuperViewAsScrollBar is { IsInitialized: false }) { // Ensures a more exactly calculation SetRelativeLayout (SuperViewAsScrollBar.Frame.Size); } int barSize = BarSize; if (value + barSize > Size) { return; } CancelEventArgs args = OnPositionChanging (_position, value); if (args.Cancel) { return; } _position = value; AdjustScroll (); OnPositionChanged (_position); } } /// Raised when the has changed. public event EventHandler>? PositionChanged; /// /// Raised when the is changing. Set to /// to prevent the position from being changed. /// public event EventHandler>? PositionChanging; /// /// 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 => _size; set { _size = value; OnSizeChanged (_size); AdjustScroll (); } } /// Raised when has changed. public event EventHandler>? SizeChanged; /// protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { int location = Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X; int barSize = BarSize; (int start, int end) sliderPos = _orientation == Orientation.Vertical ? new (_slider.Frame.Y, _slider.Frame.Bottom - 1) : new (_slider.Frame.X, _slider.Frame.Right - 1); if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location < sliderPos.start) { int distance = sliderPos.start - location; int scrollAmount = (int)((double)distance / barSize * (Size - barSize)); Position = Math.Max (Position - scrollAmount, 0); } else if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && location > sliderPos.end) { int distance = location - sliderPos.end; int scrollAmount = (int)((double)distance / barSize * (Size - barSize)); Position = Math.Min (Position + scrollAmount, Size - barSize); } else if ((mouseEvent.Flags == MouseFlags.WheeledDown && Orientation == Orientation.Vertical) || (mouseEvent.Flags == MouseFlags.WheeledRight && Orientation == Orientation.Horizontal)) { Position = Math.Min (Position + 1, Size - barSize); } else if ((mouseEvent.Flags == MouseFlags.WheeledUp && Orientation == Orientation.Vertical) || (mouseEvent.Flags == MouseFlags.WheeledLeft && Orientation == Orientation.Horizontal)) { Position = Math.Max (Position - 1, 0); } else if (mouseEvent.Flags == MouseFlags.Button1Clicked) { if (_slider.Frame.Contains (mouseEvent.Position)) { return _slider.OnMouseEvent (mouseEvent); } } return base.OnMouseEvent (mouseEvent); } /// Virtual method called when has changed. Raises . protected virtual void OnPositionChanged (int position) { PositionChanged?.Invoke (this, new (in position)); } /// /// Virtual method called when is changing. Raises , which is /// cancelable. /// protected virtual CancelEventArgs OnPositionChanging (int currentPos, int newPos) { CancelEventArgs args = new (ref currentPos, ref newPos); PositionChanging?.Invoke (this, args); return args; } /// Called when has changed. Raises . protected void OnSizeChanged (int size) { SizeChanged?.Invoke (this, new (in size)); } internal void AdjustScroll () { if (SuperViewAsScrollBar is { }) { X = Orientation == Orientation.Vertical ? 0 : 1; Y = Orientation == Orientation.Vertical ? 1 : 0; Width = Orientation == Orientation.Vertical ? Dim.Fill () : Dim.Fill (1); Height = Orientation == Orientation.Vertical ? Dim.Fill (1) : Dim.Fill (); } _slider.AdjustSlider (); SetScrollText (); } /// internal override void OnLayoutComplete (LayoutEventArgs args) { base.OnLayoutComplete (args); AdjustScroll (); } internal ScrollBar? SuperViewAsScrollBar => SuperView as ScrollBar; private int BarSize => Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width; private void SetScrollText () { TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom; // QUESTION: Should these Glyphs be configurable via CM? Text = string.Concat ( Enumerable.Repeat ( Glyphs.Stipple.ToString (), GetContentSize ().Width * GetContentSize ().Height)); } }