#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));
}
}