mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-27 00:07:58 +01:00
Refactored and went back and forth. Things are working well. Tests not so much
This commit is contained in:
@@ -130,11 +130,13 @@ public partial class View
|
||||
{
|
||||
if (_verticalScrollBar.IsValueCreated)
|
||||
{
|
||||
_verticalScrollBar.Value.ViewportDimension = Viewport.Height;
|
||||
_verticalScrollBar.Value.ContentPosition = Viewport.Y;
|
||||
}
|
||||
|
||||
if (_horizontalScrollBar.IsValueCreated)
|
||||
{
|
||||
_horizontalScrollBar.Value.ViewportDimension = Viewport.Width;
|
||||
_horizontalScrollBar.Value.ContentPosition = Viewport.X;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,8 +24,9 @@ public class Scroll : View, IOrientation, IDesignable
|
||||
public Scroll ()
|
||||
{
|
||||
_slider = new ();
|
||||
Add (_slider);
|
||||
_slider.FrameChanged += OnSliderOnFrameChanged;
|
||||
base.Add (_slider);
|
||||
_slider.Scroll += SliderOnScroll;
|
||||
_slider.PositionChanged += SliderOnPositionChanged;
|
||||
|
||||
CanFocus = false;
|
||||
|
||||
@@ -38,6 +39,7 @@ public class Scroll : View, IOrientation, IDesignable
|
||||
OnOrientationChanged (Orientation);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnSubviewLayout (LayoutEventArgs args)
|
||||
{
|
||||
@@ -104,12 +106,32 @@ public class Scroll : View, IOrientation, IDesignable
|
||||
set => _slider.ShowPercent = value;
|
||||
}
|
||||
|
||||
private int ViewportDimension => Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width;
|
||||
private int? _viewportDimension;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the viewport into the content being scrolled, bounded by <see cref="Size"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If not explicitly set, will be the appropriate dimension of the Scroll's Frame.
|
||||
/// </remarks>
|
||||
public int ViewportDimension
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_viewportDimension.HasValue)
|
||||
{
|
||||
return _viewportDimension.Value;
|
||||
}
|
||||
return Orientation == Orientation.Vertical ? Frame.Height : Frame.Width;
|
||||
|
||||
}
|
||||
set => _viewportDimension = value;
|
||||
}
|
||||
|
||||
private int _size;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the total size of the content that can be scrolled.
|
||||
/// Gets or sets the size of the content that can be scrolled.
|
||||
/// </summary>
|
||||
public int Size
|
||||
{
|
||||
@@ -135,43 +157,37 @@ public class Scroll : View, IOrientation, IDesignable
|
||||
public event EventHandler<EventArgs<int>>? SizeChanged;
|
||||
|
||||
#region SliderPosition
|
||||
private void OnSliderOnFrameChanged (object? sender, EventArgs<Rectangle> args)
|
||||
|
||||
private void SliderOnPositionChanged (object? sender, EventArgs<int> e)
|
||||
{
|
||||
if (ViewportDimension == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int framePos = Orientation == Orientation.Vertical ? args.CurrentValue.Y : args.CurrentValue.X;
|
||||
int calculatedSliderPos = CalculateSliderPosition (_contentPosition);
|
||||
|
||||
RaiseSliderPositionChangeEvents (CalculateSliderPosition (_contentPosition), framePos);
|
||||
ContentPosition = (int)Math.Round ((double)e.CurrentValue / (ViewportDimension - _slider.Size) * (Size - ViewportDimension));
|
||||
|
||||
RaiseSliderPositionChangeEvents (calculatedSliderPos, e.CurrentValue);
|
||||
}
|
||||
|
||||
private void SliderOnScroll (object? sender, EventArgs<int> e)
|
||||
{
|
||||
if (ViewportDimension == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position of the start of the Scroll slider, within the Viewport.
|
||||
/// </summary>
|
||||
public int SliderPosition
|
||||
public int GetSliderPosition () => CalculateSliderPosition (_contentPosition);
|
||||
|
||||
private void RaiseSliderPositionChangeEvents (int calculatedSliderPosition, int newSliderPosition)
|
||||
{
|
||||
get => CalculateSliderPosition (_contentPosition);
|
||||
set => RaiseSliderPositionChangeEvents (_slider.Position, value);
|
||||
}
|
||||
|
||||
private void RaiseSliderPositionChangeEvents (int currentSliderPosition, int newSliderPosition)
|
||||
{
|
||||
if (/*newSliderPosition > Size - ViewportDimension ||*/ currentSliderPosition == newSliderPosition)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OnSliderPositionChanging (currentSliderPosition, newSliderPosition))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CancelEventArgs<int> args = new (ref currentSliderPosition, ref newSliderPosition);
|
||||
SliderPositionChanging?.Invoke (this, args);
|
||||
|
||||
if (args.Cancel)
|
||||
if (/*newSliderPosition > Size - ViewportDimension ||*/ calculatedSliderPosition == newSliderPosition)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -179,27 +195,14 @@ public class Scroll : View, IOrientation, IDesignable
|
||||
// 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when <see cref="SliderPosition"/> is changing. Return true to cancel the change.
|
||||
/// </summary>
|
||||
protected virtual bool OnSliderPositionChanging (int currentSliderPosition, int newSliderPosition) { return false; }
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the <see cref="SliderPosition"/> is changing. Set <see cref="CancelEventArgs.Cancel"/> to
|
||||
/// <see langword="true"/> to prevent the position from being changed.
|
||||
/// </summary>
|
||||
public event EventHandler<CancelEventArgs<int>>? SliderPositionChanging;
|
||||
|
||||
/// <summary>Called when <see cref="SliderPosition"/> has changed.</summary>
|
||||
/// <summary>Called when the slider position has changed.</summary>
|
||||
protected virtual void OnSliderPositionChanged (int position) { }
|
||||
|
||||
/// <summary>Raised when the <see cref="SliderPosition"/> has changed.</summary>
|
||||
/// <summary>Raised when the slider position has changed.</summary>
|
||||
public event EventHandler<EventArgs<int>>? SliderPositionChanged;
|
||||
|
||||
private int CalculateSliderPosition (int contentPosition)
|
||||
@@ -219,8 +222,17 @@ public class Scroll : View, IOrientation, IDesignable
|
||||
private int _contentPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position of the ScrollSlider within the range of 0...<see cref="Size"/>.
|
||||
/// Gets or sets the position of the slider relative to <see cref="Size"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The content position is clamped to 0 and <see cref="Size"/> minus <see cref="ViewportDimension"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Setting will result in the <see cref="ContentPositionChanging"/> and <see cref="ContentPositionChanged"/>
|
||||
/// events being raised.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public int ContentPosition
|
||||
{
|
||||
get => _contentPosition;
|
||||
@@ -231,14 +243,17 @@ public class Scroll : View, IOrientation, IDesignable
|
||||
return;
|
||||
}
|
||||
|
||||
RaiseContentPositionChangeEvents (value);
|
||||
// Clamp the value between 0 and Size - ViewportDimension
|
||||
int newContentPosition = (int)Math.Clamp (value, 0, Math.Max (0, Size - ViewportDimension));
|
||||
|
||||
RaiseContentPositionChangeEvents (newContentPosition);
|
||||
|
||||
_slider.SetPosition (CalculateSliderPosition (_contentPosition));
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
@@ -255,8 +270,6 @@ public class Scroll : View, IOrientation, IDesignable
|
||||
|
||||
_contentPosition = newContentPosition;
|
||||
|
||||
SliderPosition = CalculateSliderPosition (_contentPosition);
|
||||
|
||||
OnContentPositionChanged (_contentPosition);
|
||||
ContentPositionChanged?.Invoke (this, new (in _contentPosition));
|
||||
}
|
||||
@@ -291,35 +304,39 @@ public class Scroll : View, IOrientation, IDesignable
|
||||
/// <inheritdoc/>
|
||||
protected override bool OnMouseClick (MouseEventArgs args)
|
||||
{
|
||||
// Check if the mouse click is a single click
|
||||
if (!args.IsSingleClicked)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int sliderCenter;
|
||||
int distanceFromCenter;
|
||||
|
||||
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;
|
||||
sliderCenter = _slider.Frame.Y + _slider.Frame.Height / 2;
|
||||
distanceFromCenter = args.Position.Y - sliderCenter;
|
||||
}
|
||||
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;
|
||||
sliderCenter = _slider.Frame.X + _slider.Frame.Width / 2;
|
||||
distanceFromCenter = args.Position.X - sliderCenter;
|
||||
}
|
||||
|
||||
// Ratio of the distance to the viewport dimension
|
||||
double ratio = (double)Math.Abs (distanceFromCenter) / ViewportDimension;
|
||||
// Jump size based on the ratio and the total content size
|
||||
int jump = (int)Math.Ceiling (ratio * Size);
|
||||
|
||||
// Adjust the content position based on the distance
|
||||
ContentPosition += distanceFromCenter < 0 ? -jump : jump;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount each mouse hweel event will incremenet/decrement the <see cref="ContentPosition"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -11,9 +11,6 @@ namespace Terminal.Gui;
|
||||
/// and clicking with the mouse to scroll.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="SliderPosition"/> indicates the number of rows or columns the Scroll has moved from 0.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class ScrollBar : View, IOrientation, IDesignable
|
||||
{
|
||||
@@ -27,7 +24,6 @@ public class ScrollBar : View, IOrientation, IDesignable
|
||||
CanFocus = false;
|
||||
|
||||
_scroll = new ();
|
||||
_scroll.SliderPositionChanging += OnScrollOnSliderPositionChanging;
|
||||
_scroll.SliderPositionChanged += OnScrollOnSliderPositionChanged;
|
||||
_scroll.ContentPositionChanging += OnScrollOnContentPositionChanging;
|
||||
_scroll.ContentPositionChanged += OnScrollOnContentPositionChanged;
|
||||
@@ -112,6 +108,8 @@ public class ScrollBar : View, IOrientation, IDesignable
|
||||
Height = 1;
|
||||
}
|
||||
|
||||
// Force a layout to ensure _scroll
|
||||
Layout ();
|
||||
_scroll.Orientation = newOrientation;
|
||||
}
|
||||
|
||||
@@ -183,43 +181,34 @@ public class ScrollBar : View, IOrientation, IDesignable
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the position of the slider within the ScrollBar's Viewport.</summary>
|
||||
/// <value>The position.</value>
|
||||
public int SliderPosition
|
||||
{
|
||||
get => _scroll.SliderPosition;
|
||||
set => _scroll.SliderPosition = value;
|
||||
}
|
||||
/// <returns>The position.</returns>
|
||||
public int GetSliderPosition () => _scroll.GetSliderPosition ();
|
||||
|
||||
private void OnScrollOnSliderPositionChanging (object? sender, CancelEventArgs<int> e) { SliderPositionChanging?.Invoke (this, e); }
|
||||
private void OnScrollOnSliderPositionChanged (object? sender, EventArgs<int> e) { SliderPositionChanged?.Invoke (this, e); }
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the <see cref="SliderPosition"/> is changing. Set <see cref="CancelEventArgs.Cancel"/> to
|
||||
/// <see langword="true"/> to prevent the position from being changed.
|
||||
/// </summary>
|
||||
public event EventHandler<CancelEventArgs<int>>? SliderPositionChanging;
|
||||
|
||||
/// <summary>Raised when the <see cref="SliderPosition"/> has changed.</summary>
|
||||
/// <summary>Raised when the position of the slider has changed.</summary>
|
||||
public event EventHandler<EventArgs<int>>? SliderPositionChanged;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the Scroll. This is the total size of the content that can be scrolled through.
|
||||
/// </summary>
|
||||
public int Size
|
||||
{
|
||||
get
|
||||
{
|
||||
// Add two for increment/decrement buttons
|
||||
return _scroll.Size + 2;
|
||||
}
|
||||
set
|
||||
{
|
||||
// Remove two for increment/decrement buttons
|
||||
_scroll.Size = value - 2;
|
||||
}
|
||||
get => _scroll.Size;
|
||||
set => _scroll.Size = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the viewport into the content being scrolled, bounded by <see cref="Size"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If not explicitly set, will be the appropriate dimension of the Scroll's Frame.
|
||||
/// </remarks>
|
||||
public int ViewportDimension
|
||||
{
|
||||
get => _scroll.ViewportDimension;
|
||||
set => _scroll.ViewportDimension = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the position of the ScrollSlider within the range of 0...<see cref="Size"/>.
|
||||
/// </summary>
|
||||
@@ -233,12 +222,12 @@ public class ScrollBar : View, IOrientation, IDesignable
|
||||
private void OnScrollOnContentPositionChanged (object? sender, EventArgs<int> e) { ContentPositionChanged?.Invoke (this, e); }
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the <see cref="SliderPosition"/> is changing. Set <see cref="CancelEventArgs.Cancel"/> to
|
||||
/// Raised when the <see cref="ContentPosition"/> is changing. Set <see cref="CancelEventArgs.Cancel"/> to
|
||||
/// <see langword="true"/> to prevent the position from being changed.
|
||||
/// </summary>
|
||||
public event EventHandler<CancelEventArgs<int>>? ContentPositionChanging;
|
||||
|
||||
/// <summary>Raised when the <see cref="SliderPosition"/> has changed.</summary>
|
||||
/// <summary>Raised when the <see cref="ContentPosition"/> has changed.</summary>
|
||||
public event EventHandler<EventArgs<int>>? ContentPositionChanged;
|
||||
|
||||
/// <summary>Raised when <see cref="Size"/> has changed.</summary>
|
||||
@@ -320,7 +309,7 @@ public class ScrollBar : View, IOrientation, IDesignable
|
||||
Width = 1;
|
||||
Height = Dim.Fill ();
|
||||
Size = 200;
|
||||
SliderPosition = 10;
|
||||
ContentPosition = 10;
|
||||
//ShowPercent = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Terminal.Gui;
|
||||
@@ -39,6 +40,8 @@ public class ScrollSlider : View, IOrientation, IDesignable
|
||||
|
||||
// Default size is 1
|
||||
Size = 1;
|
||||
|
||||
FrameChanged += OnFrameChanged;
|
||||
}
|
||||
|
||||
#region IOrientation members
|
||||
@@ -67,6 +70,7 @@ public class ScrollSlider : View, IOrientation, IDesignable
|
||||
// Reset Position to 0 when changing orientation
|
||||
X = 0;
|
||||
Y = 0;
|
||||
//Position = 0;
|
||||
|
||||
// Reset Size to 1 when changing orientation
|
||||
if (Orientation == Orientation.Vertical)
|
||||
@@ -103,7 +107,7 @@ public class ScrollSlider : View, IOrientation, IDesignable
|
||||
set
|
||||
{
|
||||
_showPercent = value;
|
||||
SetNeedsDraw();
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,11 +127,11 @@ public class ScrollSlider : View, IOrientation, IDesignable
|
||||
{
|
||||
if (Orientation == Orientation.Vertical)
|
||||
{
|
||||
return Frame.Height;
|
||||
return Viewport.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Frame.Width;
|
||||
return Viewport.Width;
|
||||
}
|
||||
}
|
||||
set
|
||||
@@ -148,38 +152,123 @@ public class ScrollSlider : View, IOrientation, IDesignable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// <see cref="Orientation"/>. The position will be constrained such that the ScrollSlider will not go outside the Viewport of
|
||||
/// Gets the size of the viewport into the content being scrolled, bounded by <see cref="Size"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the SuperView's Viewport demension.
|
||||
/// </remarks>
|
||||
public int ViewportDimension => Orientation == Orientation.Vertical ? SuperView?.Viewport.Height ?? 0 : SuperView?.Viewport.Width ?? 0;
|
||||
|
||||
private void OnFrameChanged (object? sender, EventArgs<Rectangle> e)
|
||||
{
|
||||
Position = Orientation == Orientation.Vertical ? e.CurrentValue.Y : e.CurrentValue.X;
|
||||
}
|
||||
|
||||
private int _position;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position of the ScrollSlider relative to the size of the ScrollSlider's Frame.
|
||||
/// The position will be constrained such that the ScrollSlider will not go outside the Viewport of
|
||||
/// the <see cref="View.SuperView"/>.
|
||||
/// </summary>
|
||||
public int Position
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Orientation == Orientation.Vertical)
|
||||
{
|
||||
return Frame.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Frame.X;
|
||||
}
|
||||
}
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
if (Orientation == Orientation.Vertical)
|
||||
if (_position == value)
|
||||
{
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
RaisePositionChangeEvents (ClampPosition (value));
|
||||
|
||||
SetNeedsLayout ();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPosition (int position)
|
||||
{
|
||||
_position = ClampPosition (position);
|
||||
|
||||
if (Orientation == Orientation.Vertical)
|
||||
{
|
||||
Y = _position;
|
||||
}
|
||||
else
|
||||
{
|
||||
X = _position;
|
||||
}
|
||||
}
|
||||
|
||||
private int ClampPosition (int newPosittion)
|
||||
{
|
||||
if (SuperView is null || !IsInitialized)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (Orientation == Orientation.Vertical)
|
||||
{
|
||||
return Math.Clamp (newPosittion, 0, ViewportDimension - Viewport.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Math.Clamp (newPosittion, 0, ViewportDimension - Viewport.Width);
|
||||
}
|
||||
}
|
||||
|
||||
private void RaisePositionChangeEvents (int newPosition)
|
||||
{
|
||||
if (OnPositionChanging (_position, newPosition))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CancelEventArgs<int> args = new (ref _position, ref newPosition);
|
||||
PositionChanging?.Invoke (this, args);
|
||||
|
||||
if (args.Cancel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int scrollAmount = newPosition -_position;
|
||||
_position = newPosition;
|
||||
|
||||
OnPositionChanged (_position);
|
||||
PositionChanged?.Invoke (this, new (in _position));
|
||||
|
||||
OnScroll (scrollAmount);
|
||||
Scroll?.Invoke (this, new (in scrollAmount));
|
||||
|
||||
RaiseSelecting (new CommandContext (Command.Select, null, null, scrollAmount));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when <see cref="Position"/> is changing. Return true to cancel the change.
|
||||
/// </summary>
|
||||
protected virtual bool OnPositionChanging (int currentPos, int newPos) { return false; }
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the <see cref="Position"/> is changing. Set <see cref="CancelEventArgs.Cancel"/> to
|
||||
/// <see langword="true"/> to prevent the position from being changed.
|
||||
/// </summary>
|
||||
public event EventHandler<CancelEventArgs<int>>? PositionChanging;
|
||||
|
||||
/// <summary>Called when <see cref="Position"/> has changed.</summary>
|
||||
protected virtual void OnPositionChanged (int position) { }
|
||||
|
||||
/// <summary>Raised when the <see cref="Position"/> has changed.</summary>
|
||||
public event EventHandler<EventArgs<int>>? PositionChanged;
|
||||
|
||||
|
||||
/// <summary>Called when <see cref="Position"/> has changed. Indicates how much to scroll.</summary>
|
||||
protected virtual void OnScroll (int scrollAmount) { }
|
||||
|
||||
/// <summary>Raised when the <see cref="Position"/> has changed. Indicates how much to scroll.</summary>
|
||||
public event EventHandler<EventArgs<int>>? Scroll;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool OnDrawingText ()
|
||||
{
|
||||
@@ -240,7 +329,7 @@ public class ScrollSlider : View, IOrientation, IDesignable
|
||||
if (Orientation == Orientation.Vertical)
|
||||
{
|
||||
Y = Frame.Y + offset < 0
|
||||
? 0
|
||||
? 0
|
||||
: Frame.Y + offset + Frame.Height > superViewDimension
|
||||
? Math.Max (superViewDimension - Frame.Height, 0)
|
||||
: Frame.Y + offset;
|
||||
@@ -248,7 +337,7 @@ public class ScrollSlider : View, IOrientation, IDesignable
|
||||
else
|
||||
{
|
||||
X = Frame.X + offset < 0
|
||||
? 0
|
||||
? 0
|
||||
: Frame.X + offset + Frame.Width > superViewDimension
|
||||
? Math.Max (superViewDimension - Frame.Width, 0)
|
||||
: Frame.X + offset;
|
||||
|
||||
@@ -1,353 +1,38 @@
|
||||
#define OTHER_CONTROLS
|
||||
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Unicode;
|
||||
using System.Threading.Tasks;
|
||||
using Terminal.Gui;
|
||||
using static Terminal.Gui.SpinnerStyle;
|
||||
|
||||
namespace UICatalog.Scenarios;
|
||||
|
||||
/// <summary>
|
||||
/// This Scenario demonstrates building a custom control (a class deriving from View) that: - Provides a
|
||||
/// "Character Map" application (like Windows' charmap.exe). - Helps test unicode character rendering in Terminal.Gui -
|
||||
/// Illustrates how to do infinite scrolling
|
||||
/// A scrollable map of the Unicode codepoints.
|
||||
/// </summary>
|
||||
[ScenarioMetadata ("Character Map", "Unicode viewer demonstrating infinite content, scrolling, and Unicode.")]
|
||||
[ScenarioCategory ("Text and Formatting")]
|
||||
[ScenarioCategory ("Drawing")]
|
||||
[ScenarioCategory ("Controls")]
|
||||
[ScenarioCategory ("Layout")]
|
||||
[ScenarioCategory ("Scrolling")]
|
||||
public class CharacterMap : Scenario
|
||||
/// <remarks>
|
||||
/// See <see href="CharacterMap/README.md"/> for details.
|
||||
/// </remarks>
|
||||
public class CharMap : View, IDesignable
|
||||
{
|
||||
public Label _errorLabel;
|
||||
private TableView _categoryList;
|
||||
private CharMap _charMap;
|
||||
|
||||
// Don't create a Window, just return the top-level view
|
||||
public override void Main ()
|
||||
{
|
||||
Application.Init ();
|
||||
|
||||
var top = new Window
|
||||
{
|
||||
BorderStyle = LineStyle.None
|
||||
};
|
||||
|
||||
_charMap = new ()
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = Dim.Fill (Dim.Func (() => _categoryList.Frame.Width)),
|
||||
Height = Dim.Fill ()
|
||||
};
|
||||
top.Add (_charMap);
|
||||
|
||||
#if OTHER_CONTROLS
|
||||
_charMap.Y = 1;
|
||||
|
||||
var jumpLabel = new Label
|
||||
{
|
||||
X = Pos.Right (_charMap) + 1,
|
||||
Y = Pos.Y (_charMap),
|
||||
HotKeySpecifier = (Rune)'_',
|
||||
Text = "_Jump To Code Point:"
|
||||
};
|
||||
top.Add (jumpLabel);
|
||||
|
||||
var jumpEdit = new TextField
|
||||
{
|
||||
X = Pos.Right (jumpLabel) + 1, Y = Pos.Y (_charMap), Width = 10, Caption = "e.g. 01BE3"
|
||||
};
|
||||
top.Add (jumpEdit);
|
||||
|
||||
_errorLabel = new ()
|
||||
{
|
||||
X = Pos.Right (jumpEdit) + 1, Y = Pos.Y (_charMap), ColorScheme = Colors.ColorSchemes ["error"], Text = "err"
|
||||
};
|
||||
top.Add (_errorLabel);
|
||||
|
||||
jumpEdit.Accepting += JumpEditOnAccept;
|
||||
|
||||
_categoryList = new () { X = Pos.Right (_charMap), Y = Pos.Bottom (jumpLabel), Height = Dim.Fill () };
|
||||
_categoryList.FullRowSelect = true;
|
||||
_categoryList.MultiSelect = false;
|
||||
|
||||
//jumpList.Style.ShowHeaders = false;
|
||||
//jumpList.Style.ShowHorizontalHeaderOverline = false;
|
||||
//jumpList.Style.ShowHorizontalHeaderUnderline = false;
|
||||
_categoryList.Style.ShowHorizontalBottomline = true;
|
||||
|
||||
//jumpList.Style.ShowVerticalCellLines = false;
|
||||
//jumpList.Style.ShowVerticalHeaderLines = false;
|
||||
_categoryList.Style.AlwaysShowHeaders = true;
|
||||
|
||||
var isDescending = false;
|
||||
|
||||
_categoryList.Table = CreateCategoryTable (0, isDescending);
|
||||
|
||||
// if user clicks the mouse in TableView
|
||||
_categoryList.MouseClick += (s, e) =>
|
||||
{
|
||||
_categoryList.ScreenToCell (e.Position, out int? clickedCol);
|
||||
|
||||
if (clickedCol != null && e.Flags.HasFlag (MouseFlags.Button1Clicked))
|
||||
{
|
||||
EnumerableTableSource<UnicodeRange> table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
|
||||
string prevSelection = table.Data.ElementAt (_categoryList.SelectedRow).Category;
|
||||
isDescending = !isDescending;
|
||||
|
||||
_categoryList.Table = CreateCategoryTable (clickedCol.Value, isDescending);
|
||||
|
||||
table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
|
||||
|
||||
_categoryList.SelectedRow = table.Data
|
||||
.Select ((item, index) => new { item, index })
|
||||
.FirstOrDefault (x => x.item.Category == prevSelection)
|
||||
?.index
|
||||
?? -1;
|
||||
}
|
||||
};
|
||||
|
||||
int longestName = UnicodeRange.Ranges.Max (r => r.Category.GetColumns ());
|
||||
|
||||
_categoryList.Style.ColumnStyles.Add (
|
||||
0,
|
||||
new () { MaxWidth = longestName, MinWidth = longestName, MinAcceptableWidth = longestName }
|
||||
);
|
||||
_categoryList.Style.ColumnStyles.Add (1, new () { MaxWidth = 1, MinWidth = 6 });
|
||||
_categoryList.Style.ColumnStyles.Add (2, new () { MaxWidth = 1, MinWidth = 6 });
|
||||
|
||||
_categoryList.Width = _categoryList.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 4;
|
||||
|
||||
_categoryList.SelectedCellChanged += (s, args) =>
|
||||
{
|
||||
EnumerableTableSource<UnicodeRange> table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
|
||||
_charMap.StartCodePoint = table.Data.ToArray () [args.NewRow].Start;
|
||||
};
|
||||
|
||||
top.Add (_categoryList);
|
||||
|
||||
var menu = new MenuBar
|
||||
{
|
||||
Menus =
|
||||
[
|
||||
new (
|
||||
"_File",
|
||||
new MenuItem []
|
||||
{
|
||||
new (
|
||||
"_Quit",
|
||||
$"{Application.QuitKey}",
|
||||
() => Application.RequestStop ()
|
||||
)
|
||||
}
|
||||
),
|
||||
new (
|
||||
"_Options",
|
||||
new [] { CreateMenuShowWidth () }
|
||||
)
|
||||
]
|
||||
};
|
||||
top.Add (menu);
|
||||
#endif // OTHER_CONTROLS
|
||||
|
||||
_charMap.SelectedCodePoint = 0;
|
||||
_charMap.SetFocus ();
|
||||
|
||||
Application.Run (top);
|
||||
top.Dispose ();
|
||||
Application.Shutdown ();
|
||||
|
||||
return;
|
||||
|
||||
void JumpEditOnAccept (object sender, CommandEventArgs e)
|
||||
{
|
||||
if (jumpEdit.Text.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint result = 0;
|
||||
|
||||
if (jumpEdit.Text.StartsWith ("U+", StringComparison.OrdinalIgnoreCase) || jumpEdit.Text.StartsWith ("\\u"))
|
||||
{
|
||||
try
|
||||
{
|
||||
result = uint.Parse (jumpEdit.Text [2..], NumberStyles.HexNumber);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
_errorLabel.Text = "Invalid hex value";
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (jumpEdit.Text.StartsWith ("0", StringComparison.OrdinalIgnoreCase) || jumpEdit.Text.StartsWith ("\\u"))
|
||||
{
|
||||
try
|
||||
{
|
||||
result = uint.Parse (jumpEdit.Text, NumberStyles.HexNumber);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
_errorLabel.Text = "Invalid hex value";
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
result = uint.Parse (jumpEdit.Text, NumberStyles.Integer);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
_errorLabel.Text = "Invalid value";
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (result > RuneExtensions.MaxUnicodeCodePoint)
|
||||
{
|
||||
_errorLabel.Text = "Beyond maximum codepoint";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_errorLabel.Text = $"U+{result:x5}";
|
||||
|
||||
EnumerableTableSource<UnicodeRange> table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
|
||||
|
||||
_categoryList.SelectedRow = table.Data
|
||||
.Select ((item, index) => new { item, index })
|
||||
.FirstOrDefault (x => x.item.Start <= result && x.item.End >= result)
|
||||
?.index
|
||||
?? -1;
|
||||
_categoryList.EnsureSelectedCellIsVisible ();
|
||||
|
||||
// Ensure the typed glyph is selected
|
||||
_charMap.SelectedCodePoint = (int)result;
|
||||
|
||||
// Cancel the event to prevent ENTER from being handled elsewhere
|
||||
e.Cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
private EnumerableTableSource<UnicodeRange> CreateCategoryTable (int sortByColumn, bool descending)
|
||||
{
|
||||
Func<UnicodeRange, object> orderBy;
|
||||
var categorySort = string.Empty;
|
||||
var startSort = string.Empty;
|
||||
var endSort = string.Empty;
|
||||
|
||||
string sortIndicator = descending ? CM.Glyphs.DownArrow.ToString () : CM.Glyphs.UpArrow.ToString ();
|
||||
|
||||
switch (sortByColumn)
|
||||
{
|
||||
case 0:
|
||||
orderBy = r => r.Category;
|
||||
categorySort = sortIndicator;
|
||||
|
||||
break;
|
||||
case 1:
|
||||
orderBy = r => r.Start;
|
||||
startSort = sortIndicator;
|
||||
|
||||
break;
|
||||
case 2:
|
||||
orderBy = r => r.End;
|
||||
endSort = sortIndicator;
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException ("Invalid column number.");
|
||||
}
|
||||
|
||||
IOrderedEnumerable<UnicodeRange> sortedRanges = descending
|
||||
? UnicodeRange.Ranges.OrderByDescending (orderBy)
|
||||
: UnicodeRange.Ranges.OrderBy (orderBy);
|
||||
|
||||
return new (
|
||||
sortedRanges,
|
||||
new ()
|
||||
{
|
||||
{ $"Category{categorySort}", s => s.Category },
|
||||
{ $"Start{startSort}", s => $"{s.Start:x5}" },
|
||||
{ $"End{endSort}", s => $"{s.End:x5}" }
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private MenuItem CreateMenuShowWidth ()
|
||||
{
|
||||
var item = new MenuItem { Title = "_Show Glyph Width" };
|
||||
item.CheckType |= MenuItemCheckStyle.Checked;
|
||||
item.Checked = _charMap?.ShowGlyphWidths;
|
||||
item.Action += () => { _charMap.ShowGlyphWidths = (bool)(item.Checked = !item.Checked); };
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public override List<Key> GetDemoKeyStrokes ()
|
||||
{
|
||||
List<Key> keys = new ();
|
||||
|
||||
for (var i = 0; i < 200; i++)
|
||||
{
|
||||
keys.Add (Key.CursorDown);
|
||||
}
|
||||
|
||||
// Category table
|
||||
keys.Add (Key.Tab.WithShift);
|
||||
|
||||
// Block elements
|
||||
keys.Add (Key.B);
|
||||
keys.Add (Key.L);
|
||||
|
||||
keys.Add (Key.Tab);
|
||||
|
||||
for (var i = 0; i < 200; i++)
|
||||
{
|
||||
keys.Add (Key.CursorLeft);
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
internal class CharMap : View, IDesignable
|
||||
{
|
||||
private const int COLUMN_WIDTH = 3;
|
||||
private const int COLUMN_WIDTH = 3; // Width of each column of glyphs
|
||||
private int _rowHeight = 1; // Height of each row of 16 glyphs - changing this is not tested
|
||||
|
||||
private ContextMenu _contextMenu = new ();
|
||||
private int _rowHeight = 1;
|
||||
private int _selected;
|
||||
private int _start;
|
||||
|
||||
private readonly ScrollBar _vScrollBar;
|
||||
private readonly ScrollBar _hScrollBar;
|
||||
|
||||
/// <summary>
|
||||
/// Initalizes a new instance.
|
||||
/// </summary>
|
||||
public CharMap ()
|
||||
{
|
||||
ColorScheme = Colors.ColorSchemes ["Dialog"];
|
||||
base.ColorScheme = Colors.ColorSchemes ["Dialog"];
|
||||
CanFocus = true;
|
||||
CursorVisibility = CursorVisibility.Default;
|
||||
|
||||
//ViewportSettings = ViewportSettings.AllowLocationGreaterThanContentSize;
|
||||
|
||||
SetContentSize (new (COLUMN_WIDTH * 16 + RowLabelWidth, _maxCodePoint / 16 * _rowHeight + 1)); // +1 for Header
|
||||
|
||||
AddCommand (
|
||||
Command.Up,
|
||||
() =>
|
||||
@@ -424,7 +109,7 @@ internal class CharMap : View, IDesignable
|
||||
Command.End,
|
||||
() =>
|
||||
{
|
||||
SelectedCodePoint = _maxCodePoint;
|
||||
SelectedCodePoint = MAX_CODE_POINT;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -453,7 +138,9 @@ internal class CharMap : View, IDesignable
|
||||
MouseEvent += Handle_MouseEvent;
|
||||
|
||||
// Add scrollbars
|
||||
Padding.Thickness = new (0, 0, 1, 0);
|
||||
Padding!.Thickness = new (0, 0, 1, 0);
|
||||
|
||||
SetContentSize (new (COLUMN_WIDTH * 16 + RowLabelWidth, MAX_CODE_POINT / 16 * _rowHeight + 1)); // +1 for Header
|
||||
|
||||
_hScrollBar = new ()
|
||||
{
|
||||
@@ -463,7 +150,7 @@ internal class CharMap : View, IDesignable
|
||||
Orientation = Orientation.Horizontal,
|
||||
Width = Dim.Fill (1),
|
||||
Size = GetContentSize ().Width - RowLabelWidth,
|
||||
Increment = COLUMN_WIDTH
|
||||
Increment = COLUMN_WIDTH,
|
||||
};
|
||||
|
||||
_vScrollBar = new ()
|
||||
@@ -510,48 +197,45 @@ internal class CharMap : View, IDesignable
|
||||
Padding.Thickness = Padding.Thickness with { Bottom = 0 };
|
||||
}
|
||||
|
||||
_hScrollBar.ContentPosition = Viewport.X;
|
||||
_vScrollBar.ContentPosition = Viewport.Y;
|
||||
//_hScrollBar.ContentPosition = Viewport.X;
|
||||
//_vScrollBar.ContentPosition = Viewport.Y;
|
||||
};
|
||||
|
||||
SubviewsLaidOut += (sender, args) =>
|
||||
{
|
||||
//_vScrollBar.ContentPosition = Viewport.Y;
|
||||
//_hScrollBar.ContentPosition = Viewport.X;
|
||||
};
|
||||
}
|
||||
|
||||
private void Handle_MouseEvent (object sender, MouseEventArgs e)
|
||||
private void ScrollToMakeCursorVisible (Point newCursor)
|
||||
{
|
||||
if (e.Flags == MouseFlags.WheeledDown)
|
||||
// Adjust vertical scrolling
|
||||
if (newCursor.Y < 1) // Header is at Y = 0
|
||||
{
|
||||
ScrollVertical (1);
|
||||
_vScrollBar.ContentPosition = Viewport.Y;
|
||||
e.Handled = true;
|
||||
|
||||
return;
|
||||
ScrollVertical (newCursor.Y - 1);
|
||||
}
|
||||
else if (newCursor.Y >= Viewport.Height)
|
||||
{
|
||||
ScrollVertical (newCursor.Y - Viewport.Height + 1);
|
||||
}
|
||||
|
||||
if (e.Flags == MouseFlags.WheeledUp)
|
||||
// Adjust horizontal scrolling
|
||||
if (newCursor.X < RowLabelWidth + 1)
|
||||
{
|
||||
ScrollVertical (-1);
|
||||
_vScrollBar.ContentPosition = Viewport.Y;
|
||||
e.Handled = true;
|
||||
|
||||
return;
|
||||
ScrollHorizontal (newCursor.X - (RowLabelWidth + 1));
|
||||
}
|
||||
else if (newCursor.X >= Viewport.Width)
|
||||
{
|
||||
ScrollHorizontal (newCursor.X - Viewport.Width + 1);
|
||||
}
|
||||
|
||||
if (e.Flags == MouseFlags.WheeledRight)
|
||||
{
|
||||
ScrollHorizontal (1);
|
||||
_hScrollBar.ContentPosition = Viewport.X;
|
||||
e.Handled = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Flags == MouseFlags.WheeledLeft)
|
||||
{
|
||||
ScrollHorizontal (-1);
|
||||
_hScrollBar.ContentPosition = Viewport.X;
|
||||
e.Handled = true;
|
||||
}
|
||||
_vScrollBar.ContentPosition = Viewport.Y;
|
||||
_hScrollBar.ContentPosition = Viewport.X;
|
||||
}
|
||||
|
||||
#region Cursor
|
||||
|
||||
/// <summary>Gets or sets the coordinates of the Cursor based on the SelectedCodePoint in Viewport-relative coordinates</summary>
|
||||
public Point Cursor
|
||||
{
|
||||
@@ -565,7 +249,30 @@ internal class CharMap : View, IDesignable
|
||||
set => throw new NotImplementedException ();
|
||||
}
|
||||
|
||||
public static int _maxCodePoint = UnicodeRange.Ranges.Max (r => r.End);
|
||||
public override Point? PositionCursor ()
|
||||
{
|
||||
if (HasFocus
|
||||
&& Cursor.X >= RowLabelWidth
|
||||
&& Cursor.X < Viewport.Width
|
||||
&& Cursor.Y > 0
|
||||
&& Cursor.Y < Viewport.Height)
|
||||
{
|
||||
Move (Cursor.X, Cursor.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Cursor;
|
||||
}
|
||||
|
||||
#endregion Cursor
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static readonly int MAX_CODE_POINT = UnicodeRange.Ranges.Max (r => r.End);
|
||||
private int _selectedCodepoint; // Currently selected codepoint
|
||||
private int _startCodepoint; // The codepoint that will be displayed at the top of the Viewport
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently selected codepoint. Causes the Viewport to scroll to make the selected code point
|
||||
@@ -573,16 +280,15 @@ internal class CharMap : View, IDesignable
|
||||
/// </summary>
|
||||
public int SelectedCodePoint
|
||||
{
|
||||
get => _selected;
|
||||
get => _selectedCodepoint;
|
||||
set
|
||||
{
|
||||
if (_selected == value)
|
||||
if (_selectedCodepoint == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Point prevCursor = Cursor;
|
||||
int newSelectedCodePoint = Math.Clamp (value, 0, _maxCodePoint);
|
||||
int newSelectedCodePoint = Math.Clamp (value, 0, MAX_CODE_POINT);
|
||||
|
||||
Point newCursor = new ()
|
||||
{
|
||||
@@ -590,42 +296,38 @@ internal class CharMap : View, IDesignable
|
||||
Y = newSelectedCodePoint / 16 * _rowHeight + 1 - Viewport.Y
|
||||
};
|
||||
|
||||
_selectedCodepoint = newSelectedCodePoint;
|
||||
|
||||
// Ensure the new cursor position is visible
|
||||
EnsureCursorIsVisible (newCursor);
|
||||
ScrollToMakeCursorVisible (newCursor);
|
||||
|
||||
_selected = newSelectedCodePoint;
|
||||
SetNeedsDraw ();
|
||||
SelectedCodePointChanged?.Invoke (this, new (SelectedCodePoint, null));
|
||||
SelectedCodePointChanged?.Invoke (this, new (SelectedCodePoint));
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureCursorIsVisible (Point newCursor)
|
||||
/// <summary>
|
||||
/// Raised when the selected code point changes.
|
||||
/// </summary>
|
||||
public event EventHandler<EventArgs<int>>? SelectedCodePointChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the starting offset for the character map. The default is 0x2500 which is the Box Drawing
|
||||
/// characters.
|
||||
/// </summary>
|
||||
public int StartCodePoint
|
||||
{
|
||||
// Adjust vertical scrolling
|
||||
if (newCursor.Y < 1) // Header is at Y = 0
|
||||
get => _startCodepoint;
|
||||
set
|
||||
{
|
||||
ScrollVertical (newCursor.Y - 1);
|
||||
_startCodepoint = value;
|
||||
SelectedCodePoint = value;
|
||||
}
|
||||
else if (newCursor.Y >= Viewport.Height)
|
||||
{
|
||||
ScrollVertical (newCursor.Y - Viewport.Height + 1);
|
||||
}
|
||||
|
||||
_vScrollBar.ContentPosition = Viewport.Y;
|
||||
|
||||
// Adjust horizontal scrolling
|
||||
if (newCursor.X < RowLabelWidth + 1)
|
||||
{
|
||||
ScrollHorizontal (newCursor.X - (RowLabelWidth + 1));
|
||||
}
|
||||
else if (newCursor.X >= Viewport.Width)
|
||||
{
|
||||
ScrollHorizontal (newCursor.X - Viewport.Width + 1);
|
||||
}
|
||||
|
||||
_hScrollBar.ContentPosition = Viewport.X;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the number of columns each glyph is displayed.
|
||||
/// </summary>
|
||||
public bool ShowGlyphWidths
|
||||
{
|
||||
get => _rowHeight == 2;
|
||||
@@ -636,25 +338,12 @@ internal class CharMap : View, IDesignable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the starting offset for the character map. The default is 0x2500 which is the Box Drawing
|
||||
/// characters.
|
||||
/// </summary>
|
||||
public int StartCodePoint
|
||||
{
|
||||
get => _start;
|
||||
set
|
||||
{
|
||||
_start = value;
|
||||
SelectedCodePoint = value;
|
||||
Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight };
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
}
|
||||
private void CopyCodePoint () { Clipboard.Contents = $"U+{SelectedCodePoint:x5}"; }
|
||||
private void CopyGlyph () { Clipboard.Contents = $"{new Rune (SelectedCodePoint)}"; }
|
||||
|
||||
private static int RowLabelWidth => $"U+{_maxCodePoint:x5}".Length + 1;
|
||||
private static int RowWidth => RowLabelWidth + COLUMN_WIDTH * 16;
|
||||
public event EventHandler<ListViewItemEventArgs> Hover;
|
||||
#region Drawing
|
||||
|
||||
private static int RowLabelWidth => $"U+{MAX_CODE_POINT:x5}".Length + 1;
|
||||
|
||||
protected override bool OnDrawingContent ()
|
||||
{
|
||||
@@ -682,7 +371,7 @@ internal class CharMap : View, IDesignable
|
||||
Move (x, 0);
|
||||
SetAttribute (GetHotNormalColor ());
|
||||
AddStr (" ");
|
||||
SetAttribute (HasFocus && cursorCol + firstColumnX == x ? ColorScheme.HotFocus : GetHotNormalColor ());
|
||||
SetAttribute (HasFocus && cursorCol + firstColumnX == x ? GetHotFocusColor () : GetHotNormalColor ());
|
||||
AddStr ($"{hexDigit:x}");
|
||||
SetAttribute (GetHotNormalColor ());
|
||||
AddStr (" ");
|
||||
@@ -697,7 +386,7 @@ internal class CharMap : View, IDesignable
|
||||
|
||||
int val = row * 16;
|
||||
|
||||
if (val > _maxCodePoint)
|
||||
if (val > MAX_CODE_POINT)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -770,7 +459,7 @@ internal class CharMap : View, IDesignable
|
||||
else
|
||||
{
|
||||
// Draw the width of the rune
|
||||
SetAttribute (ColorScheme.HotNormal);
|
||||
SetAttribute (GetHotNormalColor ());
|
||||
AddStr ($"{width}");
|
||||
}
|
||||
|
||||
@@ -784,7 +473,7 @@ internal class CharMap : View, IDesignable
|
||||
// Draw row label (U+XXXX_)
|
||||
Move (0, y);
|
||||
|
||||
SetAttribute (HasFocus && y + Viewport.Y - 1 == cursorRow ? ColorScheme.HotFocus : ColorScheme.HotNormal);
|
||||
SetAttribute (HasFocus && y + Viewport.Y - 1 == cursorRow ? GetHotFocusColor () : GetHotNormalColor ());
|
||||
|
||||
if (!ShowGlyphWidths || (y + Viewport.Y) % _rowHeight > 0)
|
||||
{
|
||||
@@ -799,26 +488,11 @@ internal class CharMap : View, IDesignable
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Point? PositionCursor ()
|
||||
{
|
||||
if (HasFocus
|
||||
&& Cursor.X >= RowLabelWidth
|
||||
&& Cursor.X < Viewport.Width
|
||||
&& Cursor.Y > 0
|
||||
&& Cursor.Y < Viewport.Height)
|
||||
{
|
||||
Move (Cursor.X, Cursor.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Cursor;
|
||||
}
|
||||
|
||||
public event EventHandler<ListViewItemEventArgs> SelectedCodePointChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Helper to convert a string into camel case.
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToCamelCase (string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty (str))
|
||||
@@ -834,10 +508,51 @@ internal class CharMap : View, IDesignable
|
||||
return str;
|
||||
}
|
||||
|
||||
private void CopyCodePoint () { Clipboard.Contents = $"U+{SelectedCodePoint:x5}"; }
|
||||
private void CopyGlyph () { Clipboard.Contents = $"{new Rune (SelectedCodePoint)}"; }
|
||||
#endregion Drawing
|
||||
|
||||
private void Handle_MouseClick (object sender, MouseEventArgs me)
|
||||
#region Mouse Handling
|
||||
|
||||
// TODO: Use this to demonstrate using a popover to show glyph info on hover
|
||||
public event EventHandler<ListViewItemEventArgs>? Hover;
|
||||
|
||||
private void Handle_MouseEvent (object? sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.Flags == MouseFlags.WheeledDown)
|
||||
{
|
||||
ScrollVertical (1);
|
||||
_vScrollBar.ContentPosition = Viewport.Y;
|
||||
e.Handled = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Flags == MouseFlags.WheeledUp)
|
||||
{
|
||||
ScrollVertical (-1);
|
||||
_vScrollBar.ContentPosition = Viewport.Y;
|
||||
e.Handled = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Flags == MouseFlags.WheeledRight)
|
||||
{
|
||||
ScrollHorizontal (1);
|
||||
_hScrollBar.ContentPosition = Viewport.X;
|
||||
e.Handled = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Flags == MouseFlags.WheeledLeft)
|
||||
{
|
||||
ScrollHorizontal (-1);
|
||||
_hScrollBar.ContentPosition = Viewport.X;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Handle_MouseClick (object? sender, MouseEventArgs me)
|
||||
{
|
||||
if (me.Flags != MouseFlags.ReportMousePosition && me.Flags != MouseFlags.Button1Clicked && me.Flags != MouseFlags.Button1DoubleClicked)
|
||||
{
|
||||
@@ -864,7 +579,7 @@ internal class CharMap : View, IDesignable
|
||||
|
||||
int val = row * 16 + col;
|
||||
|
||||
if (val > _maxCodePoint)
|
||||
if (val > MAX_CODE_POINT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -931,32 +646,41 @@ internal class CharMap : View, IDesignable
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Mouse Handling
|
||||
|
||||
#region Details Dialog
|
||||
|
||||
private void ShowDetails ()
|
||||
{
|
||||
var client = new UcdApiClient ();
|
||||
UcdApiClient? client = new ();
|
||||
var decResponse = string.Empty;
|
||||
var getCodePointError = string.Empty;
|
||||
|
||||
var waitIndicator = new Dialog
|
||||
Dialog? waitIndicator = new ()
|
||||
{
|
||||
Title = "Getting Code Point Information",
|
||||
X = Pos.Center (),
|
||||
Y = Pos.Center (),
|
||||
Height = 7,
|
||||
Width = 50,
|
||||
Buttons = [new () { Text = "Cancel" }]
|
||||
Width = 40,
|
||||
Height = 10,
|
||||
Buttons = [new () { Text = "_Cancel" }]
|
||||
};
|
||||
|
||||
var errorLabel = new Label
|
||||
{
|
||||
Text = UcdApiClient.BaseUrl,
|
||||
X = 0,
|
||||
Y = 1,
|
||||
Y = 0,
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (1),
|
||||
Height = Dim.Fill (3),
|
||||
TextAlignment = Alignment.Center
|
||||
};
|
||||
var spinner = new SpinnerView { X = Pos.Center (), Y = Pos.Center (), Style = new Aesthetic () };
|
||||
var spinner = new SpinnerView
|
||||
{
|
||||
X = Pos.Center (),
|
||||
Y = Pos.Bottom (errorLabel),
|
||||
Style = new SpinnerStyle.Aesthetic ()
|
||||
};
|
||||
spinner.AutoSpin = true;
|
||||
waitIndicator.Add (errorLabel);
|
||||
waitIndicator.Add (spinner);
|
||||
@@ -1000,30 +724,30 @@ internal class CharMap : View, IDesignable
|
||||
document.RootElement,
|
||||
new
|
||||
JsonSerializerOptions
|
||||
{ WriteIndented = true }
|
||||
{ WriteIndented = true }
|
||||
);
|
||||
}
|
||||
|
||||
var title = $"{ToCamelCase (name)} - {new Rune (SelectedCodePoint)} U+{SelectedCodePoint:x5}";
|
||||
var title = $"{ToCamelCase (name!)} - {new Rune (SelectedCodePoint)} U+{SelectedCodePoint:x5}";
|
||||
|
||||
var copyGlyph = new Button { Text = "Copy _Glyph" };
|
||||
var copyCP = new Button { Text = "Copy Code _Point" };
|
||||
var cancel = new Button { Text = "Cancel" };
|
||||
Button? copyGlyph = new () { Text = "Copy _Glyph" };
|
||||
Button? copyCodepoint = new () { Text = "Copy Code _Point" };
|
||||
Button? cancel = new () { Text = "Cancel" };
|
||||
|
||||
var dlg = new Dialog { Title = title, Buttons = [copyGlyph, copyCP, cancel] };
|
||||
var dlg = new Dialog { Title = title, Buttons = [copyGlyph, copyCodepoint, cancel] };
|
||||
|
||||
copyGlyph.Accepting += (s, a) =>
|
||||
{
|
||||
CopyGlyph ();
|
||||
dlg.RequestStop ();
|
||||
dlg!.RequestStop ();
|
||||
};
|
||||
|
||||
copyCP.Accepting += (s, a) =>
|
||||
{
|
||||
CopyCodePoint ();
|
||||
dlg.RequestStop ();
|
||||
};
|
||||
cancel.Accepting += (s, a) => dlg.RequestStop ();
|
||||
copyCodepoint.Accepting += (s, a) =>
|
||||
{
|
||||
CopyCodePoint ();
|
||||
dlg!.RequestStop ();
|
||||
};
|
||||
cancel.Accepting += (s, a) => dlg!.RequestStop ();
|
||||
|
||||
var rune = (Rune)SelectedCodePoint;
|
||||
var label = new Label { Text = "IsAscii: ", X = 0, Y = 0 };
|
||||
@@ -1097,129 +821,13 @@ internal class CharMap : View, IDesignable
|
||||
MessageBox.ErrorQuery (
|
||||
"Code Point API",
|
||||
$"{UcdApiClient.BaseUrl}codepoint/dec/{SelectedCodePoint} did not return a result for\r\n {new Rune (SelectedCodePoint)} U+{SelectedCodePoint:x5}.",
|
||||
"Ok"
|
||||
"_Ok"
|
||||
);
|
||||
}
|
||||
|
||||
// BUGBUG: This is a workaround for some weird ScrollView related mouse grab bug
|
||||
Application.GrabMouse (this);
|
||||
}
|
||||
}
|
||||
|
||||
public class UcdApiClient
|
||||
{
|
||||
public const string BaseUrl = "https://ucdapi.org/unicode/latest/";
|
||||
private static readonly HttpClient _httpClient = new ();
|
||||
|
||||
public async Task<string> GetChars (string chars)
|
||||
{
|
||||
HttpResponseMessage response = await _httpClient.GetAsync ($"{BaseUrl}chars/{Uri.EscapeDataString (chars)}");
|
||||
response.EnsureSuccessStatusCode ();
|
||||
|
||||
return await response.Content.ReadAsStringAsync ();
|
||||
}
|
||||
|
||||
public async Task<string> GetCharsName (string chars)
|
||||
{
|
||||
HttpResponseMessage response =
|
||||
await _httpClient.GetAsync ($"{BaseUrl}chars/{Uri.EscapeDataString (chars)}/name");
|
||||
response.EnsureSuccessStatusCode ();
|
||||
|
||||
return await response.Content.ReadAsStringAsync ();
|
||||
}
|
||||
|
||||
public async Task<string> GetCodepointDec (int dec)
|
||||
{
|
||||
HttpResponseMessage response = await _httpClient.GetAsync ($"{BaseUrl}codepoint/dec/{dec}");
|
||||
response.EnsureSuccessStatusCode ();
|
||||
|
||||
return await response.Content.ReadAsStringAsync ();
|
||||
}
|
||||
|
||||
public async Task<string> GetCodepointHex (string hex)
|
||||
{
|
||||
HttpResponseMessage response = await _httpClient.GetAsync ($"{BaseUrl}codepoint/hex/{hex}");
|
||||
response.EnsureSuccessStatusCode ();
|
||||
|
||||
return await response.Content.ReadAsStringAsync ();
|
||||
}
|
||||
}
|
||||
|
||||
internal class UnicodeRange
|
||||
{
|
||||
public static List<UnicodeRange> Ranges = GetRanges ();
|
||||
|
||||
public string Category;
|
||||
public int End;
|
||||
public int Start;
|
||||
|
||||
public UnicodeRange (int start, int end, string category)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
Category = category;
|
||||
}
|
||||
|
||||
public static List<UnicodeRange> GetRanges ()
|
||||
{
|
||||
IEnumerable<UnicodeRange> ranges =
|
||||
from r in typeof (UnicodeRanges).GetProperties (BindingFlags.Static | BindingFlags.Public)
|
||||
let urange = r.GetValue (null) as System.Text.Unicode.UnicodeRange
|
||||
let name = string.IsNullOrEmpty (r.Name)
|
||||
? $"U+{urange.FirstCodePoint:x5}-U+{urange.FirstCodePoint + urange.Length:x5}"
|
||||
: r.Name
|
||||
where name != "None" && name != "All"
|
||||
select new UnicodeRange (urange.FirstCodePoint, urange.FirstCodePoint + urange.Length, name);
|
||||
|
||||
// .NET 8.0 only supports BMP in UnicodeRanges: https://learn.microsoft.com/en-us/dotnet/api/system.text.unicode.unicoderanges?view=net-8.0
|
||||
List<UnicodeRange> nonBmpRanges = new ()
|
||||
{
|
||||
new (
|
||||
0x1F130,
|
||||
0x1F149,
|
||||
"Squared Latin Capital Letters"
|
||||
),
|
||||
new (
|
||||
0x12400,
|
||||
0x1240f,
|
||||
"Cuneiform Numbers and Punctuation"
|
||||
),
|
||||
new (0x10000, 0x1007F, "Linear B Syllabary"),
|
||||
new (0x10080, 0x100FF, "Linear B Ideograms"),
|
||||
new (0x10100, 0x1013F, "Aegean Numbers"),
|
||||
new (0x10300, 0x1032F, "Old Italic"),
|
||||
new (0x10330, 0x1034F, "Gothic"),
|
||||
new (0x10380, 0x1039F, "Ugaritic"),
|
||||
new (0x10400, 0x1044F, "Deseret"),
|
||||
new (0x10450, 0x1047F, "Shavian"),
|
||||
new (0x10480, 0x104AF, "Osmanya"),
|
||||
new (0x10800, 0x1083F, "Cypriot Syllabary"),
|
||||
new (
|
||||
0x1D000,
|
||||
0x1D0FF,
|
||||
"Byzantine Musical Symbols"
|
||||
),
|
||||
new (0x1D100, 0x1D1FF, "Musical Symbols"),
|
||||
new (0x1D300, 0x1D35F, "Tai Xuan Jing Symbols"),
|
||||
new (
|
||||
0x1D400,
|
||||
0x1D7FF,
|
||||
"Mathematical Alphanumeric Symbols"
|
||||
),
|
||||
new (0x1F600, 0x1F532, "Emojis Symbols"),
|
||||
new (
|
||||
0x20000,
|
||||
0x2A6DF,
|
||||
"CJK Unified Ideographs Extension B"
|
||||
),
|
||||
new (
|
||||
0x2F800,
|
||||
0x2FA1F,
|
||||
"CJK Compatibility Ideographs Supplement"
|
||||
),
|
||||
new (0xE0000, 0xE007F, "Tags")
|
||||
};
|
||||
|
||||
return ranges.Concat (nonBmpRanges).OrderBy (r => r.Category).ToList ();
|
||||
}
|
||||
|
||||
#endregion Details Dialog
|
||||
}
|
||||
342
UICatalog/Scenarios/CharacterMap/CharacterMap.cs
Normal file
342
UICatalog/Scenarios/CharacterMap/CharacterMap.cs
Normal file
@@ -0,0 +1,342 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Terminal.Gui;
|
||||
|
||||
namespace UICatalog.Scenarios;
|
||||
|
||||
/// <summary>
|
||||
/// This Scenario demonstrates building a custom control (a class deriving from View) that: - Provides a
|
||||
/// "Character Map" application (like Windows' charmap.exe). - Helps test unicode character rendering in Terminal.Gui -
|
||||
/// Illustrates how to do infinite scrolling
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="CharacterMap/README.md"/>.
|
||||
/// </remarks>
|
||||
[ScenarioMetadata ("Character Map", "Unicode viewer. Demos infinite content drawing and scrolling.")]
|
||||
[ScenarioCategory ("Text and Formatting")]
|
||||
[ScenarioCategory ("Drawing")]
|
||||
[ScenarioCategory ("Controls")]
|
||||
[ScenarioCategory ("Layout")]
|
||||
[ScenarioCategory ("Scrolling")]
|
||||
public class CharacterMap : Scenario
|
||||
{
|
||||
private Label? _errorLabel;
|
||||
private TableView? _categoryList;
|
||||
private CharMap? _charMap;
|
||||
|
||||
// Don't create a Window, just return the top-level view
|
||||
public override void Main ()
|
||||
{
|
||||
Application.Init ();
|
||||
|
||||
var top = new Window
|
||||
{
|
||||
BorderStyle = LineStyle.None
|
||||
};
|
||||
|
||||
_charMap = new ()
|
||||
{
|
||||
X = 0,
|
||||
Y = 1,
|
||||
Width = Dim.Fill (Dim.Func (() => _categoryList!.Frame.Width)),
|
||||
Height = Dim.Fill ()
|
||||
};
|
||||
top.Add (_charMap);
|
||||
|
||||
var jumpLabel = new Label
|
||||
{
|
||||
X = Pos.Right (_charMap) + 1,
|
||||
Y = Pos.Y (_charMap),
|
||||
HotKeySpecifier = (Rune)'_',
|
||||
Text = "_Jump To:"
|
||||
};
|
||||
top.Add (jumpLabel);
|
||||
|
||||
var jumpEdit = new TextField
|
||||
{
|
||||
X = Pos.Right (jumpLabel) + 1,
|
||||
Y = Pos.Y (_charMap),
|
||||
Width = 17,
|
||||
Caption = "e.g. 01BE3 or ✈",
|
||||
};
|
||||
top.Add (jumpEdit);
|
||||
|
||||
_charMap.SelectedCodePointChanged += (sender, args) => jumpEdit.Text = ((Rune)args.CurrentValue).ToString ();
|
||||
|
||||
_errorLabel = new ()
|
||||
{
|
||||
X = Pos.Right (jumpEdit) + 1,
|
||||
Y = Pos.Y (_charMap),
|
||||
ColorScheme = Colors.ColorSchemes ["error"],
|
||||
Text = "err",
|
||||
Visible = false
|
||||
|
||||
};
|
||||
top.Add (_errorLabel);
|
||||
|
||||
jumpEdit.Accepting += JumpEditOnAccept;
|
||||
|
||||
_categoryList = new () {
|
||||
X = Pos.Right (_charMap),
|
||||
Y = Pos.Bottom (jumpLabel),
|
||||
Height = Dim.Fill (),
|
||||
};
|
||||
_categoryList.FullRowSelect = true;
|
||||
_categoryList.MultiSelect = false;
|
||||
|
||||
_categoryList.Style.ShowVerticalCellLines = false;
|
||||
_categoryList.Style.AlwaysShowHeaders = true;
|
||||
|
||||
var isDescending = false;
|
||||
|
||||
_categoryList.Table = CreateCategoryTable (0, isDescending);
|
||||
|
||||
// if user clicks the mouse in TableView
|
||||
_categoryList.MouseClick += (s, e) =>
|
||||
{
|
||||
_categoryList.ScreenToCell (e.Position, out int? clickedCol);
|
||||
|
||||
if (clickedCol != null && e.Flags.HasFlag (MouseFlags.Button1Clicked))
|
||||
{
|
||||
EnumerableTableSource<UnicodeRange> table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
|
||||
string prevSelection = table.Data.ElementAt (_categoryList.SelectedRow).Category;
|
||||
isDescending = !isDescending;
|
||||
|
||||
_categoryList.Table = CreateCategoryTable (clickedCol.Value, isDescending);
|
||||
|
||||
table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
|
||||
|
||||
_categoryList.SelectedRow = table.Data
|
||||
.Select ((item, index) => new { item, index })
|
||||
.FirstOrDefault (x => x.item.Category == prevSelection)
|
||||
?.index
|
||||
?? -1;
|
||||
}
|
||||
};
|
||||
|
||||
int longestName = UnicodeRange.Ranges.Max (r => r.Category.GetColumns ());
|
||||
|
||||
_categoryList.Style.ColumnStyles.Add (
|
||||
0,
|
||||
new () { MaxWidth = longestName, MinWidth = longestName, MinAcceptableWidth = longestName }
|
||||
);
|
||||
_categoryList.Style.ColumnStyles.Add (1, new () { MaxWidth = 1, MinWidth = 6 });
|
||||
_categoryList.Style.ColumnStyles.Add (2, new () { MaxWidth = 1, MinWidth = 6 });
|
||||
|
||||
_categoryList.Width = _categoryList.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 4;
|
||||
|
||||
_categoryList.SelectedCellChanged += (s, args) =>
|
||||
{
|
||||
EnumerableTableSource<UnicodeRange> table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
|
||||
_charMap.StartCodePoint = table.Data.ToArray () [args.NewRow].Start;
|
||||
jumpEdit.Text = $"U+{_charMap.SelectedCodePoint:x5}";
|
||||
};
|
||||
|
||||
top.Add (_categoryList);
|
||||
|
||||
var menu = new MenuBar
|
||||
{
|
||||
Menus =
|
||||
[
|
||||
new (
|
||||
"_File",
|
||||
new MenuItem []
|
||||
{
|
||||
new (
|
||||
"_Quit",
|
||||
$"{Application.QuitKey}",
|
||||
() => Application.RequestStop ()
|
||||
)
|
||||
}
|
||||
),
|
||||
new (
|
||||
"_Options",
|
||||
new [] { CreateMenuShowWidth () }
|
||||
)
|
||||
]
|
||||
};
|
||||
top.Add (menu);
|
||||
|
||||
_charMap.SelectedCodePoint = 0;
|
||||
_charMap.SetFocus ();
|
||||
|
||||
Application.Run (top);
|
||||
top.Dispose ();
|
||||
Application.Shutdown ();
|
||||
|
||||
return;
|
||||
|
||||
void JumpEditOnAccept (object? sender, CommandEventArgs e)
|
||||
{
|
||||
if (jumpEdit.Text.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_errorLabel.Visible = true;
|
||||
|
||||
uint result = 0;
|
||||
|
||||
if (jumpEdit.Text.Length == 1)
|
||||
{
|
||||
result = (uint)jumpEdit.Text.ToRunes () [0].Value;
|
||||
}
|
||||
else if (jumpEdit.Text.StartsWith ("U+", StringComparison.OrdinalIgnoreCase) || jumpEdit.Text.StartsWith ("\\u"))
|
||||
{
|
||||
try
|
||||
{
|
||||
result = uint.Parse (jumpEdit.Text [2..], NumberStyles.HexNumber);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
_errorLabel.Text = "Invalid hex value";
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (jumpEdit.Text.StartsWith ("0", StringComparison.OrdinalIgnoreCase) || jumpEdit.Text.StartsWith ("\\u"))
|
||||
{
|
||||
try
|
||||
{
|
||||
result = uint.Parse (jumpEdit.Text, NumberStyles.HexNumber);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
_errorLabel.Text = "Invalid hex value";
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
result = uint.Parse (jumpEdit.Text, NumberStyles.Integer);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
_errorLabel.Text = "Invalid value";
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (result > RuneExtensions.MaxUnicodeCodePoint)
|
||||
{
|
||||
_errorLabel.Text = "Beyond maximum codepoint";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_errorLabel.Visible = false;
|
||||
|
||||
EnumerableTableSource<UnicodeRange> table = (EnumerableTableSource<UnicodeRange>)_categoryList!.Table;
|
||||
|
||||
_categoryList.SelectedRow = table.Data
|
||||
.Select ((item, index) => new { item, index })
|
||||
.FirstOrDefault (x => x.item.Start <= result && x.item.End >= result)
|
||||
?.index
|
||||
?? -1;
|
||||
_categoryList.EnsureSelectedCellIsVisible ();
|
||||
|
||||
// Ensure the typed glyph is selected
|
||||
_charMap.SelectedCodePoint = (int)result;
|
||||
_charMap.SetFocus ();
|
||||
|
||||
// Cancel the event to prevent ENTER from being handled elsewhere
|
||||
e.Cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
private EnumerableTableSource<UnicodeRange> CreateCategoryTable (int sortByColumn, bool descending)
|
||||
{
|
||||
Func<UnicodeRange, object> orderBy;
|
||||
var categorySort = string.Empty;
|
||||
var startSort = string.Empty;
|
||||
var endSort = string.Empty;
|
||||
|
||||
string sortIndicator = descending ? CM.Glyphs.DownArrow.ToString () : CM.Glyphs.UpArrow.ToString ();
|
||||
|
||||
switch (sortByColumn)
|
||||
{
|
||||
case 0:
|
||||
orderBy = r => r.Category;
|
||||
categorySort = sortIndicator;
|
||||
|
||||
break;
|
||||
case 1:
|
||||
orderBy = r => r.Start;
|
||||
startSort = sortIndicator;
|
||||
|
||||
break;
|
||||
case 2:
|
||||
orderBy = r => r.End;
|
||||
endSort = sortIndicator;
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException ("Invalid column number.");
|
||||
}
|
||||
|
||||
IOrderedEnumerable<UnicodeRange> sortedRanges = descending
|
||||
? UnicodeRange.Ranges.OrderByDescending (orderBy)
|
||||
: UnicodeRange.Ranges.OrderBy (orderBy);
|
||||
|
||||
return new (
|
||||
sortedRanges,
|
||||
new ()
|
||||
{
|
||||
{ $"Category{categorySort}", s => s.Category },
|
||||
{ $"Start{startSort}", s => $"{s.Start:x5}" },
|
||||
{ $"End{endSort}", s => $"{s.End:x5}" }
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private MenuItem CreateMenuShowWidth ()
|
||||
{
|
||||
var item = new MenuItem { Title = "_Show Glyph Width" };
|
||||
item.CheckType |= MenuItemCheckStyle.Checked;
|
||||
item.Checked = _charMap?.ShowGlyphWidths;
|
||||
item.Action += () =>
|
||||
{
|
||||
if (_charMap is { })
|
||||
{
|
||||
_charMap.ShowGlyphWidths = (bool)(item.Checked = !item.Checked)!;
|
||||
}
|
||||
};
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public override List<Key> GetDemoKeyStrokes ()
|
||||
{
|
||||
List<Key> keys = new ();
|
||||
|
||||
for (var i = 0; i < 200; i++)
|
||||
{
|
||||
keys.Add (Key.CursorDown);
|
||||
}
|
||||
|
||||
// Category table
|
||||
keys.Add (Key.Tab.WithShift);
|
||||
|
||||
// Block elements
|
||||
keys.Add (Key.B);
|
||||
keys.Add (Key.L);
|
||||
|
||||
keys.Add (Key.Tab);
|
||||
|
||||
for (var i = 0; i < 200; i++)
|
||||
{
|
||||
keys.Add (Key.CursorLeft);
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
11
UICatalog/Scenarios/CharacterMap/README.md
Normal file
11
UICatalog/Scenarios/CharacterMap/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# CharacterMap Scenario Deep Dive
|
||||
|
||||
The CharacterMap Scenario is an example of the following Terminal.Gui concepts:
|
||||
|
||||
- **Complex and High-Performnt Drawing** -
|
||||
- **Virtual Content Scrolling** -
|
||||
- **Advanced ScrollBar Control** -
|
||||
- **Unicode wide-glyph Rendering** -
|
||||
- **Advanced Layout** -
|
||||
- **Cursor Management** -
|
||||
- **Context Menu** -
|
||||
48
UICatalog/Scenarios/CharacterMap/UcdApiClient.cs
Normal file
48
UICatalog/Scenarios/CharacterMap/UcdApiClient.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace UICatalog.Scenarios;
|
||||
|
||||
/// <summary>
|
||||
/// A helper class for accessing the ucdapi.org API.
|
||||
/// </summary>
|
||||
public class UcdApiClient
|
||||
{
|
||||
public const string BaseUrl = "https://ucdapi.org/unicode/latest/";
|
||||
private static readonly HttpClient _httpClient = new ();
|
||||
|
||||
public async Task<string> GetChars (string chars)
|
||||
{
|
||||
HttpResponseMessage response = await _httpClient.GetAsync ($"{BaseUrl}chars/{Uri.EscapeDataString (chars)}");
|
||||
response.EnsureSuccessStatusCode ();
|
||||
|
||||
return await response.Content.ReadAsStringAsync ();
|
||||
}
|
||||
|
||||
public async Task<string> GetCharsName (string chars)
|
||||
{
|
||||
HttpResponseMessage response =
|
||||
await _httpClient.GetAsync ($"{BaseUrl}chars/{Uri.EscapeDataString (chars)}/name");
|
||||
response.EnsureSuccessStatusCode ();
|
||||
|
||||
return await response.Content.ReadAsStringAsync ();
|
||||
}
|
||||
|
||||
public async Task<string> GetCodepointDec (int dec)
|
||||
{
|
||||
HttpResponseMessage response = await _httpClient.GetAsync ($"{BaseUrl}codepoint/dec/{dec}");
|
||||
response.EnsureSuccessStatusCode ();
|
||||
|
||||
return await response.Content.ReadAsStringAsync ();
|
||||
}
|
||||
|
||||
public async Task<string> GetCodepointHex (string hex)
|
||||
{
|
||||
HttpResponseMessage response = await _httpClient.GetAsync ($"{BaseUrl}codepoint/hex/{hex}");
|
||||
response.EnsureSuccessStatusCode ();
|
||||
|
||||
return await response.Content.ReadAsStringAsync ();
|
||||
}
|
||||
}
|
||||
101
UICatalog/Scenarios/CharacterMap/UnicodeRange.cs
Normal file
101
UICatalog/Scenarios/CharacterMap/UnicodeRange.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Unicode;
|
||||
|
||||
namespace UICatalog.Scenarios;
|
||||
|
||||
/// <summary>
|
||||
/// Represents all of the Uniicode ranges.from System.Text.Unicode.UnicodeRange plus
|
||||
/// the non-BMP ranges not included.
|
||||
/// </summary>
|
||||
public class UnicodeRange (int start, int end, string category)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the list of all ranges.
|
||||
/// </summary>
|
||||
public static List<UnicodeRange> Ranges => GetRanges ();
|
||||
|
||||
/// <summary>
|
||||
/// The category.
|
||||
/// </summary>
|
||||
public string Category { get; set; } = category;
|
||||
|
||||
/// <summary>
|
||||
/// Te codepoint at the start of the range.
|
||||
/// </summary>
|
||||
public int Start { get; set; } = start;
|
||||
|
||||
/// <summary>
|
||||
/// The codepoint at the end of the range.
|
||||
/// </summary>
|
||||
public int End { get; set; } = end;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of all ranges..
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static List<UnicodeRange> GetRanges ()
|
||||
{
|
||||
IEnumerable<UnicodeRange> ranges =
|
||||
from r in typeof (UnicodeRanges).GetProperties (BindingFlags.Static | BindingFlags.Public)
|
||||
let urange = r.GetValue (null) as System.Text.Unicode.UnicodeRange
|
||||
let name = string.IsNullOrEmpty (r.Name)
|
||||
? $"U+{urange.FirstCodePoint:x5}-U+{urange.FirstCodePoint + urange.Length:x5}"
|
||||
: r.Name
|
||||
where name != "None" && name != "All"
|
||||
select new UnicodeRange (urange.FirstCodePoint, urange.FirstCodePoint + urange.Length, name);
|
||||
|
||||
// .NET 8.0 only supports BMP in UnicodeRanges: https://learn.microsoft.com/en-us/dotnet/api/system.text.unicode.unicoderanges?view=net-8.0
|
||||
List<UnicodeRange> nonBmpRanges = new ()
|
||||
{
|
||||
new (
|
||||
0x1F130,
|
||||
0x1F149,
|
||||
"Squared Latin Capital Letters"
|
||||
),
|
||||
new (
|
||||
0x12400,
|
||||
0x1240f,
|
||||
"Cuneiform Numbers and Punctuation"
|
||||
),
|
||||
new (0x10000, 0x1007F, "Linear B Syllabary"),
|
||||
new (0x10080, 0x100FF, "Linear B Ideograms"),
|
||||
new (0x10100, 0x1013F, "Aegean Numbers"),
|
||||
new (0x10300, 0x1032F, "Old Italic"),
|
||||
new (0x10330, 0x1034F, "Gothic"),
|
||||
new (0x10380, 0x1039F, "Ugaritic"),
|
||||
new (0x10400, 0x1044F, "Deseret"),
|
||||
new (0x10450, 0x1047F, "Shavian"),
|
||||
new (0x10480, 0x104AF, "Osmanya"),
|
||||
new (0x10800, 0x1083F, "Cypriot Syllabary"),
|
||||
new (
|
||||
0x1D000,
|
||||
0x1D0FF,
|
||||
"Byzantine Musical Symbols"
|
||||
),
|
||||
new (0x1D100, 0x1D1FF, "Musical Symbols"),
|
||||
new (0x1D300, 0x1D35F, "Tai Xuan Jing Symbols"),
|
||||
new (
|
||||
0x1D400,
|
||||
0x1D7FF,
|
||||
"Mathematical Alphanumeric Symbols"
|
||||
),
|
||||
new (0x1F600, 0x1F532, "Emojis Symbols"),
|
||||
new (
|
||||
0x20000,
|
||||
0x2A6DF,
|
||||
"CJK Unified Ideographs Extension B"
|
||||
),
|
||||
new (
|
||||
0x2F800,
|
||||
0x2FA1F,
|
||||
"CJK Compatibility Ideographs Supplement"
|
||||
),
|
||||
new (0xE0000, 0xE007F, "Tags")
|
||||
};
|
||||
|
||||
return ranges.Concat (nonBmpRanges).OrderBy (r => r.Category).ToList ();
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,10 @@ using Terminal.Gui;
|
||||
|
||||
namespace UICatalog.Scenarios;
|
||||
|
||||
[ScenarioMetadata ("ScrollBar Demo", "Demonstrates using ScrollBar view.")]
|
||||
[ScenarioMetadata ("ScrollBar Demo", "Demonstrates ScrollBar.")]
|
||||
[ScenarioCategory ("Scrolling")]
|
||||
public class ScrollBarDemo : Scenario
|
||||
{
|
||||
private ViewDiagnosticFlags _diagnosticFlags;
|
||||
|
||||
public override void Main ()
|
||||
{
|
||||
Application.Init ();
|
||||
@@ -29,23 +27,22 @@ public class ScrollBarDemo : Scenario
|
||||
X = Pos.Right (editor),
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (),
|
||||
ColorScheme = Colors.ColorSchemes ["Base"]
|
||||
ColorScheme = Colors.ColorSchemes ["Base"],
|
||||
Arrangement = ViewArrangement.Resizable
|
||||
};
|
||||
frameView!.Padding!.Thickness = new (1);
|
||||
frameView.Padding.Diagnostics = ViewDiagnosticFlags.Ruler;
|
||||
app.Add (frameView);
|
||||
|
||||
var scrollBar = new ScrollBar
|
||||
{
|
||||
X = Pos.AnchorEnd (),
|
||||
AutoHide = false
|
||||
AutoHide = false,
|
||||
Size = 100,
|
||||
//ShowPercent = true
|
||||
};
|
||||
frameView.Add (scrollBar);
|
||||
|
||||
app.Loaded += (s, e) =>
|
||||
{
|
||||
scrollBar.Size = scrollBar.Viewport.Height;
|
||||
};
|
||||
|
||||
int GetMaxLabelWidth (int groupId)
|
||||
{
|
||||
return frameView.Subviews.Max (
|
||||
@@ -134,7 +131,6 @@ public class ScrollBarDemo : Scenario
|
||||
scrollBar.Y = 0;
|
||||
scrollWidthHeight.Value = Math.Min (scrollWidthHeight.Value, scrollBar.SuperView.GetContentSize ().Width);
|
||||
scrollBar.Width = scrollWidthHeight.Value;
|
||||
scrollBar.Size /= 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -143,7 +139,6 @@ public class ScrollBarDemo : Scenario
|
||||
scrollBar.Y = Pos.AnchorEnd ();
|
||||
scrollWidthHeight.Value = Math.Min (scrollWidthHeight.Value, scrollBar.SuperView.GetContentSize ().Height);
|
||||
scrollBar.Height = scrollWidthHeight.Value;
|
||||
scrollBar.Size *= 3;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -189,34 +184,14 @@ public class ScrollBarDemo : Scenario
|
||||
};
|
||||
frameView.Add (lblSliderPosition);
|
||||
|
||||
NumericUpDown<int> scrollSliderPosition = new ()
|
||||
Label scrollSliderPosition = new ()
|
||||
{
|
||||
Value = scrollBar.SliderPosition,
|
||||
Text = scrollBar.GetSliderPosition ().ToString (),
|
||||
X = Pos.Right (lblSliderPosition) + 1,
|
||||
Y = Pos.Top (lblSliderPosition)
|
||||
};
|
||||
frameView.Add (scrollSliderPosition);
|
||||
|
||||
scrollSliderPosition.ValueChanging += (s, e) =>
|
||||
{
|
||||
if (e.NewValue < 0)
|
||||
{
|
||||
e.Cancel = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (scrollBar.SliderPosition != e.NewValue)
|
||||
{
|
||||
scrollBar.SliderPosition = e.NewValue;
|
||||
}
|
||||
|
||||
if (scrollBar.SliderPosition != e.NewValue)
|
||||
{
|
||||
e.Cancel = true;
|
||||
}
|
||||
};
|
||||
|
||||
var lblContentPosition = new Label
|
||||
{
|
||||
Text = "_ContentPosition:",
|
||||
@@ -229,7 +204,7 @@ public class ScrollBarDemo : Scenario
|
||||
|
||||
NumericUpDown<int> scrollContentPosition = new ()
|
||||
{
|
||||
Value = scrollBar.SliderPosition,
|
||||
Value = scrollBar.GetSliderPosition (),
|
||||
X = Pos.Right (lblContentPosition) + 1,
|
||||
Y = Pos.Top (lblContentPosition)
|
||||
};
|
||||
@@ -342,22 +317,15 @@ public class ScrollBarDemo : Scenario
|
||||
}
|
||||
};
|
||||
|
||||
scrollBar.SliderPositionChanging += (s, e) =>
|
||||
{
|
||||
eventLog.Log ($"SliderPositionChanging: {e.CurrentValue}");
|
||||
eventLog.Log ($" NewValue: {e.NewValue}");
|
||||
};
|
||||
|
||||
scrollBar.SliderPositionChanged += (s, e) =>
|
||||
{
|
||||
eventLog.Log ($"SliderPositionChanged: {e.CurrentValue}");
|
||||
eventLog.Log ($" ContentPosition: {scrollBar.ContentPosition}");
|
||||
scrollSliderPosition.Value = e.CurrentValue;
|
||||
scrollSliderPosition.Text = e.CurrentValue.ToString ();
|
||||
};
|
||||
|
||||
editor.Initialized += (s, e) =>
|
||||
{
|
||||
scrollBar.Size = int.Max (app.GetContentSize ().Height * 2, app.GetContentSize ().Width * 2);
|
||||
editor.ViewToEdit = scrollBar;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Terminal.Gui;
|
||||
|
||||
namespace UICatalog.Scenarios;
|
||||
|
||||
[ScenarioMetadata ("Scroll Demo", "Demonstrates using Scroll view.")]
|
||||
[ScenarioCategory ("Drawing")]
|
||||
[ScenarioMetadata ("Scroll Demo", "Demonstrates Scroll.")]
|
||||
[ScenarioCategory ("Scrolling")]
|
||||
public class ScrollDemo : Scenario
|
||||
{
|
||||
private ViewDiagnosticFlags _diagnosticFlags;
|
||||
|
||||
public override void Main ()
|
||||
{
|
||||
Application.Init ();
|
||||
|
||||
Window app = new ()
|
||||
{
|
||||
Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"
|
||||
Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
|
||||
Arrangement = ViewArrangement.Fixed
|
||||
};
|
||||
|
||||
var editor = new AdornmentsEditor ();
|
||||
@@ -28,25 +27,40 @@ public class ScrollDemo : Scenario
|
||||
X = Pos.Right (editor),
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (),
|
||||
ColorScheme = Colors.ColorSchemes ["Base"]
|
||||
ColorScheme = Colors.ColorSchemes ["Base"],
|
||||
Arrangement = ViewArrangement.Resizable
|
||||
};
|
||||
frameView.Padding.Thickness = new (1);
|
||||
frameView.Padding.Diagnostics = ViewDiagnosticFlags.Ruler;
|
||||
app.Add (frameView);
|
||||
|
||||
var scroll = new Scroll
|
||||
{
|
||||
X = Pos.AnchorEnd (),
|
||||
ShowPercent = true
|
||||
Size = 1000,
|
||||
};
|
||||
frameView.Add (scroll);
|
||||
|
||||
app.Loaded += (s, e) =>
|
||||
{
|
||||
scroll.Size = frameView.Viewport.Height;
|
||||
};
|
||||
int GetMaxLabelWidth (int groupId)
|
||||
{
|
||||
return frameView.Subviews.Max (
|
||||
v =>
|
||||
{
|
||||
if (v.Y.Has<PosAlign> (out var pos) && pos.GroupId == groupId)
|
||||
{
|
||||
return v.Text.GetColumns ();
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
var lblWidthHeight = new Label
|
||||
{
|
||||
Text = "Width/Height:"
|
||||
Text = "_Width/Height:",
|
||||
TextAlignment = Alignment.End,
|
||||
Y = Pos.Align (Alignment.Start, AlignmentModes.StartToEnd, groupId: 1),
|
||||
Width = Dim.Func (() => GetMaxLabelWidth (1))
|
||||
};
|
||||
frameView.Add (lblWidthHeight);
|
||||
|
||||
@@ -59,71 +73,79 @@ public class ScrollDemo : Scenario
|
||||
frameView.Add (scrollWidthHeight);
|
||||
|
||||
scrollWidthHeight.ValueChanging += (s, e) =>
|
||||
{
|
||||
if (e.NewValue < 1
|
||||
|| (e.NewValue
|
||||
> (scroll.Orientation == Orientation.Vertical
|
||||
? scroll.SuperView?.GetContentSize ().Width
|
||||
: scroll.SuperView?.GetContentSize ().Height)))
|
||||
{
|
||||
// TODO: This must be handled in the ScrollSlider if Width and Height being virtual
|
||||
e.Cancel = true;
|
||||
{
|
||||
if (e.NewValue < 1
|
||||
|| (e.NewValue
|
||||
> (scroll.Orientation == Orientation.Vertical
|
||||
? scroll.SuperView?.GetContentSize ().Width
|
||||
: scroll.SuperView?.GetContentSize ().Height)))
|
||||
{
|
||||
// TODO: This must be handled in the ScrollSlider if Width and Height being virtual
|
||||
e.Cancel = true;
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (scroll.Orientation == Orientation.Vertical)
|
||||
{
|
||||
scroll.Width = e.NewValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
scroll.Height = e.NewValue;
|
||||
}
|
||||
};
|
||||
if (scroll.Orientation == Orientation.Vertical)
|
||||
{
|
||||
scroll.Width = e.NewValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
scroll.Height = e.NewValue;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var lblOrientationabel = new Label
|
||||
{
|
||||
Text = "_Orientation:",
|
||||
TextAlignment = Alignment.End,
|
||||
Y = Pos.Align (Alignment.Start, groupId: 1),
|
||||
Width = Dim.Func (() => GetMaxLabelWidth (1))
|
||||
};
|
||||
frameView.Add (lblOrientationabel);
|
||||
|
||||
var rgOrientation = new RadioGroup
|
||||
{
|
||||
Y = Pos.Bottom (lblWidthHeight),
|
||||
X = Pos.Right (lblOrientationabel) + 1,
|
||||
Y = Pos.Top (lblOrientationabel),
|
||||
RadioLabels = ["Vertical", "Horizontal"],
|
||||
Orientation = Orientation.Horizontal
|
||||
};
|
||||
frameView.Add (rgOrientation);
|
||||
|
||||
rgOrientation.SelectedItemChanged += (s, e) =>
|
||||
{
|
||||
if (e.SelectedItem == e.PreviousSelectedItem)
|
||||
{
|
||||
return;
|
||||
}
|
||||
{
|
||||
if (e.SelectedItem == e.PreviousSelectedItem)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (rgOrientation.SelectedItem == 0)
|
||||
{
|
||||
scroll.Orientation = Orientation.Vertical;
|
||||
scroll.X = Pos.AnchorEnd ();
|
||||
scroll.Y = 0;
|
||||
scrollWidthHeight.Value = Math.Min (scrollWidthHeight.Value, scroll.SuperView.GetContentSize ().Width);
|
||||
scroll.Width = scrollWidthHeight.Value;
|
||||
scroll.Height = Dim.Fill ();
|
||||
scroll.Size /= 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
scroll.Orientation = Orientation.Horizontal;
|
||||
scroll.X = 0;
|
||||
scroll.Y = Pos.AnchorEnd ();
|
||||
scroll.Width = Dim.Fill ();
|
||||
|
||||
scrollWidthHeight.Value = Math.Min (scrollWidthHeight.Value, scroll.SuperView.GetContentSize ().Height);
|
||||
scroll.Height = scrollWidthHeight.Value;
|
||||
scroll.Size *= 3;
|
||||
}
|
||||
};
|
||||
if (rgOrientation.SelectedItem == 0)
|
||||
{
|
||||
scroll.Orientation = Orientation.Vertical;
|
||||
scroll.X = Pos.AnchorEnd ();
|
||||
scroll.Y = 0;
|
||||
scrollWidthHeight.Value = Math.Min (scrollWidthHeight.Value, scroll.SuperView.GetContentSize ().Width);
|
||||
scroll.Width = scrollWidthHeight.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
scroll.Orientation = Orientation.Horizontal;
|
||||
scroll.X = 0;
|
||||
scroll.Y = Pos.AnchorEnd ();
|
||||
scrollWidthHeight.Value = Math.Min (scrollWidthHeight.Value, scroll.SuperView.GetContentSize ().Height);
|
||||
scroll.Height = scrollWidthHeight.Value;
|
||||
}
|
||||
};
|
||||
|
||||
var lblSize = new Label
|
||||
{
|
||||
Y = Pos.Bottom (rgOrientation),
|
||||
Text = "Size:"
|
||||
Text = "_Size:",
|
||||
TextAlignment = Alignment.End,
|
||||
Y = Pos.Align (Alignment.Start, groupId: 1),
|
||||
Width = Dim.Func (() => GetMaxLabelWidth (1))
|
||||
};
|
||||
frameView.Add (lblSize);
|
||||
|
||||
@@ -133,106 +155,109 @@ public class ScrollDemo : Scenario
|
||||
X = Pos.Right (lblSize) + 1,
|
||||
Y = Pos.Top (lblSize)
|
||||
};
|
||||
scroll.SizeChanged += (sender, args) => scrollSize.Value = args.CurrentValue;
|
||||
frameView.Add (scrollSize);
|
||||
|
||||
scrollSize.ValueChanging += (s, e) =>
|
||||
{
|
||||
if (e.NewValue < 0)
|
||||
{
|
||||
e.Cancel = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (scroll.Size != e.NewValue)
|
||||
{
|
||||
scroll.Size = e.NewValue;
|
||||
}
|
||||
};
|
||||
|
||||
var lblPosition = new Label
|
||||
{
|
||||
Y = Pos.Bottom (lblSize),
|
||||
Text = "Position:"
|
||||
};
|
||||
frameView.Add (lblPosition);
|
||||
if (e.NewValue < 0)
|
||||
{
|
||||
e.Cancel = true;
|
||||
|
||||
NumericUpDown<int> scrollPosition = new ()
|
||||
return;
|
||||
}
|
||||
|
||||
if (scroll.Size != e.NewValue)
|
||||
{
|
||||
scroll.Size = e.NewValue;
|
||||
}
|
||||
};
|
||||
|
||||
var lblSliderPosition = new Label
|
||||
{
|
||||
Value = scroll.SliderPosition,
|
||||
X = Pos.Right (lblPosition) + 1,
|
||||
Y = Pos.Top (lblPosition)
|
||||
Text = "_SliderPosition:",
|
||||
TextAlignment = Alignment.End,
|
||||
Y = Pos.Align (Alignment.Start, groupId: 1),
|
||||
Width = Dim.Func (() => GetMaxLabelWidth (1))
|
||||
|
||||
};
|
||||
frameView.Add (scrollPosition);
|
||||
frameView.Add (lblSliderPosition);
|
||||
|
||||
scrollPosition.ValueChanging += (s, e) =>
|
||||
{
|
||||
if (e.NewValue < 0)
|
||||
{
|
||||
e.Cancel = true;
|
||||
Label scrollSliderPosition = new ()
|
||||
{
|
||||
Text = scroll.GetSliderPosition ().ToString (),
|
||||
X = Pos.Right (lblSliderPosition) + 1,
|
||||
Y = Pos.Top (lblSliderPosition)
|
||||
};
|
||||
frameView.Add (scrollSliderPosition);
|
||||
|
||||
return;
|
||||
}
|
||||
var lblContentPosition = new Label
|
||||
{
|
||||
Text = "_ContentPosition:",
|
||||
TextAlignment = Alignment.End,
|
||||
Y = Pos.Align (Alignment.Start, groupId: 1),
|
||||
Width = Dim.Func (() => GetMaxLabelWidth (1))
|
||||
|
||||
if (scroll.SliderPosition != e.NewValue)
|
||||
{
|
||||
scroll.SliderPosition = e.NewValue;
|
||||
}
|
||||
};
|
||||
frameView.Add (lblContentPosition);
|
||||
|
||||
if (scroll.SliderPosition != e.NewValue)
|
||||
{
|
||||
e.Cancel = true;
|
||||
}
|
||||
};
|
||||
NumericUpDown<int> scrollContentPosition = new ()
|
||||
{
|
||||
Value = scroll.GetSliderPosition (),
|
||||
X = Pos.Right (lblContentPosition) + 1,
|
||||
Y = Pos.Top (lblContentPosition)
|
||||
};
|
||||
frameView.Add (scrollContentPosition);
|
||||
|
||||
scrollContentPosition.ValueChanging += (s, e) =>
|
||||
{
|
||||
if (e.NewValue < 0)
|
||||
{
|
||||
e.Cancel = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (scroll.ContentPosition != e.NewValue)
|
||||
{
|
||||
scroll.ContentPosition = e.NewValue;
|
||||
}
|
||||
|
||||
if (scroll.ContentPosition != e.NewValue)
|
||||
{
|
||||
e.Cancel = true;
|
||||
}
|
||||
};
|
||||
|
||||
var lblOptions = new Label
|
||||
{
|
||||
Text = "_Options:",
|
||||
TextAlignment = Alignment.End,
|
||||
Y = Pos.Align (Alignment.Start, groupId: 1),
|
||||
Width = Dim.Func (() => GetMaxLabelWidth (1))
|
||||
};
|
||||
frameView.Add (lblOptions);
|
||||
|
||||
var ckbShowPercent = new CheckBox
|
||||
{
|
||||
Y = Pos.Top (lblOptions),
|
||||
X = Pos.Right (lblOptions) + 1,
|
||||
Text = "Sho_wPercent",
|
||||
CheckedState = scroll.ShowPercent ? CheckState.Checked : CheckState.UnChecked
|
||||
};
|
||||
ckbShowPercent.CheckedStateChanging += (s, e) => scroll.ShowPercent = e.NewValue == CheckState.Checked;
|
||||
frameView.Add (ckbShowPercent);
|
||||
|
||||
//var ckbKeepContentInAllViewport = new CheckBox
|
||||
//{
|
||||
// Y = Pos.Bottom (scrollPosition), Text = "KeepContentInAllViewport",
|
||||
// CheckedState = scroll.KeepContentInAllViewport ? CheckState.Checked : CheckState.UnChecked
|
||||
// X = Pos.Right (ckbShowScrollIndicator) + 1, Y = Pos.Bottom (scrollPosition), Text = "KeepContentInAllViewport",
|
||||
// CheckedState = Scroll.KeepContentInAllViewport ? CheckState.Checked : CheckState.UnChecked
|
||||
//};
|
||||
//ckbKeepContentInAllViewport.CheckedStateChanging += (s, e) => scroll.KeepContentInAllViewport = e.NewValue == CheckState.Checked;
|
||||
//ckbKeepContentInAllViewport.CheckedStateChanging += (s, e) => Scroll.KeepContentInAllViewport = e.NewValue == CheckState.Checked;
|
||||
//view.Add (ckbKeepContentInAllViewport);
|
||||
|
||||
var lblSizeChanged = new Label
|
||||
{
|
||||
Y = Pos.Bottom (scrollPosition) + 1
|
||||
};
|
||||
frameView.Add (lblSizeChanged);
|
||||
|
||||
scroll.SizeChanged += (s, e) =>
|
||||
{
|
||||
lblSizeChanged.Text = $"SizeChanged event - CurrentValue: {e.CurrentValue}";
|
||||
|
||||
if (scrollSize.Value != e.CurrentValue)
|
||||
{
|
||||
scrollSize.Value = e.CurrentValue;
|
||||
}
|
||||
};
|
||||
|
||||
var lblPosChanging = new Label
|
||||
{
|
||||
Y = Pos.Bottom (lblSizeChanged)
|
||||
};
|
||||
frameView.Add (lblPosChanging);
|
||||
|
||||
scroll.SliderPositionChanging += (s, e) => { lblPosChanging.Text = $"PositionChanging event - CurrentValue: {e.CurrentValue}; NewValue: {e.NewValue}"; };
|
||||
|
||||
var lblPositionChanged = new Label
|
||||
{
|
||||
Y = Pos.Bottom (lblPosChanging)
|
||||
};
|
||||
frameView.Add (lblPositionChanged);
|
||||
|
||||
scroll.SliderPositionChanged += (s, e) =>
|
||||
{
|
||||
lblPositionChanged.Text = $"PositionChanged event - CurrentValue: {e.CurrentValue}";
|
||||
scrollPosition.Value = e.CurrentValue;
|
||||
};
|
||||
|
||||
var lblScrollFrame = new Label
|
||||
{
|
||||
Y = Pos.Bottom (lblPositionChanged) + 1
|
||||
Y = Pos.Bottom (lblOptions) + 1
|
||||
};
|
||||
frameView.Add (lblScrollFrame);
|
||||
|
||||
@@ -248,21 +273,59 @@ public class ScrollDemo : Scenario
|
||||
};
|
||||
frameView.Add (lblScrollContentSize);
|
||||
|
||||
|
||||
scroll.SubviewsLaidOut += (s, e) =>
|
||||
{
|
||||
lblScrollFrame.Text = $"Scroll Frame: {scroll.Frame.ToString ()}";
|
||||
lblScrollViewport.Text = $"Scroll Viewport: {scroll.Viewport.ToString ()}";
|
||||
lblScrollContentSize.Text = $"Scroll ContentSize: {scroll.GetContentSize ().ToString ()}";
|
||||
};
|
||||
{
|
||||
lblScrollFrame.Text = $"Scroll Frame: {scroll.Frame.ToString ()}";
|
||||
lblScrollViewport.Text = $"Scroll Viewport: {scroll.Viewport.ToString ()}";
|
||||
lblScrollContentSize.Text = $"Scroll ContentSize: {scroll.GetContentSize ().ToString ()}";
|
||||
};
|
||||
|
||||
editor.Initialized += (s, e) =>
|
||||
{
|
||||
scroll.Size = int.Max (app.GetContentSize ().Height * 2, app.GetContentSize ().Width * 2);
|
||||
editor.ViewToEdit = scroll;
|
||||
};
|
||||
EventLog eventLog = new ()
|
||||
{
|
||||
X = Pos.AnchorEnd () - 1,
|
||||
Y = 0,
|
||||
Height = Dim.Height (frameView),
|
||||
BorderStyle = LineStyle.Single,
|
||||
ViewToLog = scroll
|
||||
};
|
||||
app.Add (eventLog);
|
||||
frameView.Width = Dim.Fill (Dim.Func (() => Math.Max (28, eventLog.Frame.Width + 1)));
|
||||
|
||||
app.Closed += (s, e) => View.Diagnostics = _diagnosticFlags;
|
||||
app.Initialized += AppOnInitialized;
|
||||
|
||||
|
||||
void AppOnInitialized (object sender, EventArgs e)
|
||||
{
|
||||
scroll.SizeChanged += (s, e) =>
|
||||
{
|
||||
eventLog.Log ($"SizeChanged: {e.CurrentValue}");
|
||||
|
||||
if (scrollSize.Value != e.CurrentValue)
|
||||
{
|
||||
scrollSize.Value = e.CurrentValue;
|
||||
}
|
||||
};
|
||||
|
||||
scroll.SliderPositionChanged += (s, e) =>
|
||||
{
|
||||
eventLog.Log ($"SliderPositionChanged: {e.CurrentValue}");
|
||||
eventLog.Log ($" ContentPosition: {scroll.ContentPosition}");
|
||||
scrollSliderPosition.Text = e.CurrentValue.ToString ();
|
||||
};
|
||||
|
||||
scroll.ContentPositionChanged += (s, e) =>
|
||||
{
|
||||
eventLog.Log ($"SliderPositionChanged: {e.CurrentValue}");
|
||||
eventLog.Log ($" ContentPosition: {scroll.ContentPosition}");
|
||||
scrollContentPosition.Value = e.CurrentValue;
|
||||
};
|
||||
|
||||
editor.Initialized += (s, e) =>
|
||||
{
|
||||
editor.ViewToEdit = scroll;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Application.Run (app);
|
||||
app.Dispose ();
|
||||
|
||||
@@ -65,8 +65,8 @@ public class TableEditor : Scenario
|
||||
"Cuneiform Numbers and Punctuation"
|
||||
),
|
||||
new (
|
||||
(uint)(CharMap._maxCodePoint - 16),
|
||||
(uint)CharMap._maxCodePoint,
|
||||
(uint)(UICatalog.Scenarios.UnicodeRange.Ranges.Max (r => r.End) - 16),
|
||||
(uint)UICatalog.Scenarios.UnicodeRange.Ranges.Max (r => r.End),
|
||||
"End"
|
||||
),
|
||||
new (0x0020, 0x007F, "Basic Latin"),
|
||||
@@ -1533,17 +1533,10 @@ public class TableEditor : Scenario
|
||||
);
|
||||
}
|
||||
|
||||
private class UnicodeRange
|
||||
public class UnicodeRange (uint start, uint end, string category)
|
||||
{
|
||||
public readonly string Category;
|
||||
public readonly uint End;
|
||||
public readonly uint Start;
|
||||
|
||||
public UnicodeRange (uint start, uint end, string category)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
Category = category;
|
||||
}
|
||||
public readonly string Category = category;
|
||||
public readonly uint End = end;
|
||||
public readonly uint Start = start;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public class ScrollBarTests
|
||||
Assert.False (scrollBar.CanFocus);
|
||||
Assert.Equal (Orientation.Vertical, scrollBar.Orientation);
|
||||
Assert.Equal (0, scrollBar.Size);
|
||||
Assert.Equal (0, scrollBar.SliderPosition);
|
||||
Assert.Equal (0, scrollBar.GetSliderPosition ());
|
||||
Assert.True (scrollBar.AutoHide);
|
||||
}
|
||||
|
||||
@@ -82,43 +82,10 @@ public class ScrollBarTests
|
||||
};
|
||||
super.Add (scrollBar);
|
||||
scrollBar.Layout ();
|
||||
scrollBar.SliderPosition = 1;
|
||||
scrollBar.ContentPosition = 1;
|
||||
scrollBar.Orientation = Orientation.Horizontal;
|
||||
|
||||
Assert.Equal (0, scrollBar.SliderPosition);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void SliderPosition_Event_Cancelables ()
|
||||
{
|
||||
var changingCount = 0;
|
||||
var changedCount = 0;
|
||||
var scrollBar = new ScrollBar { };
|
||||
scrollBar.Layout ();
|
||||
scrollBar.Size = scrollBar.Viewport.Height * 2;
|
||||
scrollBar.Layout ();
|
||||
|
||||
scrollBar.SliderPositionChanging += (s, e) =>
|
||||
{
|
||||
if (changingCount == 0)
|
||||
{
|
||||
e.Cancel = true;
|
||||
}
|
||||
|
||||
changingCount++;
|
||||
};
|
||||
scrollBar.SliderPositionChanged += (s, e) => changedCount++;
|
||||
|
||||
scrollBar.SliderPosition = 1;
|
||||
Assert.Equal (0, scrollBar.SliderPosition);
|
||||
Assert.Equal (1, changingCount);
|
||||
Assert.Equal (0, changedCount);
|
||||
|
||||
scrollBar.SliderPosition = 1;
|
||||
Assert.Equal (1, scrollBar.SliderPosition);
|
||||
Assert.Equal (2, changingCount);
|
||||
Assert.Equal (1, changedCount);
|
||||
Assert.Equal (0, scrollBar.GetSliderPosition ());
|
||||
}
|
||||
|
||||
|
||||
@@ -179,10 +146,10 @@ public class ScrollBarTests
|
||||
Assert.Equal (30, scrollBar.Size);
|
||||
|
||||
scrollBar.KeepContentInAllViewport = false;
|
||||
scrollBar.SliderPosition = 50;
|
||||
Assert.Equal (scrollBar.SliderPosition, scrollBar.Size - 1);
|
||||
Assert.Equal (scrollBar.SliderPosition, view.Viewport.Y);
|
||||
Assert.Equal (29, scrollBar.SliderPosition);
|
||||
scrollBar.ContentPosition = 50;
|
||||
Assert.Equal (scrollBar.GetSliderPosition (), scrollBar.Size - 1);
|
||||
Assert.Equal (scrollBar.GetSliderPosition (), view.Viewport.Y);
|
||||
Assert.Equal (29, scrollBar.GetSliderPosition ());
|
||||
Assert.Equal (29, view.Viewport.Y);
|
||||
|
||||
top.Dispose ();
|
||||
@@ -198,7 +165,7 @@ public class ScrollBarTests
|
||||
var scrollBar = new ScrollBar
|
||||
{
|
||||
X = 10, Y = 10, Width = orientation == Orientation.Vertical ? 1 : 10, Height = orientation == Orientation.Vertical ? 10 : 1, Size = 20,
|
||||
SliderPosition = 5, Orientation = orientation, KeepContentInAllViewport = true
|
||||
ContentPosition = 5, Orientation = orientation, KeepContentInAllViewport = true
|
||||
};
|
||||
var top = new Toplevel ();
|
||||
top.Add (scrollBar);
|
||||
@@ -346,7 +313,7 @@ public class ScrollBarTests
|
||||
|
||||
scrollBar.Size = sliderSize;
|
||||
scrollBar.Layout ();
|
||||
scrollBar.SliderPosition = sliderPosition;
|
||||
scrollBar.ContentPosition = sliderPosition;
|
||||
|
||||
super.BeginInit ();
|
||||
super.EndInit ();
|
||||
@@ -392,10 +359,10 @@ public class ScrollBarTests
|
||||
|
||||
top.Add (scrollBar);
|
||||
RunState rs = Application.Begin (top);
|
||||
scrollBar.SliderPosition = 5;
|
||||
scrollBar.ContentPosition = 5;
|
||||
Application.RunIteration (ref rs);
|
||||
|
||||
Assert.Equal (5, scrollBar.SliderPosition);
|
||||
Assert.Equal (5, scrollBar.GetSliderPosition ());
|
||||
Assert.Equal (12, scrollBar.ContentPosition);
|
||||
int initialPos = scrollBar.ContentPosition;
|
||||
|
||||
@@ -433,10 +400,10 @@ public class ScrollBarTests
|
||||
|
||||
top.Add (scrollBar);
|
||||
RunState rs = Application.Begin (top);
|
||||
scrollBar.SliderPosition = 0;
|
||||
scrollBar.ContentPosition = 0;
|
||||
Application.RunIteration (ref rs);
|
||||
|
||||
Assert.Equal (0, scrollBar.SliderPosition);
|
||||
Assert.Equal (0, scrollBar.GetSliderPosition ());
|
||||
Assert.Equal (0, scrollBar.ContentPosition);
|
||||
int initialPos = scrollBar.ContentPosition;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ public class ScrollTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnOrientationChanged_Sets_Position_To_0 ()
|
||||
public void OnOrientationChanged_Sets_ContentPosition_To_0 ()
|
||||
{
|
||||
View super = new View ()
|
||||
{
|
||||
@@ -34,10 +34,10 @@ public class ScrollTests
|
||||
};
|
||||
super.Add (scroll);
|
||||
scroll.Layout ();
|
||||
scroll.SliderPosition = 1;
|
||||
scroll.ContentPosition = 1;
|
||||
scroll.Orientation = Orientation.Horizontal;
|
||||
|
||||
Assert.Equal (0, scroll.SliderPosition);
|
||||
Assert.Equal (0, scroll.ContentPosition);
|
||||
}
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ public class ScrollTests
|
||||
Assert.False (scroll.CanFocus);
|
||||
Assert.Equal (Orientation.Vertical, scroll.Orientation);
|
||||
Assert.Equal (0, scroll.Size);
|
||||
Assert.Equal (0, scroll.SliderPosition);
|
||||
Assert.Equal (0, scroll.GetSliderPosition ());
|
||||
}
|
||||
|
||||
//[Fact]
|
||||
@@ -324,7 +324,7 @@ public class ScrollTests
|
||||
top.Add (scroll);
|
||||
RunState rs = Application.Begin (top);
|
||||
|
||||
Assert.Equal (0, scroll.SliderPosition);
|
||||
Assert.Equal (0, scroll.GetSliderPosition ());
|
||||
Assert.Equal (0, scroll.ContentPosition);
|
||||
|
||||
Application.RaiseMouseEvent (new ()
|
||||
@@ -377,10 +377,10 @@ public class ScrollTests
|
||||
|
||||
top.Add (scroll);
|
||||
RunState rs = Application.Begin (top);
|
||||
scroll.SliderPosition = 5;
|
||||
scroll.ContentPosition = 5;
|
||||
Application.RunIteration (ref rs);
|
||||
|
||||
Assert.Equal (5, scroll.SliderPosition);
|
||||
Assert.Equal (5, scroll.GetSliderPosition ());
|
||||
Assert.Equal (10, scroll.ContentPosition);
|
||||
|
||||
Application.RaiseMouseEvent (new ()
|
||||
@@ -390,7 +390,7 @@ public class ScrollTests
|
||||
});
|
||||
Application.RunIteration (ref rs);
|
||||
|
||||
Assert.Equal (0, scroll.SliderPosition);
|
||||
Assert.Equal (0, scroll.GetSliderPosition ());
|
||||
Assert.Equal (0, scroll.ContentPosition);
|
||||
|
||||
Application.ResetState (true);
|
||||
@@ -421,42 +421,10 @@ public class ScrollTests
|
||||
scroll.Size = scrollSize;
|
||||
super.Layout ();
|
||||
|
||||
scroll.SliderPosition = scrollPosition;
|
||||
scroll.ContentPosition = scrollPosition;
|
||||
super.Layout ();
|
||||
|
||||
Assert.True (scroll.SliderPosition <= scrollSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SliderPosition_Event_Cancelables ()
|
||||
{
|
||||
var changingCount = 0;
|
||||
var changedCount = 0;
|
||||
var scroll = new Scroll { };
|
||||
scroll.Layout ();
|
||||
scroll.Size = scroll.Viewport.Height * 2;
|
||||
scroll.Layout ();
|
||||
|
||||
scroll.SliderPositionChanging += (s, e) =>
|
||||
{
|
||||
if (changingCount == 0)
|
||||
{
|
||||
e.Cancel = true;
|
||||
}
|
||||
|
||||
changingCount++;
|
||||
};
|
||||
scroll.SliderPositionChanged += (s, e) => changedCount++;
|
||||
|
||||
scroll.SliderPosition = 1;
|
||||
Assert.Equal (0, scroll.SliderPosition);
|
||||
Assert.Equal (1, changingCount);
|
||||
Assert.Equal (0, changedCount);
|
||||
|
||||
scroll.SliderPosition = 1;
|
||||
Assert.Equal (1, scroll.SliderPosition);
|
||||
Assert.Equal (2, changingCount);
|
||||
Assert.Equal (1, changedCount);
|
||||
Assert.True (scroll.GetSliderPosition () <= scrollSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -466,60 +434,52 @@ public class ScrollTests
|
||||
var cancel = false;
|
||||
var changed = 0;
|
||||
var scroll = new Scroll { Height = 10, Size = 20 };
|
||||
scroll.SliderPositionChanging += Scroll_PositionChanging;
|
||||
scroll.SliderPositionChanged += Scroll_PositionChanged;
|
||||
|
||||
Assert.Equal (Orientation.Vertical, scroll.Orientation);
|
||||
scroll.Layout ();
|
||||
Assert.Equal (new (0, 0, 1, 10), scroll.Viewport);
|
||||
Assert.Equal (0, scroll.SliderPosition);
|
||||
Assert.Equal (0, scroll.GetSliderPosition ());
|
||||
Assert.Equal (0, changing);
|
||||
Assert.Equal (0, changed);
|
||||
|
||||
scroll.SliderPosition = 0;
|
||||
Assert.Equal (0, scroll.SliderPosition);
|
||||
scroll.ContentPosition = 0;
|
||||
Assert.Equal (0, scroll.GetSliderPosition ());
|
||||
Assert.Equal (0, changing);
|
||||
Assert.Equal (0, changed);
|
||||
|
||||
scroll.SliderPosition = 1;
|
||||
Assert.Equal (1, scroll.SliderPosition);
|
||||
scroll.ContentPosition = 1;
|
||||
Assert.Equal (1, scroll.GetSliderPosition ());
|
||||
Assert.Equal (1, changing);
|
||||
Assert.Equal (1, changed);
|
||||
|
||||
Reset ();
|
||||
cancel = true;
|
||||
scroll.SliderPosition = 2;
|
||||
Assert.Equal (1, scroll.SliderPosition);
|
||||
scroll.ContentPosition = 2;
|
||||
Assert.Equal (1, scroll.GetSliderPosition ());
|
||||
Assert.Equal (1, changing);
|
||||
Assert.Equal (0, changed);
|
||||
|
||||
Reset ();
|
||||
scroll.SliderPosition = 10;
|
||||
Assert.Equal (5, scroll.SliderPosition);
|
||||
scroll.ContentPosition = 10;
|
||||
Assert.Equal (5, scroll.GetSliderPosition ());
|
||||
Assert.Equal (1, changing);
|
||||
Assert.Equal (1, changed);
|
||||
|
||||
Reset ();
|
||||
scroll.SliderPosition = 11;
|
||||
Assert.Equal (5, scroll.SliderPosition);
|
||||
scroll.ContentPosition = 11;
|
||||
Assert.Equal (5, scroll.GetSliderPosition ());
|
||||
Assert.Equal (1, changing);
|
||||
Assert.Equal (1, changed);
|
||||
|
||||
Reset ();
|
||||
scroll.SliderPosition = 0;
|
||||
Assert.Equal (0, scroll.SliderPosition);
|
||||
scroll.ContentPosition = 0;
|
||||
Assert.Equal (0, scroll.GetSliderPosition ());
|
||||
Assert.Equal (1, changing);
|
||||
Assert.Equal (1, changed);
|
||||
|
||||
scroll.SliderPositionChanging -= Scroll_PositionChanging;
|
||||
scroll.SliderPositionChanged -= Scroll_PositionChanged;
|
||||
|
||||
void Scroll_PositionChanging (object sender, CancelEventArgs<int> e)
|
||||
{
|
||||
changing++;
|
||||
e.Cancel = cancel;
|
||||
}
|
||||
|
||||
void Scroll_PositionChanged (object sender, EventArgs<int> e) { changed++; }
|
||||
|
||||
void Reset ()
|
||||
@@ -625,7 +585,7 @@ public class ScrollTests
|
||||
|
||||
scroll.Size = sliderSize;
|
||||
scroll.Layout ();
|
||||
scroll.SliderPosition = sliderPosition;
|
||||
scroll.ContentPosition = sliderPosition;
|
||||
|
||||
super.BeginInit ();
|
||||
super.EndInit ();
|
||||
|
||||
Reference in New Issue
Block a user