Skip to content

Commit 4335a3a

Browse files
committed
docs: use fluent control builders throughout Tutorial 3
1 parent b1a90bc commit 4335a3a

1 file changed

Lines changed: 96 additions & 115 deletions

File tree

docs/tutorials/03-settings-app.md

Lines changed: 96 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,17 @@ using SharpConsoleUI.Drivers;
3333
var driver = new NetConsoleDriver(RenderMode.Buffer);
3434
var windowSystem = new ConsoleWindowSystem(driver);
3535

36-
Window? settingsWindow = null; // built in Step 3, referenced by the click handler
36+
Window? settingsWindow = null; // built in Step 5, referenced by the click handler
3737
3838
var mainWindow = new WindowBuilder(windowSystem)
3939
.WithTitle("My App")
4040
.WithSize(60, 20)
4141
.Centered()
4242
.Build();
4343

44-
mainWindow.AddControl(new MarkupControl(
45-
new List<string> { "[bold]Welcome![/] Press the button below to open Settings." }));
44+
mainWindow.AddControl(Controls.Markup()
45+
.AddLine("[bold]Welcome![/] Press the button below to open Settings.")
46+
.Build());
4647

4748
mainWindow.AddControl(Controls.Button("⚙ Settings")
4849
.OnClick((s, e, win) =>
@@ -55,63 +56,65 @@ mainWindow.AddControl(Controls.Button("⚙ Settings")
5556
windowSystem.AddWindow(mainWindow);
5657
```
5758

58-
The settings window is declared as `null` here and assigned in Step 3 — the click handler captures it by reference.
59+
The settings window is declared as `null` here and assigned in Step 5 — the click handler captures it by reference.
5960

60-
## Step 3: Create the settings window and NavigationView
61+
## Step 3: Create the settings window
6162

62-
Create the settings window and add a `NavigationView` that fills it — the nav pane is on the left, the content panel on the right.
63+
Create the settings window. The `NavigationView` will be added to it in Step 5 once all pages are defined inline.
6364

6465
```csharp
6566
settingsWindow = new WindowBuilder(windowSystem)
6667
.WithTitle("Settings")
6768
.WithSize(70, 22)
6869
.Centered()
6970
.Build();
70-
71-
var nav = new NavigationView
72-
{
73-
PaneHeader = "[bold]Settings[/]",
74-
VerticalAlignment = VerticalAlignment.Fill
75-
};
76-
settingsWindow.AddControl(nav);
7771
```
7872

79-
The `NavigationView` has two zones: a narrow pane on the left for nav items, and a content panel on the right. You'll populate both in the next two steps.
80-
81-
## Step 4: Add navigation items
73+
## Step 4: Declare sliders before the NavigationView builder
8274

83-
Each `NavigationItem` represents a page — first argument is the display name, second is an optional icon.
75+
The sliders must be declared outside the `NavigationView` builder so that `ValueChanged` handlers can capture them after the builder call. They are added to the Appearance page inline inside `AddItem()`.
8476

8577
```csharp
86-
var appearanceItem = nav.AddItem("Appearance", "🎨");
87-
var generalItem = nav.AddItem("General", "");
78+
var redSlider = Controls.Slider().Horizontal().WithName("r").WithRange(0, 255).WithValue(30).WithStep(1).Build();
79+
var greenSlider = Controls.Slider().Horizontal().WithName("g").WithRange(0, 255).WithValue(60).WithStep(1).Build();
80+
var blueSlider = Controls.Slider().Horizontal().WithName("b").WithRange(0, 255).WithValue(120).WithStep(1).Build();
8881
```
8982

90-
`NavigationView` auto-selects the first item on load. You can pass a subtitle as an optional third argument.
83+
## Step 5: Build the NavigationView with inline page content
9184

92-
## Step 5: Populate the Appearance page
93-
94-
`SetItemContent` registers a factory — `NavigationView` calls it when the item is selected, passing the content `ScrollablePanelControl` for you to populate.
85+
`AddItem()` accepts an optional `content:` factory — `NavigationView` calls it when the item is first selected, passing the content `ScrollablePanelControl` for you to populate. Both pages are defined inline here.
9586

9687
```csharp
97-
// Declared outside the factory so we can read them in ValueChanged:
98-
var redSlider = new SliderControl { Name = "r", MinValue = 0, MaxValue = 255, Value = 30, Step = 1 };
99-
var greenSlider = new SliderControl { Name = "g", MinValue = 0, MaxValue = 255, Value = 60, Step = 1 };
100-
var blueSlider = new SliderControl { Name = "b", MinValue = 0, MaxValue = 255, Value = 120, Step = 1 };
88+
var nav = Controls.NavigationView()
89+
.WithPaneHeader("[bold]Settings[/]")
90+
.AddItem("Appearance", "🎨", content: panel =>
91+
{
92+
panel.AddControl(Controls.Markup().AddLine("[bold]Background Color[/]").Build());
93+
panel.AddControl(Controls.Markup().AddLine("Red").Build());
94+
panel.AddControl(redSlider);
95+
panel.AddControl(Controls.Markup().AddLine("Green").Build());
96+
panel.AddControl(greenSlider);
97+
panel.AddControl(Controls.Markup().AddLine("Blue").Build());
98+
panel.AddControl(blueSlider);
99+
})
100+
.AddItem("General", "", content: panel =>
101+
{
102+
panel.AddControl(Controls.Markup().AddLine("[bold]General[/]").Build());
103+
panel.AddControl(Controls.Markup().AddLine("Display name:").Build());
104+
panel.AddControl(Controls.Prompt("Display name:").WithName("displayName").Build());
105+
panel.AddControl(Controls.Markup().AddLine("API endpoint:").Build());
106+
panel.AddControl(Controls.Prompt("API endpoint:").WithName("apiEndpoint").Build());
107+
panel.AddControl(Controls.Checkbox("Enable notifications").Checked().Build());
108+
})
109+
.Fill()
110+
.Build();
101111

102-
nav.SetItemContent(appearanceItem, panel =>
103-
{
104-
panel.AddControl(new MarkupControl(new List<string> { "[bold]Background Color[/]" }));
105-
panel.AddControl(new MarkupControl(new List<string> { "Red" }));
106-
panel.AddControl(redSlider);
107-
panel.AddControl(new MarkupControl(new List<string> { "Green" }));
108-
panel.AddControl(greenSlider);
109-
panel.AddControl(new MarkupControl(new List<string> { "Blue" }));
110-
panel.AddControl(blueSlider);
111-
});
112+
settingsWindow.AddControl(nav);
112113
```
113114

114-
> **Alternative:** Skip `SetItemContent`, subscribe to `nav.SelectedItemChanged`, and manipulate `nav.ContentPanel` directly — useful when content depends on external state not known at registration time. See [NavigationView reference](../controls/NavigationView.md).
115+
`NavigationView` auto-selects the first item on load. `.Fill()` sets `VerticalAlignment.Fill` so the nav spans the full window height.
116+
117+
Alternatively, subscribe to `nav.SelectedItemChanged` and manipulate `nav.ContentPanel` directly for content that depends on external runtime state.
115118

116119
## Step 6: Wire sliders to the gradient
117120

@@ -140,36 +143,15 @@ blueSlider.ValueChanged += (s, e) => ApplyGradient();
140143

141144
`GradientBackground` is a record combining a `ColorGradient` and a `GradientDirection`. `ColorGradient.FromColors()` interpolates smoothly between the stops. See [Gradients & Alpha](../GRADIENTS.md) for predefined gradients and more patterns.
142145

143-
## Step 7: Populate the General page
144-
145-
The General page is a simple form — labels, text inputs, and a checkbox stacked vertically in the content panel.
146-
147-
```csharp
148-
nav.SetItemContent(generalItem, panel =>
149-
{
150-
panel.AddControl(new MarkupControl(new List<string> { "[bold]General[/]" }));
151-
152-
panel.AddControl(new MarkupControl(new List<string> { "Display name:" }));
153-
panel.AddControl(new PromptControl { Name = "displayName", PlaceholderText = "Enter name..." });
154-
155-
panel.AddControl(new MarkupControl(new List<string> { "API endpoint:" }));
156-
panel.AddControl(new PromptControl { Name = "apiEndpoint", PlaceholderText = "https://..." });
157-
158-
panel.AddControl(new CheckboxControl { Text = "Enable notifications", IsChecked = true });
159-
});
160-
```
161-
162-
Form fields are standard controls inside a `ScrollablePanelControl` — no special form container needed.
163-
164-
## Step 8: Add a status bar and keyboard shortcuts
146+
## Step 7: Add a status bar and keyboard shortcuts
165147

166148
`StatusBarControl` is display-and-click-only — it shows key hint text but does NOT intercept keyboard events; wire shortcuts separately via `window.PreviewKeyPressed`.
167149

168150
```csharp
169-
var statusBar = new StatusBarControl();
170-
statusBar.AddItem(new StatusBarItem { Shortcut = "Ctrl+S", Label = "Save" });
171-
statusBar.AddItem(new StatusBarItem { Shortcut = "Esc", Label = "Cancel" });
172-
settingsWindow.AddControl(statusBar);
151+
settingsWindow.AddControl(Controls.StatusBar()
152+
.AddLeft("Ctrl+S", "Save")
153+
.AddLeft("Esc", "Cancel")
154+
.Build());
173155

174156
// PreviewKeyPressed fires before the focused control sees the key — correct for global shortcuts.
175157
settingsWindow.PreviewKeyPressed += (s, e) =>
@@ -189,7 +171,7 @@ settingsWindow.PreviewKeyPressed += (s, e) =>
189171

190172
Pressing Esc closes the settings window and returns focus to the main window. Ctrl+S calls `SaveAndClose()` which applies the gradient and closes the window.
191173

192-
## Step 9: Open and close the settings window
174+
## Step 8: Open and close the settings window
193175

194176
`windowSystem.AddWindow()` activates the settings window on top; closing it removes it from the stack and returns focus to the main window.
195177

@@ -221,10 +203,10 @@ using SharpConsoleUI.Rendering;
221203
var driver = new NetConsoleDriver(RenderMode.Buffer);
222204
var windowSystem = new ConsoleWindowSystem(driver);
223205

224-
// ── Sliders declared before SetItemContent factories so ValueChanged can capture them ──
225-
var redSlider = new SliderControl { Name = "r", MinValue = 0, MaxValue = 255, Value = 30, Step = 1 };
226-
var greenSlider = new SliderControl { Name = "g", MinValue = 0, MaxValue = 255, Value = 60, Step = 1 };
227-
var blueSlider = new SliderControl { Name = "b", MinValue = 0, MaxValue = 255, Value = 120, Step = 1 };
206+
// ── Sliders declared before the NavigationView builder so ValueChanged can capture them ──
207+
var redSlider = Controls.Slider().Horizontal().WithName("r").WithRange(0, 255).WithValue(30).WithStep(1).Build();
208+
var greenSlider = Controls.Slider().Horizontal().WithName("g").WithRange(0, 255).WithValue(60).WithStep(1).Build();
209+
var blueSlider = Controls.Slider().Horizontal().WithName("b").WithRange(0, 255).WithValue(120).WithStep(1).Build();
228210

229211
// ── Settings window (assigned below; captured by the Settings button click handler) ──
230212
Window? settingsWindow = null;
@@ -236,8 +218,9 @@ var mainWindow = new WindowBuilder(windowSystem)
236218
.Centered()
237219
.Build();
238220

239-
mainWindow.AddControl(new MarkupControl(
240-
new List<string> { "[bold]Welcome![/] Press the button below to open Settings." }));
221+
mainWindow.AddControl(Controls.Markup()
222+
.AddLine("[bold]Welcome![/] Press the button below to open Settings.")
223+
.Build());
241224

242225
mainWindow.AddControl(Controls.Button("⚙ Settings")
243226
.OnClick((s, e, win) =>
@@ -256,28 +239,34 @@ settingsWindow = new WindowBuilder(windowSystem)
256239
.Centered()
257240
.Build();
258241

259-
var nav = new NavigationView
260-
{
261-
PaneHeader = "[bold]Settings[/]",
262-
VerticalAlignment = VerticalAlignment.Fill
263-
};
264-
settingsWindow.AddControl(nav);
265-
266-
var appearanceItem = nav.AddItem("Appearance", "🎨");
267-
var generalItem = nav.AddItem("General", "");
242+
// ── NavigationView with inline page content ──
243+
var nav = Controls.NavigationView()
244+
.WithPaneHeader("[bold]Settings[/]")
245+
.AddItem("Appearance", "🎨", content: panel =>
246+
{
247+
panel.AddControl(Controls.Markup().AddLine("[bold]Background Color[/]").Build());
248+
panel.AddControl(Controls.Markup().AddLine("Red").Build());
249+
panel.AddControl(redSlider);
250+
panel.AddControl(Controls.Markup().AddLine("Green").Build());
251+
panel.AddControl(greenSlider);
252+
panel.AddControl(Controls.Markup().AddLine("Blue").Build());
253+
panel.AddControl(blueSlider);
254+
})
255+
.AddItem("General", "", content: panel =>
256+
{
257+
panel.AddControl(Controls.Markup().AddLine("[bold]General[/]").Build());
258+
panel.AddControl(Controls.Markup().AddLine("Display name:").Build());
259+
panel.AddControl(Controls.Prompt("Display name:").WithName("displayName").Build());
260+
panel.AddControl(Controls.Markup().AddLine("API endpoint:").Build());
261+
panel.AddControl(Controls.Prompt("API endpoint:").WithName("apiEndpoint").Build());
262+
panel.AddControl(Controls.Checkbox("Enable notifications").Checked().Build());
263+
})
264+
.Fill()
265+
.Build();
268266

269-
// Appearance page
270-
nav.SetItemContent(appearanceItem, panel =>
271-
{
272-
panel.AddControl(new MarkupControl(new List<string> { "[bold]Background Color[/]" }));
273-
panel.AddControl(new MarkupControl(new List<string> { "Red" }));
274-
panel.AddControl(redSlider);
275-
panel.AddControl(new MarkupControl(new List<string> { "Green" }));
276-
panel.AddControl(greenSlider);
277-
panel.AddControl(new MarkupControl(new List<string> { "Blue" }));
278-
panel.AddControl(blueSlider);
279-
});
267+
settingsWindow.AddControl(nav);
280268

269+
// ── Gradient helper + slider wiring ──
281270
void ApplyGradient()
282271
{
283272
var color = new Color(
@@ -294,24 +283,13 @@ redSlider.ValueChanged += (s, e) => ApplyGradient();
294283
greenSlider.ValueChanged += (s, e) => ApplyGradient();
295284
blueSlider.ValueChanged += (s, e) => ApplyGradient();
296285

297-
// General page
298-
nav.SetItemContent(generalItem, panel =>
299-
{
300-
panel.AddControl(new MarkupControl(new List<string> { "[bold]General[/]" }));
301-
panel.AddControl(new MarkupControl(new List<string> { "Display name:" }));
302-
panel.AddControl(new PromptControl { Name = "displayName", PlaceholderText = "Enter name..." });
303-
panel.AddControl(new MarkupControl(new List<string> { "API endpoint:" }));
304-
panel.AddControl(new PromptControl { Name = "apiEndpoint", PlaceholderText = "https://..." });
305-
panel.AddControl(new CheckboxControl { Text = "Enable notifications", IsChecked = true });
306-
});
307-
308-
// Status bar
309-
var statusBar = new StatusBarControl();
310-
statusBar.AddItem(new StatusBarItem { Shortcut = "Ctrl+S", Label = "Save" });
311-
statusBar.AddItem(new StatusBarItem { Shortcut = "Esc", Label = "Cancel" });
312-
settingsWindow.AddControl(statusBar);
313-
314-
// Keyboard shortcuts via PreviewKeyPressed
286+
// ── Status bar ──
287+
settingsWindow.AddControl(Controls.StatusBar()
288+
.AddLeft("Ctrl+S", "Save")
289+
.AddLeft("Esc", "Cancel")
290+
.Build());
291+
292+
// ── Keyboard shortcuts via PreviewKeyPressed ──
315293
settingsWindow.PreviewKeyPressed += (s, e) =>
316294
{
317295
if (e.KeyInfo.Key == ConsoleKey.Escape)
@@ -326,7 +304,7 @@ settingsWindow.PreviewKeyPressed += (s, e) =>
326304
}
327305
};
328306

329-
// Save toolbar button
307+
// ── Save toolbar button ──
330308
void SaveAndClose()
331309
{
332310
ApplyGradient();
@@ -340,11 +318,14 @@ windowSystem.Run();
340318

341319
## What you learned
342320

343-
- `NavigationView` — pane + content panel layout
344-
- `nav.AddItem()` — register navigation items with icon and optional subtitle
345-
- `nav.SetItemContent()` — factory pattern for populating content panels
346-
- `nav.ContentPanel` + `SelectedItemChanged` — alternative for dynamic content when content depends on external state not known at registration time
347-
- `SliderControl` with `ValueChanged` — fires on every drag tick for live updates
321+
- `Controls.NavigationView()` builder — `WithPaneHeader()`, `AddItem(..., content:)`, and `.Fill()` replace manual `new NavigationView { ... }` + `SetItemContent()` calls
322+
- `nav.ContentPanel` + `SelectedItemChanged` — alternative for dynamic content that depends on external runtime state
323+
- `Controls.Slider()` builder — `.Horizontal()`, `.WithName()`, `.WithRange()`, `.WithValue()`, `.WithStep()`
324+
- `SliderControl.ValueChanged` — fires on every drag tick for live updates
325+
- `Controls.Markup().AddLine()` — replaces `new MarkupControl(new List<string> { ... })`
326+
- `Controls.Prompt().WithName()` — replaces `new PromptControl { Name = ... }`
327+
- `Controls.Checkbox().Checked()` — replaces `new CheckboxControl { IsChecked = true }`
328+
- `Controls.StatusBar().AddLeft()` — replaces `new StatusBarControl()` + `AddItem()` calls
348329
- `GradientBackground` + `ColorGradient.FromColors()` + `GradientDirection` — live window background gradients
349330
- Form layout: `PromptControl` + `CheckboxControl` in a `ScrollablePanelControl` — no special form container needed
350331
- `StatusBarControl` — display+click only; does not intercept key events

0 commit comments

Comments
 (0)