- /// Indicates whether the specified SuperView-relative coordinates are within the View's <see cref="Frame"/>.
+ /// Indicates whether the specified SuperView-relative coordinates are within the View's <see cref="Frame"/>.
/// </summary>
/// </summary>
/// <param name="x">SuperView-relative X coordinate.</param>
/// <param name="x">SuperView-relative X coordinate.</param>
/// <param name="y">SuperView-relative Y coordinate.</param>
/// <param name="y">SuperView-relative Y coordinate.</param>
/// <returns><see langword="true"/> if the specified SuperView-relative coordinates are within the View.</returns>
/// <returns><see langword="true"/> if the specified SuperView-relative coordinates are within the View.</returns>
- public virtual bool Contains (int x, int y)
- {
- return Frame.Contains (x, y);
- }
+ public virtual bool Contains (int x, int y) { return Frame.Contains (x, y); }
#nullable enable
#nullable enable
/// <summary>Finds the first Subview of <paramref name="start"/> that is visible at the provided location.</summary>
/// <summary>Finds the first Subview of <paramref name="start"/> that is visible at the provided location.</summary>
/// <remarks>
/// <remarks>
- /// <para>
- /// Used to determine what view the mouse is over.
- /// </para>
+ /// <para>
+ /// Used to determine what view the mouse is over.
+ /// </para>
/// </remarks>
/// </remarks>
/// <param name="start">The view to scope the search by.</param>
/// <param name="start">The view to scope the search by.</param>
/// <param name="x"><paramref name="start"/>.SuperView-relative X coordinate.</param>
/// <param name="x"><paramref name="start"/>.SuperView-relative X coordinate.</param>
@@ -682,57 +627,66 @@ public partial class View
// CONCURRENCY: This method is not thread-safe. Undefined behavior and likely program crashes are exposed by unsynchronized access to InternalSubviews.
// CONCURRENCY: This method is not thread-safe. Undefined behavior and likely program crashes are exposed by unsynchronized access to InternalSubviews.
internal static View? FindDeepestView (View? start, int x, int y)
internal static View? FindDeepestView (View? start, int x, int y)
{
{
- if (start is null || !start.Visible || !start.Contains (x, y))
+ while (start is { Visible: true } && start.Contains (x, y))
{
{
- return null;
- }
-
- Adornment? found = null;
+ Adornment? found = null;
- if (start.Margin.Contains (x, y))
- {
- found = start.Margin;
- }
- else if (start.Border.Contains (x, y))
- {
- found = start.Border;
- }
- else if (start.Padding.Contains (x, y))
- {
- found = start.Padding;
- }
+ if (start.Margin.Contains (x, y))
+ {
+ found = start.Margin;
+ }
+ else if (start.Border.Contains (x, y))
+ {
+ found = start.Border;
+ }
+ else if (start.Padding.Contains (x, y))
+ {
+ found = start.Padding;
+ }
- Point boundsOffset = start.GetBoundsOffset ();
+ Point viewportOffset = start.GetViewportOffsetFromFrame ();
- if (found is { })
- {
- start = found;
- boundsOffset = found.Parent.Frame.Location;
- }
+ if (found is { })
+ {
+ start = found;
+ viewportOffset = found.Parent.Frame.Location;
+ }
- if (start.InternalSubviews is { Count: > 0 })
- {
- int startOffsetX = x - (start.Frame.X + boundsOffset.X);
- int startOffsetY = y - (start.Frame.Y + boundsOffset.Y);
+ int startOffsetX = x - (start.Frame.X + viewportOffset.X);
+ int startOffsetY = y - (start.Frame.Y + viewportOffset.Y);
+ View? subview = null;
for (int i = start.InternalSubviews.Count - 1; i >= 0; i--)
for (int i = start.InternalSubviews.Count - 1; i >= 0; i--)
{
{
- View nextStart = start.InternalSubviews [i];
-
- if (nextStart.Visible && nextStart.Contains (startOffsetX, startOffsetY))
+ /// The value that will be incremented or decremented.
+ /// </summary>
+ public T Value
+ {
+ get => _value;
+ set
+ {
+ if (_value.Equals (value))
+ {
+ return;
+ }
+
+ T oldValue = value;
+ StateEventArgs<T> args = new StateEventArgs<T> (_value, value);
+ ValueChanging?.Invoke (this, args);
+
+ if (args.Cancel)
+ {
+ return;
+ }
+
+ _value = value;
+ _number.Text = _value.ToString ();
+ ValueChanged?.Invoke (this, new (oldValue, _value));
+ }
+ }
+
+ /// <summary>
+ /// Fired when the value is about to change. Set <see cref="StateEventArgs{T}.Cancel"/> to true to prevent the change.
+ /// </summary>
+ [CanBeNull]
+ public event EventHandler<StateEventArgs<T>> ValueChanging;
+
+ /// <summary>
+ /// Fired when the value has changed.
+ /// </summary>
+ [CanBeNull]
+ public event EventHandler<StateEventArgs<T>> ValueChanged;
+
+ /// <summary>
+ /// The number of digits to display. The <see cref="View.Viewport"/> will be resized to fit this number of characters plus the buttons. The default is 3.
+[ScenarioMetadata ("Content Scrolling", "Demonstrates using View.Viewport and View.ContentSize to scroll content.")]
+[ScenarioCategory ("Layout")]
+[ScenarioCategory ("Drawing")]
+[ScenarioCategory ("Scrolling")]
+public class ContentScrolling : Scenario
+{
+ private ViewDiagnosticFlags _diagnosticFlags;
+
+ public class ScrollingDemoView : FrameView
+ {
+ public ScrollingDemoView ()
+ {
+ Width = Dim.Fill ();
+ Height = Dim.Fill ();
+ ColorScheme = Colors.ColorSchemes ["Base"];
+ Text = "Text (ScrollingDemoView.Text). This is long text.\nThe second line.\n3\n4\n5th line\nLine 6. This is a longer line that should wrap automatically.";
+ "This label is long. It should clip to the ContentArea if ClipContentOnly is set. This is a virtual scrolling demo. Use the arrow keys and/or mouse wheel to scroll the content."
- // TODO: Move this to another Scenario that specifically tests `Views` that have no subviews.
- //Top.Text = "Press CTRL-Q to Quit. This is the Text for the TopLevel View. TextAlignment.Centered was specified. It is intentionally very long to illustrate word wrap.\n" +
- // "<-- There is a new line here to show a hard line break. You should see this text bleed underneath the subviews, which start at Y = 3.";
- public void Contains (int frameX, int frameY, int marginThickness, int borderThickness, int paddingThinkcness, int testX, int testY, Type? expectedAdornmentType)
+ public void Contains (int frameX, int frameY, int marginThickness, int borderThickness, int paddingThickness, int testX, int testY, Type? expectedAdornmentType)
{
{
var view = new View ()
var view = new View ()
{
{
@@ -77,7 +77,7 @@ public class FindDeepestViewTests (ITestOutputHelper output)
};
};
view.Margin.Thickness = new Thickness (marginThickness);
view.Margin.Thickness = new Thickness (marginThickness);
view.Border.Thickness = new Thickness (borderThickness);
view.Border.Thickness = new Thickness (borderThickness);
- view.Padding.Thickness = new Thickness (paddingThinkcness);
+ view.Padding.Thickness = new Thickness (paddingThickness);
Type? containedType = null;
Type? containedType = null;
if (view.Contains (testX, testY))
if (view.Contains (testX, testY))
@@ -271,6 +271,71 @@ public class FindDeepestViewTests (ITestOutputHelper output)
Assert.Equal (expectedSubViewFound, found == subview);
Assert.Equal (expectedSubViewFound, found == subview);
}
}
+ // Test that FindDeepestView works if the start view has offset Viewport location
+ [Theory]
+ [InlineData (1, 0, 0, true)]
+ [InlineData (1, 1, 1, true)]
+ [InlineData (1, 2, 2, false)]
+
+ [InlineData (-1, 3, 3, true)]
+ [InlineData (-1, 2, 2, true)]
+ [InlineData (-1, 1, 1, false)]
+ [InlineData (-1, 0, 0, false)]
+ public void Returns_Correct_If_Start_Has_Offset_Viewport (int offset, int testX, int testY, bool expectedSubViewFound)