Skip to content

Commit fc9c40c

Browse files
CopilottigCopilot
authored
Fixes gui-cs#4150 - Finish implementing Line View (gui-cs#4260)
* 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>
1 parent 1822216 commit fc9c40c

File tree

16 files changed

+1249
-255
lines changed

16 files changed

+1249
-255
lines changed

AGENTS.md

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,13 @@ This file provides instructions for GitHub Copilot when working with the Termina
102102
- Many existing unit tests are obtuse and not really unit tests. Anytime new tests are added or updated, strive to refactor the tests into more granular tests where each test covers the smallest area possible.
103103
- Many existing unit tests in the `./Tests/UnitTests` project incorrectly require `Application.Init` and use `[AutoInitShutdown]`. Anytime new tests are added or updated, strive to remove these dependencies and make the tests parallelizable. This means not taking any dependency on static objects like `Application` and `ConfigurationManager`.
104104

105-
## Pull Request Checklist
106-
107-
Before submitting a PR, ensure:
108-
- [ ] PR title: "Fixes #issue. Terse description."
109-
- [ ] Code follows style guidelines (`.editorconfig`)
110-
- [ ] Code follows design guidelines (`CONTRIBUTING.md`)
111-
- [ ] Ran `dotnet test` and all tests pass
112-
- [ ] Added/updated XML API documentation (`///` comments)
113-
- [ ] No new warnings generated
114-
- [ ] Checked for grammar/spelling errors
115-
- [ ] Conducted basic QA testing
116-
- [ ] Added/updated UICatalog scenario if applicable
105+
## Pull Request Guidelines
106+
107+
- Titles should be of the form "Fixes #issue. Terse description."
108+
- If the PR addresses multiple issues, use "Fixes #issue1, #issue2. Terse description."
109+
- First comment should include "- Fixes #issue" for each issue addressed. If an issue is only partially addressed, use "Partially addresses #issue".
110+
- First comment should include a thorough description of the change and any impact.
111+
- Put temporary .md files in `/docfx/docs/drafts/` and remove before merging.
117112

118113
## Building and Running
119114

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
namespace UICatalog.Scenarios;
2+
3+
[ScenarioMetadata ("Line", "Demonstrates the Line view with LineCanvas integration.")]
4+
[ScenarioCategory ("Controls")]
5+
[ScenarioCategory ("Drawing")]
6+
[ScenarioCategory ("Adornments")]
7+
public class LineExample : Scenario
8+
{
9+
public override void Main ()
10+
{
11+
Application.Init ();
12+
13+
var app = new Window
14+
{
15+
Title = GetQuitKeyAndName ()
16+
};
17+
18+
// Section 1: Basic Lines
19+
var basicLabel = new Label
20+
{
21+
X = 0,
22+
Y = 0,
23+
Text = "Basic Lines:"
24+
};
25+
app.Add (basicLabel);
26+
27+
// Horizontal line
28+
var hLine = new Line
29+
{
30+
X = 0,
31+
Y = 1,
32+
Width = 30
33+
};
34+
app.Add (hLine);
35+
36+
// Vertical line
37+
var vLine = new Line
38+
{
39+
X = 32,
40+
Y = 0,
41+
Height = 10,
42+
Orientation = Orientation.Vertical
43+
};
44+
app.Add (vLine);
45+
46+
// Section 2: Different Line Styles
47+
var stylesLabel = new Label
48+
{
49+
X = 0,
50+
Y = 3,
51+
Text = "Line Styles:"
52+
};
53+
app.Add (stylesLabel);
54+
55+
(LineStyle, string) [] styles = new []
56+
{
57+
(LineStyle.Single, "Single"),
58+
(LineStyle.Double, "Double"),
59+
(LineStyle.Heavy, "Heavy"),
60+
(LineStyle.Rounded, "Rounded"),
61+
(LineStyle.Dashed, "Dashed"),
62+
(LineStyle.Dotted, "Dotted")
63+
};
64+
65+
var yPos = 4;
66+
67+
foreach ((LineStyle style, string name) in styles)
68+
{
69+
app.Add (new Label { X = 0, Y = yPos, Width = 15, Text = name + ":" });
70+
app.Add (new Line { X = 16, Y = yPos, Width = 14, Style = style });
71+
yPos++;
72+
}
73+
74+
// Section 3: Line Intersections
75+
var intersectionLabel = new Label
76+
{
77+
X = 35,
78+
Y = 3,
79+
Text = "Line Intersections:"
80+
};
81+
app.Add (intersectionLabel);
82+
83+
// Create a grid of intersecting lines
84+
var gridX = 35;
85+
var gridY = 5;
86+
87+
// Horizontal lines in the grid
88+
for (var i = 0; i < 5; i++)
89+
{
90+
app.Add (
91+
new Line
92+
{
93+
X = gridX,
94+
Y = gridY + i * 2,
95+
Width = 21,
96+
Style = LineStyle.Single
97+
});
98+
}
99+
100+
// Vertical lines in the grid
101+
for (var i = 0; i < 5; i++)
102+
{
103+
app.Add (
104+
new Line
105+
{
106+
X = gridX + i * 5,
107+
Y = gridY,
108+
Height = 9,
109+
Orientation = Orientation.Vertical,
110+
Style = LineStyle.Single
111+
});
112+
}
113+
114+
// Section 4: Mixed Styles (shows how LineCanvas handles different line styles)
115+
var mixedLabel = new Label
116+
{
117+
X = 60,
118+
Y = 3,
119+
Text = "Mixed Style Intersections:"
120+
};
121+
app.Add (mixedLabel);
122+
123+
// Double horizontal
124+
app.Add (
125+
new Line
126+
{
127+
X = 60,
128+
Y = 5,
129+
Width = 20,
130+
Style = LineStyle.Double
131+
});
132+
133+
// Single vertical through double horizontal
134+
app.Add (
135+
new Line
136+
{
137+
X = 70,
138+
Y = 4,
139+
Height = 3,
140+
Orientation = Orientation.Vertical,
141+
Style = LineStyle.Single
142+
});
143+
144+
// Heavy horizontal
145+
app.Add (
146+
new Line
147+
{
148+
X = 60,
149+
Y = 8,
150+
Width = 20,
151+
Style = LineStyle.Heavy
152+
});
153+
154+
// Single vertical through heavy horizontal
155+
app.Add (
156+
new Line
157+
{
158+
X = 70,
159+
Y = 7,
160+
Height = 3,
161+
Orientation = Orientation.Vertical,
162+
Style = LineStyle.Single
163+
});
164+
165+
// Section 5: Box Example (showing borders and lines working together)
166+
var boxLabel = new Label
167+
{
168+
X = 0,
169+
Y = 12,
170+
Text = "Lines with Borders:"
171+
};
172+
app.Add (boxLabel);
173+
174+
var framedView = new FrameView
175+
{
176+
Title = "Frame",
177+
X = 0,
178+
Y = 13,
179+
Width = 30,
180+
Height = 8,
181+
BorderStyle = LineStyle.Single
182+
};
183+
184+
// Add a cross inside the frame
185+
framedView.Add (
186+
new Line
187+
{
188+
X = 0,
189+
Y = 3,
190+
Width = Dim.Fill (),
191+
Style = LineStyle.Single
192+
});
193+
194+
framedView.Add (
195+
new Line
196+
{
197+
X = 14,
198+
Y = 0,
199+
Height = Dim.Fill (),
200+
Orientation = Orientation.Vertical,
201+
Style = LineStyle.Single
202+
});
203+
204+
app.Add (framedView);
205+
206+
// Section 6: Comparison with LineView
207+
var comparisonLabel = new Label
208+
{
209+
X = 35,
210+
Y = 15,
211+
Text = "Line vs LineView Comparison:"
212+
};
213+
app.Add (comparisonLabel);
214+
215+
app.Add (new Label { X = 35, Y = 16, Text = "Line (uses LineCanvas):" });
216+
app.Add (new Line { X = 35, Y = 17, Width = 20, Style = LineStyle.Single });
217+
218+
app.Add (new Label { X = 35, Y = 18, Text = "LineView (direct render):" });
219+
app.Add (new LineView { X = 35, Y = 19, Width = 20 });
220+
221+
// Add help text
222+
var helpLabel = new Label
223+
{
224+
X = Pos.Center (),
225+
Y = Pos.AnchorEnd (1),
226+
Text = "Line integrates with LineCanvas for automatic intersection handling"
227+
};
228+
app.Add (helpLabel);
229+
230+
Application.Run (app);
231+
app.Dispose ();
232+
Application.Shutdown ();
233+
}
234+
}

Terminal.Gui/App/CWP/CWPPropertyHelper.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public static class CWPPropertyHelper
2626
/// <param name="newValue">The proposed new property value, which may be null for nullable types.</param>
2727
/// <param name="onChanging">The virtual method invoked before the change, returning true to cancel.</param>
2828
/// <param name="changingEvent">The pre-change event raised to allow modification or cancellation.</param>
29+
/// <param name="doWork">The action that performs the actual work of setting the property (e.g., updating backing field, calling related methods).</param>
2930
/// <param name="onChanged">The virtual method invoked after the change.</param>
3031
/// <param name="changedEvent">The post-change event raised to notify of the completed change.</param>
3132
/// <param name="finalValue">
@@ -39,22 +40,23 @@ public static class CWPPropertyHelper
3940
/// </exception>
4041
/// <example>
4142
/// <code>
42-
/// string? current = null;
43+
/// string? current = _schemeName;
4344
/// string? proposed = "Base";
44-
/// Func&lt;ValueChangingEventArgs&lt;string?&gt;, bool&gt; onChanging = args =&gt; false;
45-
/// EventHandler&lt;ValueChangingEventArgs&lt;string?&gt;&gt;? changingEvent = null;
46-
/// Action&lt;ValueChangedEventArgs&lt;string?&gt;&gt;? onChanged = args =&gt;
47-
/// Console.WriteLine($"SchemeName changed to {args.NewValue ?? "none"}.");
48-
/// EventHandler&lt;ValueChangedEventArgs&lt;string?&gt;&gt;? changedEvent = null;
45+
/// Func&lt;ValueChangingEventArgs&lt;string?&gt;, bool&gt; onChanging = OnSchemeNameChanging;
46+
/// EventHandler&lt;ValueChangingEventArgs&lt;string?&gt;&gt;? changingEvent = SchemeNameChanging;
47+
/// Action&lt;string?&gt; doWork = value => _schemeName = value;
48+
/// Action&lt;ValueChangedEventArgs&lt;string?&gt;&gt;? onChanged = OnSchemeNameChanged;
49+
/// EventHandler&lt;ValueChangedEventArgs&lt;string?&gt;&gt;? changedEvent = SchemeNameChanged;
4950
/// bool changed = CWPPropertyHelper.ChangeProperty(
50-
/// current, proposed, onChanging, changingEvent, onChanged, changedEvent, out string? final);
51+
/// current, proposed, onChanging, changingEvent, doWork, onChanged, changedEvent, out string? final);
5152
/// </code>
5253
/// </example>
5354
public static bool ChangeProperty<T> (
5455
T currentValue,
5556
T newValue,
5657
Func<ValueChangingEventArgs<T>, bool> onChanging,
5758
EventHandler<ValueChangingEventArgs<T>>? changingEvent,
59+
Action<T> doWork,
5860
Action<ValueChangedEventArgs<T>>? onChanged,
5961
EventHandler<ValueChangedEventArgs<T>>? changedEvent,
6062
out T finalValue
@@ -93,6 +95,10 @@ out T finalValue
9395
}
9496

9597
finalValue = args.NewValue;
98+
99+
// Do the work (set backing field, update related properties, etc.) BEFORE raising Changed events
100+
doWork (finalValue);
101+
96102
ValueChangedEventArgs<T> changedArgs = new (currentValue, finalValue);
97103
onChanged?.Invoke (changedArgs);
98104
changedEvent?.Invoke (null, changedArgs);

Terminal.Gui/ViewBase/Layout/Dim.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public abstract record Dim : IEqualityOperators<Dim, Dim, bool>
9393
/// <summary>Creates an Absolute <see cref="Dim"/> from the specified integer value.</summary>
9494
/// <returns>The Absolute <see cref="Dim"/>.</returns>
9595
/// <param name="size">The value to convert to the <see cref="Dim"/>.</param>
96-
public static Dim? Absolute (int size) { return new DimAbsolute (size); }
96+
public static Dim Absolute (int size) { return new DimAbsolute (size); }
9797

9898
/// <summary>
9999
/// 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>
119119
/// </param>
120120
/// <param name="minimumContentDim">The minimum dimension the View's ContentSize will be constrained to.</param>
121121
/// <param name="maximumContentDim">The maximum dimension the View's ContentSize will be fit to.</param>
122-
public static Dim? Auto (DimAutoStyle style = DimAutoStyle.Auto, Dim? minimumContentDim = null, Dim? maximumContentDim = null)
122+
public static Dim Auto (DimAutoStyle style = DimAutoStyle.Auto, Dim? minimumContentDim = null, Dim? maximumContentDim = null)
123123
{
124124
return new DimAuto (
125125
MinimumContentDim: minimumContentDim,
@@ -131,14 +131,14 @@ public abstract record Dim : IEqualityOperators<Dim, Dim, bool>
131131
/// Creates a <see cref="Dim"/> object that fills the dimension, leaving no margin.
132132
/// </summary>
133133
/// <returns>The Fill dimension.</returns>
134-
public static Dim? Fill () { return new DimFill (0); }
134+
public static Dim Fill () { return new DimFill (0); }
135135

136136
/// <summary>
137137
/// Creates a <see cref="Dim"/> object that fills the dimension, leaving the specified margin.
138138
/// </summary>
139139
/// <returns>The Fill dimension.</returns>
140140
/// <param name="margin">Margin to use.</param>
141-
public static Dim? Fill (Dim margin) { return new DimFill (margin); }
141+
public static Dim Fill (Dim margin) { return new DimFill (margin); }
142142

143143
/// <summary>
144144
/// 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>
172172
/// };
173173
/// </code>
174174
/// </example>
175-
public static Dim? Percent (int percent, DimPercentMode mode = DimPercentMode.ContentSize)
175+
public static Dim Percent (int percent, DimPercentMode mode = DimPercentMode.ContentSize)
176176
{
177177
ArgumentOutOfRangeException.ThrowIfNegative (percent, nameof (percent));
178178

0 commit comments

Comments
 (0)