mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2026-01-01 16:59:35 +01:00
* Initial plan * Add comprehensive analysis of Line implementation status Co-authored-by: tig <585482+tig@users.noreply.github.com> * Complete Line implementation with documentation, example, and tests Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add PR summary documenting Line implementation completion Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add comprehensive completion report for Issue 4150 Co-authored-by: tig <585482+tig@users.noreply.github.com> * Fix Line rendering: use SuperView's LineCanvas instead of own Co-authored-by: tig <585482+tig@users.noreply.github.com> * Redesign Line to use Border instead of manual LineCanvas Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add Line.Style property to avoid BorderStyle conflict Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add SetWidth/SetHeight methods to preserve dimensions on Orientation change Co-authored-by: tig <585482+tig@users.noreply.github.com> * Implement CWP events for Width/Height properties; update Line to use events Co-authored-by: tig <585482+tig@users.noreply.github.com> * WIP: Updating Line. Cleaned up Layout tests. * Made Height/Width non-nullable * Add doWork stage to CWPPropertyHelper to execute between Changing and Changed events Co-authored-by: tig <585482+tig@users.noreply.github.com> * Move ViewLayoutEventTests to parallelizable tests without AutoInitShutdown Co-authored-by: tig <585482+tig@users.noreply.github.com> * Replace tracking fields with Length property for thread-safe Line implementation Co-authored-by: tig <585482+tig@users.noreply.github.com> * Fix orientation handling to preserve user-set dimensions in object initializers Co-authored-by: tig <585482+tig@users.noreply.github.com> * Simplify orientation handling with dimension swapping - all tests passing Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add Length backing field and fix object initializer dimension handling Co-authored-by: tig <585482+tig@users.noreply.github.com> * Use CWP OnChanging events to manage dimensions instead of OnChanged Co-authored-by: tig <585482+tig@users.noreply.github.com> * Move LineTests to parallelizable; simplify tests with GetAnchor; fix Length property Co-authored-by: tig <585482+tig@users.noreply.github.com> * Code cleanup. * Code cleanup. * Update Terminal.Gui/ViewBase/View.Layout.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Terminal.Gui/ViewBase/View.Layout.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Terminal.Gui/ViewBase/View.Layout.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Terminal.Gui/ViewBase/View.Layout.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fixed nullable warning in test * Removed PR files and updated copilot guidance * Reverted .gitignore change --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Tig <tig@users.noreply.github.com> Co-authored-by: tig <585482+tig@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -26,6 +26,7 @@ public static class CWPPropertyHelper
|
||||
/// <param name="newValue">The proposed new property value, which may be null for nullable types.</param>
|
||||
/// <param name="onChanging">The virtual method invoked before the change, returning true to cancel.</param>
|
||||
/// <param name="changingEvent">The pre-change event raised to allow modification or cancellation.</param>
|
||||
/// <param name="doWork">The action that performs the actual work of setting the property (e.g., updating backing field, calling related methods).</param>
|
||||
/// <param name="onChanged">The virtual method invoked after the change.</param>
|
||||
/// <param name="changedEvent">The post-change event raised to notify of the completed change.</param>
|
||||
/// <param name="finalValue">
|
||||
@@ -39,15 +40,15 @@ public static class CWPPropertyHelper
|
||||
/// </exception>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// string? current = null;
|
||||
/// string? current = _schemeName;
|
||||
/// string? proposed = "Base";
|
||||
/// Func<ValueChangingEventArgs<string?>, bool> onChanging = args => false;
|
||||
/// EventHandler<ValueChangingEventArgs<string?>>? changingEvent = null;
|
||||
/// Action<ValueChangedEventArgs<string?>>? onChanged = args =>
|
||||
/// Console.WriteLine($"SchemeName changed to {args.NewValue ?? "none"}.");
|
||||
/// EventHandler<ValueChangedEventArgs<string?>>? changedEvent = null;
|
||||
/// Func<ValueChangingEventArgs<string?>, bool> onChanging = OnSchemeNameChanging;
|
||||
/// EventHandler<ValueChangingEventArgs<string?>>? changingEvent = SchemeNameChanging;
|
||||
/// Action<string?> doWork = value => _schemeName = value;
|
||||
/// Action<ValueChangedEventArgs<string?>>? onChanged = OnSchemeNameChanged;
|
||||
/// EventHandler<ValueChangedEventArgs<string?>>? changedEvent = SchemeNameChanged;
|
||||
/// bool changed = CWPPropertyHelper.ChangeProperty(
|
||||
/// current, proposed, onChanging, changingEvent, onChanged, changedEvent, out string? final);
|
||||
/// current, proposed, onChanging, changingEvent, doWork, onChanged, changedEvent, out string? final);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static bool ChangeProperty<T> (
|
||||
@@ -55,6 +56,7 @@ public static class CWPPropertyHelper
|
||||
T newValue,
|
||||
Func<ValueChangingEventArgs<T>, bool> onChanging,
|
||||
EventHandler<ValueChangingEventArgs<T>>? changingEvent,
|
||||
Action<T> doWork,
|
||||
Action<ValueChangedEventArgs<T>>? onChanged,
|
||||
EventHandler<ValueChangedEventArgs<T>>? changedEvent,
|
||||
out T finalValue
|
||||
@@ -93,6 +95,10 @@ public static class CWPPropertyHelper
|
||||
}
|
||||
|
||||
finalValue = args.NewValue;
|
||||
|
||||
// Do the work (set backing field, update related properties, etc.) BEFORE raising Changed events
|
||||
doWork (finalValue);
|
||||
|
||||
ValueChangedEventArgs<T> changedArgs = new (currentValue, finalValue);
|
||||
onChanged?.Invoke (changedArgs);
|
||||
changedEvent?.Invoke (null, changedArgs);
|
||||
|
||||
@@ -93,7 +93,7 @@ public abstract record Dim : IEqualityOperators<Dim, Dim, bool>
|
||||
/// <summary>Creates an Absolute <see cref="Dim"/> from the specified integer value.</summary>
|
||||
/// <returns>The Absolute <see cref="Dim"/>.</returns>
|
||||
/// <param name="size">The value to convert to the <see cref="Dim"/>.</param>
|
||||
public static Dim? Absolute (int size) { return new DimAbsolute (size); }
|
||||
public static Dim Absolute (int size) { return new DimAbsolute (size); }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Dim"/> object that automatically sizes the view to fit all the view's Content, SubViews, and/or Text.
|
||||
@@ -119,7 +119,7 @@ public abstract record Dim : IEqualityOperators<Dim, Dim, bool>
|
||||
/// </param>
|
||||
/// <param name="minimumContentDim">The minimum dimension the View's ContentSize will be constrained to.</param>
|
||||
/// <param name="maximumContentDim">The maximum dimension the View's ContentSize will be fit to.</param>
|
||||
public static Dim? Auto (DimAutoStyle style = DimAutoStyle.Auto, Dim? minimumContentDim = null, Dim? maximumContentDim = null)
|
||||
public static Dim Auto (DimAutoStyle style = DimAutoStyle.Auto, Dim? minimumContentDim = null, Dim? maximumContentDim = null)
|
||||
{
|
||||
return new DimAuto (
|
||||
MinimumContentDim: minimumContentDim,
|
||||
@@ -131,14 +131,14 @@ public abstract record Dim : IEqualityOperators<Dim, Dim, bool>
|
||||
/// Creates a <see cref="Dim"/> object that fills the dimension, leaving no margin.
|
||||
/// </summary>
|
||||
/// <returns>The Fill dimension.</returns>
|
||||
public static Dim? Fill () { return new DimFill (0); }
|
||||
public static Dim Fill () { return new DimFill (0); }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Dim"/> object that fills the dimension, leaving the specified margin.
|
||||
/// </summary>
|
||||
/// <returns>The Fill dimension.</returns>
|
||||
/// <param name="margin">Margin to use.</param>
|
||||
public static Dim? Fill (Dim margin) { return new DimFill (margin); }
|
||||
public static Dim Fill (Dim margin) { return new DimFill (margin); }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a function <see cref="Dim"/> object that computes the dimension based on the passed view and by executing
|
||||
@@ -172,7 +172,7 @@ public abstract record Dim : IEqualityOperators<Dim, Dim, bool>
|
||||
/// };
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static Dim? Percent (int percent, DimPercentMode mode = DimPercentMode.ContentSize)
|
||||
public static Dim Percent (int percent, DimPercentMode mode = DimPercentMode.ContentSize)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative (percent, nameof (percent));
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#nullable enable
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Terminal.Gui.ViewBase;
|
||||
|
||||
public partial class View
|
||||
@@ -27,19 +25,15 @@ public partial class View
|
||||
get => _schemeName;
|
||||
set
|
||||
{
|
||||
bool changed = CWPPropertyHelper.ChangeProperty (
|
||||
_schemeName,
|
||||
value,
|
||||
OnSchemeNameChanging,
|
||||
SchemeNameChanging,
|
||||
OnSchemeNameChanged,
|
||||
SchemeNameChanged,
|
||||
out string? finalValue);
|
||||
|
||||
if (changed)
|
||||
{
|
||||
_schemeName = finalValue;
|
||||
}
|
||||
CWPPropertyHelper.ChangeProperty (
|
||||
_schemeName,
|
||||
value,
|
||||
OnSchemeNameChanging,
|
||||
SchemeNameChanging,
|
||||
newValue => _schemeName = newValue,
|
||||
OnSchemeNameChanged,
|
||||
SchemeNameChanged,
|
||||
out string? _);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,18 +42,13 @@ public partial class View
|
||||
/// </summary>
|
||||
/// <param name="args">The event arguments containing the current and proposed new scheme name.</param>
|
||||
/// <returns>True to cancel the change, false to proceed.</returns>
|
||||
protected virtual bool OnSchemeNameChanging (ValueChangingEventArgs<string?> args)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
protected virtual bool OnSchemeNameChanging (ValueChangingEventArgs<string?> args) { return false; }
|
||||
|
||||
/// <summary>
|
||||
/// Called after the <see cref="SchemeName"/> property changes, allowing subclasses to react to the change.
|
||||
/// </summary>
|
||||
/// <param name="args">The event arguments containing the old and new scheme name.</param>
|
||||
protected virtual void OnSchemeNameChanged (ValueChangedEventArgs<string?> args)
|
||||
{
|
||||
}
|
||||
protected virtual void OnSchemeNameChanged (ValueChangedEventArgs<string?> args) { }
|
||||
|
||||
/// <summary>
|
||||
/// Raised before the <see cref="SchemeName"/> property changes, allowing handlers to modify or cancel the change.
|
||||
@@ -115,7 +104,8 @@ public partial class View
|
||||
/// <returns>The resolved scheme, never null.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method uses the Cancellable Work Pattern (CWP) via <see cref="CWPWorkflowHelper.ExecuteWithResult{TResult}"/>
|
||||
/// This method uses the Cancellable Work Pattern (CWP) via
|
||||
/// <see cref="CWPWorkflowHelper.ExecuteWithResult{TResult}"/>
|
||||
/// to allow customization or cancellation of scheme resolution through the <see cref="OnGettingScheme"/> method
|
||||
/// and <see cref="GettingScheme"/> event.
|
||||
/// </para>
|
||||
@@ -135,13 +125,14 @@ public partial class View
|
||||
ResultEventArgs<Scheme?> args = new ();
|
||||
|
||||
return CWPWorkflowHelper.ExecuteWithResult (
|
||||
onMethod: args =>
|
||||
{
|
||||
bool cancelled = OnGettingScheme (out Scheme? newScheme);
|
||||
args.Result = newScheme;
|
||||
return cancelled;
|
||||
},
|
||||
eventHandler: GettingScheme,
|
||||
args =>
|
||||
{
|
||||
bool cancelled = OnGettingScheme (out Scheme? newScheme);
|
||||
args.Result = newScheme;
|
||||
|
||||
return cancelled;
|
||||
},
|
||||
GettingScheme,
|
||||
args,
|
||||
DefaultAction);
|
||||
|
||||
@@ -170,6 +161,7 @@ public partial class View
|
||||
protected virtual bool OnGettingScheme (out Scheme? scheme)
|
||||
{
|
||||
scheme = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -180,7 +172,6 @@ public partial class View
|
||||
/// </summary>
|
||||
public event EventHandler<ResultEventArgs<Scheme?>>? GettingScheme;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the scheme for the <see cref="View"/>, marking it as explicitly set.
|
||||
/// </summary>
|
||||
@@ -190,7 +181,8 @@ public partial class View
|
||||
/// <para>
|
||||
/// This method uses the Cancellable Work Pattern (CWP) via <see cref="CWPPropertyHelper.ChangeProperty{T}"/>
|
||||
/// to allow customization or cancellation of the scheme change through the <see cref="OnSettingScheme"/> method
|
||||
/// and <see cref="SchemeChanging"/> event. The <see cref="SchemeChanged"/> event is raised after a successful change.
|
||||
/// and <see cref="SchemeChanging"/> event. The <see cref="SchemeChanged"/> event is raised after a successful
|
||||
/// change.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If set to null, <see cref="HasScheme"/> will be false, and the view will inherit the scheme from its
|
||||
@@ -216,21 +208,15 @@ public partial class View
|
||||
/// </example>
|
||||
public bool SetScheme (Scheme? scheme)
|
||||
{
|
||||
bool changed = CWPPropertyHelper.ChangeProperty (
|
||||
_scheme,
|
||||
scheme,
|
||||
OnSettingScheme,
|
||||
SchemeChanging,
|
||||
OnSchemeChanged,
|
||||
SchemeChanged,
|
||||
out Scheme? finalValue);
|
||||
|
||||
if (changed)
|
||||
{
|
||||
_scheme = finalValue;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return CWPPropertyHelper.ChangeProperty (
|
||||
_scheme,
|
||||
scheme,
|
||||
OnSettingScheme,
|
||||
SchemeChanging,
|
||||
newValue => _scheme = newValue,
|
||||
OnSchemeChanged,
|
||||
SchemeChanged,
|
||||
out Scheme? _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -238,19 +224,13 @@ public partial class View
|
||||
/// </summary>
|
||||
/// <param name="args">The event arguments containing the current and proposed new scheme.</param>
|
||||
/// <returns>True to cancel the change, false to proceed.</returns>
|
||||
protected virtual bool OnSettingScheme (ValueChangingEventArgs<Scheme?> args)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
protected virtual bool OnSettingScheme (ValueChangingEventArgs<Scheme?> args) { return false; }
|
||||
|
||||
/// <summary>
|
||||
/// Called after the scheme is set, allowing subclasses to react to the change.
|
||||
/// </summary>
|
||||
/// <param name="args">The event arguments containing the old and new scheme.</param>
|
||||
protected virtual void OnSchemeChanged (ValueChangedEventArgs<Scheme?> args)
|
||||
{
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
protected virtual void OnSchemeChanged (ValueChangedEventArgs<Scheme?> args) { SetNeedsDraw (); }
|
||||
|
||||
/// <summary>
|
||||
/// Raised before the scheme is set, allowing handlers to modify or cancel the change.
|
||||
@@ -269,5 +249,4 @@ public partial class View
|
||||
/// <see cref="ValueChangedEventArgs{T}.NewValue"/>, which may be null.
|
||||
/// </remarks>
|
||||
public event EventHandler<ValueChangedEventArgs<Scheme?>>? SchemeChanged;
|
||||
|
||||
}
|
||||
|
||||
@@ -56,6 +56,11 @@ public partial class View // Layout APIs
|
||||
// This will set _frame, call SetsNeedsLayout, and raise OnViewportChanged/ViewportChanged
|
||||
if (SetFrame (value with { Width = Math.Max (value.Width, 0), Height = Math.Max (value.Height, 0) }))
|
||||
{
|
||||
// BUGBUG: We set the internal fields here to avoid recursion. However, this means that
|
||||
// BUGBUG: other logic in the property setters does not get executed. Specifically:
|
||||
// BUGBUG: - Reset TextFormatter
|
||||
// BUGBUG: - SetLayoutNeeded (not an issue as we explictly call Layout below)
|
||||
// BUGBUG: - If we add property change events for X/Y/Width/Height they will not be invoked
|
||||
// If Frame gets set, set all Pos/Dim to Absolute values.
|
||||
_x = _frame!.Value.X;
|
||||
_y = _frame!.Value.Y;
|
||||
@@ -279,7 +284,7 @@ public partial class View // Layout APIs
|
||||
}
|
||||
}
|
||||
|
||||
private Dim? _height = Dim.Absolute (0);
|
||||
private Dim _height = Dim.Absolute (0);
|
||||
|
||||
/// <summary>Gets or sets the height dimension of the view.</summary>
|
||||
/// <value>The <see cref="Dim"/> object representing the height of the view (the number of rows).</value>
|
||||
@@ -304,28 +309,67 @@ public partial class View // Layout APIs
|
||||
/// <para>
|
||||
/// Changing this property will cause <see cref="Frame"/> to be updated.
|
||||
/// </para>
|
||||
/// <para>The default value is <c>Dim.Sized (0)</c>.</para>
|
||||
/// <para>
|
||||
/// Setting this property raises pre- and post-change events via <see cref="CWPPropertyHelper"/>,
|
||||
/// allowing customization or cancellation of the change. The <see cref="HeightChanging"/> event
|
||||
/// is raised before the change, and <see cref="HeightChanged"/> is raised after.
|
||||
/// </para>
|
||||
/// <para>The default value is <c>Dim.Absolute (0)</c>.</para>
|
||||
/// </remarks>
|
||||
public Dim? Height
|
||||
/// <seealso cref="HeightChanging"/>
|
||||
/// <seealso cref="HeightChanged"/>
|
||||
public Dim Height
|
||||
{
|
||||
get => VerifyIsInitialized (_height, nameof (Height));
|
||||
set
|
||||
{
|
||||
if (Equals (_height, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
CWPPropertyHelper.ChangeProperty (
|
||||
_height,
|
||||
value,
|
||||
OnHeightChanging,
|
||||
HeightChanging,
|
||||
newValue =>
|
||||
{
|
||||
_height = newValue;
|
||||
|
||||
_height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null");
|
||||
|
||||
// Reset TextFormatter - Will be recalculated in SetTextFormatterSize
|
||||
TextFormatter.ConstrainToHeight = null;
|
||||
|
||||
PosDimSet ();
|
||||
// Reset TextFormatter - Will be recalculated in SetTextFormatterSize
|
||||
TextFormatter.ConstrainToHeight = null;
|
||||
PosDimSet ();
|
||||
},
|
||||
OnHeightChanged,
|
||||
HeightChanged,
|
||||
out Dim _);
|
||||
}
|
||||
}
|
||||
|
||||
private Dim? _width = Dim.Absolute (0);
|
||||
/// <summary>
|
||||
/// Called before the <see cref="Height"/> property changes, allowing subclasses to cancel or modify the change.
|
||||
/// </summary>
|
||||
/// <param name="args">The event arguments containing the current and proposed new height.</param>
|
||||
/// <returns>True to cancel the change, false to proceed.</returns>
|
||||
protected virtual bool OnHeightChanging (ValueChangingEventArgs<Dim> args) { return false; }
|
||||
|
||||
/// <summary>
|
||||
/// Called after the <see cref="Height"/> property changes, allowing subclasses to react to the change.
|
||||
/// </summary>
|
||||
/// <param name="args">The event arguments containing the old and new height.</param>
|
||||
protected virtual void OnHeightChanged (ValueChangedEventArgs<Dim> args) { }
|
||||
|
||||
/// <summary>
|
||||
/// Raised before the <see cref="Height"/> property changes, allowing handlers to modify or cancel the change.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Set <see cref="ValueChangingEventArgs{T}.Handled"/> to true to cancel the change or modify
|
||||
/// <see cref="ValueChangingEventArgs{T}.NewValue"/> to adjust the proposed value.
|
||||
/// </remarks>
|
||||
public event EventHandler<ValueChangingEventArgs<Dim>>? HeightChanging;
|
||||
|
||||
/// <summary>
|
||||
/// Raised after the <see cref="Height"/> property changes, allowing handlers to react to the change.
|
||||
/// </summary>
|
||||
public event EventHandler<ValueChangedEventArgs<Dim>>? HeightChanged;
|
||||
|
||||
private Dim _width = Dim.Absolute (0);
|
||||
|
||||
/// <summary>Gets or sets the width dimension of the view.</summary>
|
||||
/// <value>The <see cref="Dim"/> object representing the width of the view (the number of columns).</value>
|
||||
@@ -351,26 +395,66 @@ public partial class View // Layout APIs
|
||||
/// <para>
|
||||
/// Changing this property will cause <see cref="Frame"/> to be updated.
|
||||
/// </para>
|
||||
/// <para>The default value is <c>Dim.Sized (0)</c>.</para>
|
||||
/// <para>
|
||||
/// Setting this property raises pre- and post-change events via <see cref="CWPPropertyHelper"/>,
|
||||
/// allowing customization or cancellation of the change. The <see cref="WidthChanging"/> event
|
||||
/// is raised before the change, and <see cref="WidthChanged"/> is raised after.
|
||||
/// </para>
|
||||
/// <para>The default value is <c>Dim.Absolute (0)</c>.</para>
|
||||
/// </remarks>
|
||||
public Dim? Width
|
||||
/// <seealso cref="WidthChanging"/>
|
||||
/// <seealso cref="WidthChanged"/>
|
||||
public Dim Width
|
||||
{
|
||||
get => VerifyIsInitialized (_width, nameof (Width));
|
||||
set
|
||||
{
|
||||
if (Equals (_width, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
CWPPropertyHelper.ChangeProperty (
|
||||
_width,
|
||||
value,
|
||||
OnWidthChanging,
|
||||
WidthChanging,
|
||||
newValue =>
|
||||
{
|
||||
_width = newValue;
|
||||
|
||||
_width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null");
|
||||
|
||||
// Reset TextFormatter - Will be recalculated in SetTextFormatterSize
|
||||
TextFormatter.ConstrainToWidth = null;
|
||||
PosDimSet ();
|
||||
// Reset TextFormatter - Will be recalculated in SetTextFormatterSize
|
||||
TextFormatter.ConstrainToWidth = null;
|
||||
PosDimSet ();
|
||||
},
|
||||
OnWidthChanged,
|
||||
WidthChanged,
|
||||
out Dim _);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before the <see cref="Width"/> property changes, allowing subclasses to cancel or modify the change.
|
||||
/// </summary>
|
||||
/// <param name="args">The event arguments containing the current and proposed new width.</param>
|
||||
/// <returns>True to cancel the change, false to proceed.</returns>
|
||||
protected virtual bool OnWidthChanging (ValueChangingEventArgs<Dim> args) { return false; }
|
||||
|
||||
/// <summary>
|
||||
/// Called after the <see cref="Width"/> property changes, allowing subclasses to react to the change.
|
||||
/// </summary>
|
||||
/// <param name="args">The event arguments containing the old and new width.</param>
|
||||
protected virtual void OnWidthChanged (ValueChangedEventArgs<Dim> args) { }
|
||||
|
||||
/// <summary>
|
||||
/// Raised before the <see cref="Width"/> property changes, allowing handlers to modify or cancel the change.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Set <see cref="ValueChangingEventArgs{T}.Handled"/> to true to cancel the change or modify
|
||||
/// <see cref="ValueChangingEventArgs{T}.NewValue"/> to adjust the proposed value.
|
||||
/// </remarks>
|
||||
public event EventHandler<ValueChangingEventArgs<Dim>>? WidthChanging;
|
||||
|
||||
/// <summary>
|
||||
/// Raised after the <see cref="Width"/> property changes, allowing handlers to react to the change.
|
||||
/// </summary>
|
||||
public event EventHandler<ValueChangedEventArgs<Dim>>? WidthChanged;
|
||||
|
||||
#endregion Frame/Position/Dimension
|
||||
|
||||
#region Core Layout API
|
||||
@@ -474,8 +558,7 @@ public partial class View // Layout APIs
|
||||
{
|
||||
Debug.Assert (_x is { });
|
||||
Debug.Assert (_y is { });
|
||||
Debug.Assert (_width is { });
|
||||
Debug.Assert (_height is { });
|
||||
|
||||
|
||||
CheckDimAuto ();
|
||||
|
||||
@@ -532,10 +615,15 @@ public partial class View // Layout APIs
|
||||
|
||||
if (Frame != newFrame)
|
||||
{
|
||||
// Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height
|
||||
// This will set _frame, call SetsNeedsLayout, and raise OnViewportChanged/ViewportChanged
|
||||
// Set the frame. Do NOT use `Frame = newFrame` as it overwrites X, Y, Width, and Height
|
||||
// SetFrame will set _frame, call SetsNeedsLayout, and raise OnViewportChanged/ViewportChanged
|
||||
SetFrame (newFrame);
|
||||
|
||||
// BUGBUG: We set the internal fields here to avoid recursion. However, this means that
|
||||
// BUGBUG: other logic in the property setters does not get executed. Specifically:
|
||||
// BUGBUG: - Reset TextFormatter
|
||||
// BUGBUG: - SetLayoutNeeded (not an issue as we explicitly call Layout below)
|
||||
// BUGBUG: - If we add property change events for X/Y/Width/Height they will not be invoked
|
||||
if (_x is PosAbsolute)
|
||||
{
|
||||
_x = Frame.X;
|
||||
@@ -1152,13 +1240,15 @@ public partial class View // Layout APIs
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Views that are under <paramref name="screenLocation"/>, including Adornments. The list is ordered by depth. The
|
||||
/// Gets the Views that are under <paramref name="screenLocation"/>, including Adornments. The list is ordered by
|
||||
/// depth. The
|
||||
/// deepest
|
||||
/// View is at the end of the list (the top most View is at element 0).
|
||||
/// </summary>
|
||||
/// <param name="screenLocation">Screen-relative location.</param>
|
||||
/// <param name="excludeViewportSettingsFlags">
|
||||
/// If set, excludes Views that have the <see cref="ViewportSettingsFlags.Transparent"/> or <see cref="ViewportSettingsFlags.TransparentMouse"/>
|
||||
/// If set, excludes Views that have the <see cref="ViewportSettingsFlags.Transparent"/> or
|
||||
/// <see cref="ViewportSettingsFlags.TransparentMouse"/>
|
||||
/// flags set in their ViewportSettings.
|
||||
/// </param>
|
||||
public static List<View?> GetViewsUnderLocation (in Point screenLocation, ViewportSettingsFlags excludeViewportSettingsFlags)
|
||||
@@ -1219,21 +1309,24 @@ public partial class View // Layout APIs
|
||||
|
||||
/// <summary>
|
||||
/// INTERNAL: Helper for GetViewsUnderLocation that starts from a given root view.
|
||||
/// Gets the Views that are under <paramref name="screenLocation"/>, including Adornments. The list is ordered by depth. The
|
||||
/// Gets the Views that are under <paramref name="screenLocation"/>, including Adornments. The list is ordered by
|
||||
/// depth. The
|
||||
/// deepest
|
||||
/// View is at the end of the list (the topmost View is at element 0).
|
||||
/// </summary>
|
||||
/// <param name="root"></param>
|
||||
/// <param name="screenLocation">Screen-relative location.</param>
|
||||
/// <param name="excludeViewportSettingsFlags">
|
||||
/// If set, excludes Views that have the <see cref="ViewportSettingsFlags.Transparent"/> or <see cref="ViewportSettingsFlags.TransparentMouse"/>
|
||||
/// If set, excludes Views that have the <see cref="ViewportSettingsFlags.Transparent"/> or
|
||||
/// <see cref="ViewportSettingsFlags.TransparentMouse"/>
|
||||
/// flags set in their ViewportSettings.
|
||||
/// </param>
|
||||
internal static List<View?> GetViewsUnderLocation (View root, in Point screenLocation, ViewportSettingsFlags excludeViewportSettingsFlags)
|
||||
{
|
||||
List<View?> viewsUnderLocation = GetViewsAtLocation (root, screenLocation);
|
||||
|
||||
if (!excludeViewportSettingsFlags.HasFlag (ViewportSettingsFlags.Transparent) && !excludeViewportSettingsFlags.HasFlag (ViewportSettingsFlags.TransparentMouse))
|
||||
if (!excludeViewportSettingsFlags.HasFlag (ViewportSettingsFlags.Transparent)
|
||||
&& !excludeViewportSettingsFlags.HasFlag (ViewportSettingsFlags.TransparentMouse))
|
||||
{
|
||||
// Only filter views if we are excluding transparent views.
|
||||
return viewsUnderLocation;
|
||||
@@ -1241,8 +1334,7 @@ public partial class View // Layout APIs
|
||||
|
||||
// Remove all views that have an adornment with ViewportSettings.TransparentMouse; they are in the list
|
||||
// because the point was in their adornment, and if the adornment is transparent, they should be removed.
|
||||
viewsUnderLocation.RemoveAll (
|
||||
v =>
|
||||
viewsUnderLocation.RemoveAll (v =>
|
||||
{
|
||||
if (v is null or Adornment)
|
||||
{
|
||||
@@ -1277,6 +1369,7 @@ public partial class View // Layout APIs
|
||||
|
||||
return viewsUnderLocation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// INTERNAL: Gets ALL Views (Subviews and Adornments) in the of <see cref="SuperView"/> hierarchcy that are at
|
||||
/// <paramref name="location"/>,
|
||||
@@ -1320,6 +1413,7 @@ public partial class View // Layout APIs
|
||||
for (int i = currentView.InternalSubViews.Count - 1; i >= 0; i--)
|
||||
{
|
||||
View subview = currentView.InternalSubViews [i];
|
||||
|
||||
if (subview.Visible && subview.FrameToScreen ().Contains (location))
|
||||
{
|
||||
viewsToProcess.Push (subview);
|
||||
@@ -1350,7 +1444,7 @@ public partial class View // Layout APIs
|
||||
}
|
||||
|
||||
// Diagnostics to highlight when Width or Height is read before the view has been initialized
|
||||
private Dim? VerifyIsInitialized (Dim? dim, string member)
|
||||
private Dim VerifyIsInitialized (Dim dim, string member)
|
||||
{
|
||||
//#if DEBUG
|
||||
// if (dim.ReferencesOtherViews () && !IsInitialized)
|
||||
|
||||
@@ -38,9 +38,10 @@ public partial class View : IDisposable, ISupportInitializeNotification
|
||||
|
||||
#if DEBUG_IDISPOSABLE
|
||||
WasDisposed = true;
|
||||
|
||||
// Safely remove any disposed views from the Instances list
|
||||
List<View> itemsToKeep = Instances.Where (view => !view.WasDisposed).ToList ();
|
||||
Instances = new ConcurrentBag<View> (itemsToKeep);
|
||||
Instances = new (itemsToKeep);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -108,9 +109,11 @@ public partial class View : IDisposable, ISupportInitializeNotification
|
||||
/// <remarks>The id should be unique across all Views that share a SuperView.</remarks>
|
||||
public string Id { get; set; } = "";
|
||||
|
||||
private IConsoleDriver? _driver = null;
|
||||
private IConsoleDriver? _driver;
|
||||
|
||||
/// <summary>
|
||||
/// INTERNAL: Use <see cref="Application.Driver"/> instead. Points to the current driver in use by the view, it is a convenience property for simplifying the development
|
||||
/// INTERNAL: Use <see cref="Application.Driver"/> instead. Points to the current driver in use by the view, it is a
|
||||
/// convenience property for simplifying the development
|
||||
/// of new views.
|
||||
/// </summary>
|
||||
internal IConsoleDriver? Driver
|
||||
@@ -121,6 +124,7 @@ public partial class View : IDisposable, ISupportInitializeNotification
|
||||
{
|
||||
return _driver;
|
||||
}
|
||||
|
||||
return Application.Driver;
|
||||
}
|
||||
set => _driver = value;
|
||||
@@ -345,6 +349,7 @@ public partial class View : IDisposable, ISupportInitializeNotification
|
||||
{
|
||||
// BUGBUG: Ideally we'd reset _previouslyFocused to the first focusable subview
|
||||
_previouslyFocused = SubViews.FirstOrDefault (v => v.CanFocus);
|
||||
|
||||
if (HasFocus)
|
||||
{
|
||||
HasFocus = false;
|
||||
@@ -449,10 +454,7 @@ public partial class View : IDisposable, ISupportInitializeNotification
|
||||
/// <value>The title.</value>
|
||||
public string Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return _title;
|
||||
}
|
||||
get { return _title; }
|
||||
set
|
||||
{
|
||||
#if DEBUG_IDISPOSABLE
|
||||
@@ -530,7 +532,6 @@ public partial class View : IDisposable, ISupportInitializeNotification
|
||||
/// </summary>
|
||||
public static bool EnableDebugIDisposableAsserts { get; set; } = true;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether <see cref="View.Dispose"/> was called on this view or not.
|
||||
/// For debug purposes to verify objects are being disposed properly.
|
||||
|
||||
@@ -1,33 +1,155 @@
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Terminal.Gui.Views;
|
||||
|
||||
/// <summary>
|
||||
/// Draws a single line using the <see cref="LineStyle"/> specified by <see cref="View.BorderStyle"/>.
|
||||
/// Draws a single line using the <see cref="LineStyle"/> specified by <see cref="Line.Style"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="Line"/> is a <see cref="View"/> that renders a single horizontal or vertical line
|
||||
/// using the <see cref="LineCanvas"/> system. Unlike <see cref="LineView"/>, which directly renders
|
||||
/// runes, <see cref="Line"/> integrates with the LineCanvas to enable proper box-drawing character
|
||||
/// selection and line intersection handling.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The line's appearance is controlled by the <see cref="Style"/> property, which supports
|
||||
/// various line styles including Single, Double, Heavy, Rounded, Dashed, and Dotted.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Use the <see cref="Length"/> property to control the extent of the line regardless of its
|
||||
/// <see cref="Orientation"/>. For horizontal lines, Length controls Width; for vertical lines,
|
||||
/// it controls Height. The perpendicular dimension is always 1.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When multiple <see cref="Line"/> instances or other LineCanvas-aware views (like <see cref="Border"/>)
|
||||
/// intersect, the LineCanvas automatically selects the appropriate box-drawing characters for corners,
|
||||
/// T-junctions, and crosses.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="Line"/> sets <see cref="View.SuperViewRendersLineCanvas"/> to <see langword="true"/>,
|
||||
/// meaning its parent view is responsible for rendering the line. This allows for proper intersection
|
||||
/// handling when multiple views contribute lines to the same canvas.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Create a horizontal line
|
||||
/// var hLine = new Line { Y = 5 };
|
||||
///
|
||||
/// // Create a vertical line with specific length
|
||||
/// var vLine = new Line { X = 10, Orientation = Orientation.Vertical, Length = 15 };
|
||||
///
|
||||
/// // Create a double-line style horizontal line
|
||||
/// var doubleLine = new Line { Y = 10, Style = LineStyle.Double };
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class Line : View, IOrientation
|
||||
{
|
||||
private readonly OrientationHelper _orientationHelper;
|
||||
private LineStyle _style = LineStyle.Single;
|
||||
private Dim _length = Dim.Fill ();
|
||||
|
||||
/// <summary>Constructs a Line object.</summary>
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the <see cref="Line"/> class with horizontal orientation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, a horizontal line fills the available width and has a height of 1.
|
||||
/// The line style defaults to <see cref="LineStyle.Single"/>.
|
||||
/// </remarks>
|
||||
public Line ()
|
||||
{
|
||||
CanFocus = false;
|
||||
|
||||
base.SuperViewRendersLineCanvas = true;
|
||||
|
||||
_orientationHelper = new (this);
|
||||
_orientationHelper.Orientation = Orientation.Horizontal;
|
||||
OnOrientationChanged(Orientation);
|
||||
|
||||
// Set default dimensions for horizontal orientation
|
||||
// Set Height first (this will update _length, but we'll override it next)
|
||||
Height = 1;
|
||||
|
||||
// Now set Width and _length to Fill
|
||||
_length = Dim.Fill ();
|
||||
Width = _length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the length of the line along its orientation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is the "source of truth" for the line's primary dimension.
|
||||
/// For a horizontal line, Length controls Width.
|
||||
/// For a vertical line, Length controls Height.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When Width or Height is set directly, Length is updated to match the primary dimension.
|
||||
/// When Orientation changes, the appropriate dimension is set to Length and the perpendicular
|
||||
/// dimension is set to 1.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This property provides a cleaner API for controlling the line's extent
|
||||
/// without needing to know whether to use Width or Height.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public Dim Length
|
||||
{
|
||||
get => Orientation == Orientation.Horizontal ? Width : Height;
|
||||
set
|
||||
{
|
||||
_length = value;
|
||||
|
||||
// Update the appropriate dimension based on current orientation
|
||||
if (Orientation == Orientation.Horizontal)
|
||||
{
|
||||
Width = _length;
|
||||
}
|
||||
else
|
||||
{
|
||||
Height = _length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style of the line. This controls the visual appearance of the line.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Supports various line styles including Single, Double, Heavy, Rounded, Dashed, and Dotted.
|
||||
/// Note: This is separate from <see cref="View.BorderStyle"/> to avoid conflicts with the View's Border.
|
||||
/// </remarks>
|
||||
public LineStyle Style
|
||||
{
|
||||
get => _style;
|
||||
set
|
||||
{
|
||||
if (_style != value)
|
||||
{
|
||||
_style = value;
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IOrientation members
|
||||
|
||||
/// <summary>
|
||||
/// The direction of the line. If you change this you will need to manually update the Width/Height of the
|
||||
/// control to cover a relevant area based on the new direction.
|
||||
/// The direction of the line.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When orientation changes, the appropriate dimension is set to <see cref="Length"/>
|
||||
/// and the perpendicular dimension is set to 1.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// For object initializers where dimensions are set before orientation:
|
||||
/// <code>new Line { Height = 9, Orientation = Orientation.Vertical }</code>
|
||||
/// Setting Height=9 updates Length to 9 (since default orientation is Horizontal and Height is perpendicular).
|
||||
/// Then when Orientation is set to Vertical, Height is set to Length (9) and Width is set to 1,
|
||||
/// resulting in the expected Width=1, Height=9.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public Orientation Orientation
|
||||
{
|
||||
get => _orientationHelper.Orientation;
|
||||
@@ -36,48 +158,83 @@ public class Line : View, IOrientation
|
||||
|
||||
#pragma warning disable CS0067 // The event is never used
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
|
||||
public event EventHandler<CancelEventArgs<Orientation>>? OrientationChanging;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<EventArgs<Orientation>> OrientationChanged;
|
||||
public event EventHandler<EventArgs<Orientation>>? OrientationChanged;
|
||||
#pragma warning restore CS0067 // The event is never used
|
||||
|
||||
/// <summary>Called when <see cref="Orientation"/> has changed.</summary>
|
||||
/// <param name="newOrientation"></param>
|
||||
/// <summary>
|
||||
/// Called when <see cref="Orientation"/> has changed.
|
||||
/// </summary>
|
||||
/// <param name="newOrientation">The new orientation value.</param>
|
||||
public void OnOrientationChanged (Orientation newOrientation)
|
||||
{
|
||||
|
||||
switch (newOrientation)
|
||||
// Set dimensions based on new orientation:
|
||||
// - Primary dimension (along orientation) = Length
|
||||
// - Perpendicular dimension = 1
|
||||
if (newOrientation == Orientation.Horizontal)
|
||||
{
|
||||
case Orientation.Horizontal:
|
||||
Height = 1;
|
||||
Width = Dim.Fill ();
|
||||
|
||||
break;
|
||||
case Orientation.Vertical:
|
||||
Width = 1;
|
||||
Height = Dim.Fill ();
|
||||
|
||||
break;
|
||||
|
||||
Width = _length;
|
||||
Height = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Height = _length;
|
||||
Width = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool OnWidthChanging (ValueChangingEventArgs<Dim> e)
|
||||
{
|
||||
// If horizontal, allow width changes and update _length
|
||||
_length = e.NewValue;
|
||||
if (Orientation == Orientation.Horizontal)
|
||||
{
|
||||
return base.OnWidthChanging (e);
|
||||
}
|
||||
|
||||
// If vertical, keep width at 1 (don't allow changes to perpendicular dimension)
|
||||
e.NewValue = 1;
|
||||
|
||||
return base.OnWidthChanging (e);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool OnHeightChanging (ValueChangingEventArgs<Dim> e)
|
||||
{
|
||||
// If vertical, allow height changes and update _length
|
||||
_length = e.NewValue;
|
||||
if (Orientation == Orientation.Vertical)
|
||||
{
|
||||
return base.OnHeightChanging (e);
|
||||
}
|
||||
|
||||
e.NewValue = 1;
|
||||
|
||||
return base.OnHeightChanging (e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// This method adds the line to the LineCanvas for rendering.
|
||||
/// The actual rendering is performed by the parent view through <see cref="View.RenderLineCanvas"/>.
|
||||
/// </remarks>
|
||||
protected override bool OnDrawingContent ()
|
||||
{
|
||||
Point pos = ViewportToScreen (Viewport).Location;
|
||||
int length = Orientation == Orientation.Horizontal ? Frame.Width : Frame.Height;
|
||||
|
||||
LineCanvas?.AddLine (
|
||||
pos,
|
||||
length,
|
||||
Orientation,
|
||||
BorderStyle
|
||||
);
|
||||
LineCanvas.AddLine (
|
||||
pos,
|
||||
length,
|
||||
Orientation,
|
||||
Style
|
||||
);
|
||||
|
||||
//SuperView?.SetNeedsDraw ();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user