diff --git a/AGENTS.md b/AGENTS.md index f64690b..f608e7b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,6 +71,7 @@ Update this file when the user gives: - a rule request for library skill quality, such as “when adding a library skill, include installation and practical usage patterns in the skill body” - In chat status updates for repository work, keep progress messages terse and execution-focused. Do not dump internal planning narration, repo-level obligation reminders, file/search counters, or step-by-step patching strategy unless the user explicitly asks for that level of detail. +- When resolving pull-request conflicts and local Git access is available, use the repository checkout and Git CLI directly. Do not switch to browser-based conflict resolution merely because a prior push or API attempt encountered an environment or authentication problem. Treat explicit frustration, swearing, sarcasm, repeated rejection, or "don't do this again" as strong signals that a durable rule should likely be captured here. diff --git a/README.md b/README.md index 9f88064..ee5f2ce 100644 --- a/README.md +++ b/README.md @@ -291,7 +291,7 @@ When you refresh vendored upstream content locally, use `bash scripts/sync_exter -This catalog currently contains **185** skills. +This catalog currently contains **186** skills. ### .NET Foundations @@ -315,6 +315,7 @@ This catalog currently contains **185** skills. | [`plan-ui-change`](catalog/Platform/Official-DotNet-Blazor/skills/plan-ui-change/) | `0.1.0` | Plan complex Blazor UI features by decomposing them into focused components. USE FOR: building a complex Blazor page with multiple sections, planning component decomposition, designing a multi-section dashboard or layout, breaking down a large UI feature into composable components, pages with sidebars and content panels, any page with 3+ distinct visual sections or multiple interacting sub-features, identifying parent-child relationships and data flow. DO NOT USE FOR: creating new Blazor projects or apps from scratch (use create-blazor-project), implementing a single individual component (use author-component), writing component code with parameters and EventCallback (use author-component), or simple single-component pages. | | [`project-setup`](catalog/Platform/Project-Setup/skills/project-setup/) | `1.0.1` | Create or reorganize .NET solutions with clean project boundaries, repeatable SDK settings, and a maintainable baseline for libraries, apps, tests, CI, and local development. USE FOR: creating a new .NET solution or restructuring an existing one; setting up Directory.Build.props, shared package management, or repo-wide defaults; defining project. DO NOT USE FOR: unrelated stacks; generic tasks that do not need this specific guidance. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, or validation commands when changes are made. | | [`setup-local-sdk`](catalog/Platform/Official-DotNet/skills/setup-local-sdk/) | `0.2.0` | Install a .NET SDK locally for safe preview testing, specific-version pinning, or reproducible team setups — without modifying the system-wide installation. USE FOR: trying .NET previews safely, testing specific SDK versions, installing MAUI or other workloads on a preview, updating or replacing an existing local SDK, creating reproducible team/CI install scripts, configuring global.json paths. DO NOT USE FOR: system-wide SDK installs, .NET hosts older than 10, runtime-only installs, or projects not using SDK-style commands. | +| [`sharpconsoleui`](catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/) | `1.0.0` | Use SharpConsoleUI to build full terminal (TUI) applications in .NET — equally suited to full-screen single-window apps and multi-window desktops with overlapping draggable windows — using a compositor, a DOM layout engine, and 40+ reactive controls (data tables, tree views, forms, an embedded PTY terminal, markdown, charts, video). USE FOR: interactive console apps, full-screen TUIs, multi-window terminal desktops, dashboards, wizards, and admin/monitoring UIs that need focus, mouse, and flicker-free rendering over local terminals or SSH; NativeAOT console tools. DO NOT USE FOR: simple line-based CLI output or argument parsing; non-interactive scripts; GUI/desktop (WPF/WinForms) or web UIs. INVOKES: inspect the project, add the SharpConsoleUI package, scaffold a window/control tree, and build/run to verify the app renders. | | [`support-prerendering`](catalog/Platform/Official-DotNet-Blazor/skills/support-prerendering/) | `0.1.0` | Make interactive Blazor components work correctly with prerendering. USE FOR fixing duplicate data loads, UI flicker during prerender-to-interactive handoff, null references during prerender, persisting state across prerender, disabling prerendering, excluding pages from interactive routing, or detecting whether a component is currently prerendering. DO NOT USE for choosing which render mode to use (see create-blazor-project) or general component authoring (see author-component). | | [`system-text-json-net11`](catalog/Platform/Official-DotNet-Dotnet11/skills/system-text-json-net11/) | `0.1.0` | Provides guidance on new System.Text.Json APIs introduced in .NET 11. It covers typed JsonTypeInfo access via GetTypeInfo and TryGetTypeInfo on JsonSerializerOptions, and the new JsonNamingPolicy.PascalCase static property. Use when serializing or deserializing JSON in .NET 11 applications and needing typed metadata access or PascalCase property naming. | | [`use-js-interop`](catalog/Platform/Official-DotNet-Blazor/skills/use-js-interop/) | `0.1.0` | Add, review, or fix JavaScript interop in Blazor components. USE FOR: calling JavaScript from Blazor, calling .NET from JavaScript, collocated .razor.js modules, IJSRuntime, IJSObjectReference lifecycle, DotNetObjectReference, ElementReference, timing rules for when JS is available, IAsyncDisposable disposal of JS references, server-side JS interop safety. DO NOT USE FOR: general Blazor component authoring without JS interop needs (use author-component), forms (use collect-user-input). | diff --git a/catalog/Frameworks/SharpConsoleUI/manifest.json b/catalog/Frameworks/SharpConsoleUI/manifest.json new file mode 100644 index 0000000..5a5272c --- /dev/null +++ b/catalog/Frameworks/SharpConsoleUI/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "SharpConsoleUI", + "title": "SharpConsoleUI", + "description": "Terminal application framework for .NET with overlapping windows, a real compositor, a DOM layout engine, and 40+ reactive controls.", + "links": { + "repository": "https://github.com/nickprotop/ConsoleEx", + "docs": "https://nickprotop.github.io/ConsoleEx/", + "nuget": "https://www.nuget.org/packages/SharpConsoleUI/" + } +} diff --git a/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/SKILL.md b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/SKILL.md new file mode 100644 index 0000000..10ca0d2 --- /dev/null +++ b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/SKILL.md @@ -0,0 +1,143 @@ +--- +name: sharpconsoleui +description: "Use SharpConsoleUI to build full terminal (TUI) applications in .NET — equally suited to full-screen single-window apps and multi-window desktops with overlapping draggable windows — using a compositor, a DOM layout engine, and 40+ reactive controls (data tables, tree views, forms, an embedded PTY terminal, markdown, charts, video). USE FOR: interactive console apps, full-screen TUIs, multi-window terminal desktops, dashboards, wizards, and admin/monitoring UIs that need focus, mouse, and flicker-free rendering over local terminals or SSH; NativeAOT console tools. DO NOT USE FOR: simple line-based CLI output or argument parsing; non-interactive scripts; GUI/desktop (WPF/WinForms) or web UIs. INVOKES: inspect the project, add the SharpConsoleUI package, scaffold a window/control tree, and build/run to verify the app renders." +compatibility: "Requires a .NET 8.0+ console project (net8.0/net9.0/net10.0) that can reference the `SharpConsoleUI` package and run against a modern terminal. Uses a retained-mode UI on a single cooperative UI thread, not immediate-mode Console.Write." +--- + +# SharpConsoleUI terminal application framework + +## Trigger On + +- building an interactive terminal UI: dashboards, wizards, settings screens, admin/monitoring consoles +- building a full-screen single-window TUI: a `.Frameless()` (no title bar, no title buttons) + `.Maximized()` app that owns the whole terminal +- building a multi-window terminal desktop: overlapping windows with drag/resize/minimize/maximize, z-order, focus routing, modal windows, and mouse support (full-screen and multi-window are equally first-class here) +- wanting flicker-free rendering that stays clean over SSH (diff-based cell buffer, not full repaints) +- needing rich terminal controls: data tables, tree/list views, forms, an embedded PTY terminal, markdown, charts, or video +- shipping a NativeAOT-ready console application with a real UI + +Do not trigger for plain line-based CLI tools, argument parsing, or non-interactive scripts. + +## Install + +- NuGet: + - `dotnet add package SharpConsoleUI` + - `dotnet add package SharpConsoleUI --version ` +- XML package reference: + - `` +- Targets `net8.0`, `net9.0`, `net10.0`. +- Sources: + - [NuGet: SharpConsoleUI](https://www.nuget.org/packages/SharpConsoleUI/) + - [GitHub: nickprotop/ConsoleEx](https://github.com/nickprotop/ConsoleEx) + - [Docs site](https://nickprotop.github.io/ConsoleEx/) + +## Workflow + +```mermaid +flowchart LR + A["NetConsoleDriver (RenderMode.Buffer)"] --> B["ConsoleWindowSystem"] + B --> C["WindowBuilder -> Window"] + C --> D["window.AddControl(Controls.*)"] + D --> E["DOM layout: Measure -> Arrange -> Paint"] + E --> F["windowSystem.AddWindow(window)"] + F --> G["windowSystem.Run() (blocks until Shutdown)"] + G --> H["compositor merges per-window buffers -> terminal"] +``` + +1. Create a `NetConsoleDriver` and a `ConsoleWindowSystem` that owns all windows. +2. Build one or more windows with `WindowBuilder` (title, size, position, borders, padding). +3. Add controls to each window with `window.AddControl(...)`, usually via the `Controls` static factory. +4. Wire interactivity through control events (e.g. `Button.OnClick`); call `windowSystem.Shutdown()` to exit. +5. `windowSystem.AddWindow(window)` then `windowSystem.Run()` starts the render/input loop (blocks until shutdown). +6. For layout, dialogs, portals/overlays, forms, and the full control set, load the reference files below. + +### Minimal app (read + show + interact) + +```csharp +using SharpConsoleUI; +using SharpConsoleUI.Builders; +using SharpConsoleUI.Controls; +using SharpConsoleUI.Drivers; + +var driver = new NetConsoleDriver(RenderMode.Buffer); +var windowSystem = new ConsoleWindowSystem(driver); + +var window = new WindowBuilder(windowSystem) + .WithTitle("Hello World") + .WithSize(50, 12) + .Centered() + .Build(); + +window.AddControl(Controls.Markup() + .AddLine("[bold cyan]Hello, SharpConsoleUI![/]") + .Build()); + +window.AddControl(Controls.Button("Quit") + .OnClick((sender, e, win) => windowSystem.Shutdown()) + .Build()); + +windowSystem.AddWindow(window); +windowSystem.Run(); +``` + +### Layout + data example + +Use a `GridControl` when you need columns/rows with fixed, size-to-content, or +proportional (`Star`) tracks, and put content controls (tables, lists, markdown) +into the cells: + +```csharp +var grid = Controls.Grid() + .Columns(GridLength.Cells(20), GridLength.Star(1)) // fixed sidebar + fill + .Rows(GridLength.Auto(), GridLength.Star(1)) // toolbar + body + .RowGap(1) + .Place(Controls.Markup("[bold]Dashboard[/]").Build(), 0, 0, colSpan: 2) + .Place(sidebarList, 1, 0) + .Place(dataTable, 1, 1) + .Build(); + +window.AddControl(grid); +``` + +See `references/recipes.md` for full grid/table/form/dialog examples and +`references/controls.md` for the control chosen per region. + +## Best practices + +- Describe the UI declaratively (retained mode). Let the framework own redraws, focus, and input — do not mix raw `Console.Write` into a running app. +- The app runs on one cooperative UI thread. Never block it: don't call `.Result` / `.Wait()` on async work inside a handler (it deadlocks the loop). Push blocking/CPU work off-thread and marshal UI mutations back with `EnqueueOnUIThread` / `InvokeAsync`. See `references/architecture.md`. +- Use portals for dropdowns, overlays, and toasts, and the built-in `Dialogs` for confirm/prompt/progress, instead of hand-positioning windows. See `references/recipes.md`. +- Apply semantic control `Role`s (Primary, Success, Danger, …) so colors come from the active theme instead of being hand-set. +- Style all text with `[tag]text[/]` markup — it works everywhere text renders (labels, titles, status bars, table cells, tree nodes), including `[markdown]`, `[gradient=…]`, and inline `[spinner]`. Escape untrusted text with `MarkupParser.Escape(...)`. See `references/markup.md`. +- Prefer `Controls.*` factory + fluent builders over manual control construction for consistent wiring. +- For a full app (not a single screen), follow the production patterns in `references/app-patterns.md`: one maximized window with content-swap navigation, an app-owned header/hint bar, custom awaitable modals closed via `CloseModalWindow`, dialogs clamped to `DesktopDimensions`, a semantic color/theme layer (no hardcoded hex), and a wrapper so no async action can crash the loop. + +## Limitations to check before production + +- It is a full retained-mode application framework, not a drop-in for one-off `Console.WriteLine` output. Small non-interactive tools do not need it. +- Cooperative single-threaded UI model: unsynchronized cross-thread control mutation, or blocking the UI thread on async work, is a bug — not a shortcut. +- `TerminalControl` (the embedded PTY emulator) runs on Linux and Windows 10 1809+ (openpty on Linux, ConPTY on Windows); it throws `PlatformNotSupportedException` on other OSes. On Linux it requires `PtyShim.RunIfShim(args)` as the very first line of `Main` (a safe no-op on Windows) — see `references/controls.md`. +- Advanced media (video, Kitty-graphics image rendering) auto-detects terminal capability and degrades to half-block/ASCII/braille fallbacks; verify on the target terminal. +- NativeAOT is supported (the library is `IsAotCompatible` and CI-verified), but confirm your own reflection/serialization code is trim/AOT-safe; `HtmlControl` is the one documented AOT caveat. See `references/architecture.md`. + +## Deliver + +- an install + first-run guide ready to paste into a .NET console project +- a minimal window app and a layout/data example that actually render +- clear notes on the threading model, portals/dialogs, and terminal-capability tradeoffs + +## Validate + +- `dotnet add package SharpConsoleUI` restores and the project compiles. +- The minimal app above builds and runs, showing a centered window with a working Quit button (Tab to focus, Enter/click to quit). +- One layout/control sample from `references/recipes.md` renders correctly. +- Any control used is confirmed against `references/controls.md` (correct factory/API). + +## Load References + +- [Control reference](references/controls.md) — the 40+ control set grouped by purpose, with the factory/API entry point and a "use when" for each group. +- [Markup reference](references/markup.md) — the `[tag]text[/]` markup language (colors, decorations, links, `[spinner]`, `[markdown]`, `[gradient]`, `MarkupParser` API). Markup works everywhere text renders — load this for any styled-text work. +- [Architecture reference](references/architecture.md) — compositor, DOM layout pipeline, cooperative UI-thread model, marshalling, and NativeAOT notes. +- [Recipe reference](references/recipes.md) — full-screen and multi-window apps, window builders, grids, dialogs, portals/toasts, and forms, with verified code. +- [Feature reference](references/features.md) — gradients, transparency/alpha blending, compositor effects, animations, desktop background, and image/video/syntax-highlighting rendering. +- [System reference](references/system.md) — constructor overloads, state services, panels, configuration, registry, MVVM data binding, flows, plugins, clipboard, shell-pipeline/schost distribution, and the "when to choose this framework" comparison + pattern catalog. +- [Application-pattern reference](references/app-patterns.md) — how to structure and polish a real production app: single-window shell + content-swap navigation, own-your-header, custom awaitable modals, live process output, the semantic color/theme layer, escaping untrusted text, threading discipline, and a never-crash wrapper. Load this when building a full app, not just one screen. diff --git a/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/manifest.json b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/manifest.json new file mode 100644 index 0000000..3535b19 --- /dev/null +++ b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/manifest.json @@ -0,0 +1,7 @@ +{ + "version": "1.0.0", + "category": "Terminal UI", + "packages": [ + "SharpConsoleUI" + ] +} diff --git a/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/app-patterns.md b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/app-patterns.md new file mode 100644 index 0000000..7078fa1 --- /dev/null +++ b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/app-patterns.md @@ -0,0 +1,131 @@ +# SharpConsoleUI app architecture and UX patterns + +How to structure and polish a real production SharpConsoleUI app, distilled from +shipping apps (the cx-/lazy- series and third-party consumers). These are +app-level patterns built on the framework APIs named below — adopt them when +building anything beyond a single-screen demo. + +## Single-window shell + content-swap navigation + +Most polished apps are **one maximized window**, not many. "Screens" are +content-swap units inside that one window, driven by a small navigator — this +gives full control of every row and a consistent frame. + +- Bootstrap once: `new ConsoleWindowSystem(new NetConsoleDriver(RenderMode.Buffer), theme, options)`, one `WindowBuilder(...).Maximized().Movable(false).Resizable(false).Closable(false).HideTitle().Build()`, `AddWindow`, `Run()`. Disable the built-in panels (`ShowTopPanel: false, ShowBottomPanel: false`) so the app owns every row. +- Navigator holds a `Stack` with `Root/Push/Replace/Pop/PopToRoot`. Each transition calls `window.ClearControls()` then the top screen's `Build(...)`, which re-adds its controls. A screen abstraction is just an interface you define: a breadcrumb label, a `Build` method, and an optional `HandleKey(ConsoleKeyInfo) -> bool`. +- Guard content swaps against async races: bump a generation counter on every navigation, capture it before a screen's `Build` awaits, and discard the stale continuation's mutations if navigation advanced meanwhile. Prevents a slow screen painting over one the user already left. + +```csharp +void Show(IScreen screen) +{ + window.ClearControls(); + window.AddControl(BuildHeader()); // your own header (see below) + screen.Build(this); // screen appends its controls + window.FocusControl(screen.FirstFocusable); +} +``` + +## Own your header / status / hints + +With the built-in panels off, render your own 3-line header on every navigation: +a live status line, a breadcrumb + right-aligned global hints, and a full-width +rule (`─`). Every screen also appends a bottom **hint bar** listing exactly the +keys its `HandleKey` implements, so hints never drift from behavior. Build all of +it as markup in a `MarkupControl`. To right-align two segments despite markup +tags, measure visible width by stripping tags (see "escaping" below). + +## Keyboard-first interaction + +- Global keys via `window.PreviewKeyPressed` (fires before controls): offer the key to the current screen's `HandleKey` first, then handle `Esc` = back/pop, `?` = help, etc. App-wide hotkeys via `system.RegisterGlobalShortcut(ConsoleModifiers.Control, ConsoleKey.Q, () => system.RequestExit(0))`. +- Conventions to keep consistent across screens: `Esc` cancels/goes back; `Enter` activates/confirms; single-letter mnemonics for actions; arrow Up/Down for history recall. Resolve list selection on `ListControl.ItemActivated` (Enter/double-click), not `SelectedIndexChanged` (mere highlight). + +## Custom modal dialog with an awaitable result + +Beyond the built-in `Dialogs.*` helpers, the standard pattern for custom dialogs +is a modal window that a caller can `await`: + +```csharp +var tcs = new TaskCompletionSource(); +var (w, h) = ClampSize(system, desiredW, desiredH); // clamp to desktop, see below +var dialog = new WindowBuilder(system) + .WithTitle(" Pick ").WithSize(w, h).Centered().AsModal().Build(); +list.ItemActivated += (s, e) => { chosen = list.SelectedIndex; system.CloseModalWindow(dialog); }; +dialog.PreviewKeyPressed += (s, e) => { if (e.KeyInfo.Key == ConsoleKey.Escape) system.CloseModalWindow(dialog); }; +dialog.OnClosed += (_, _) => tcs.TrySetResult(chosen); +dialog.AddControl(list); +system.AddWindow(dialog); +dialog.FocusControl(list); +return await tcs.Task; +``` + +- Close a **modal** with `system.CloseModalWindow(window)` (not `CloseWindow`) so the modal stack unwinds. +- Clamp every dialog to the terminal so content can't overflow it: + +```csharp +(int W, int H) ClampSize(ConsoleWindowSystem s, int dw, int dh) => ( + Math.Clamp(dw, 40, Math.Max(40, s.DesktopDimensions.Width - 4)), + Math.Clamp(dh, 10, Math.Max(10, s.DesktopDimensions.Height - 4))); +``` + +- Use a **toast** (`system.ToastService.Show(msg, NotificationSeverity.Success)`) for don't-interrupt feedback; a **modal** for must-read/must-acknowledge. Disable Enter/Esc-close while a modal is running a long task. + +## Live external-process output + +Run a cancellable process and stream its output into a modal without leaving the +TUI: a `ScrollablePanel` + `MarkupControl`, lines guarded by a `lock` and +republished as an immutable snapshot via `system.EnqueueOnUIThread(() => +markup.SetContent(snapshot))`. Tick the window title with elapsed seconds from a +`Timer`, also through `EnqueueOnUIThread`. Escape untrusted output before it +enters markup (below). This is the canonical pattern for any long/cancellable +external work. + +## Semantic color layer (no hardcoded color in screens) + +Route every color through one place, never hardcode hex in screen code: + +1. A `Palette` of hex constants. +2. A theme derived once: `Theme.From(new ModernGrayTheme()).WithName("app").With(t => { /* set ~30 ITheme slots from Palette */ }).Build()`, passed to the `ConsoleWindowSystem(driver, theme, options)` constructor. (`Theme.FromPalette(...)` is the shortcut when you don't need slot-level control.) +3. A `Styles` helper module of semantic markup wrappers (`Accent`, `Title`, `Muted`, `Danger`, `Key`, `Hints`, `Section`, `Rule`) that every screen uses instead of raw `[color]` tags. Wrap primary content explicitly — unstyled markup falls back to control defaults. + +Apply control-level theming with a semantic `Role` (`ColorRole`: Primary, +Success, Danger, …) so a control's colors come from the theme. + +## Escape untrusted text, always + +Any string the app didn't author — process stdout, model names, user input — +must be escaped before entering a markup control, or stray `[...]` is parsed as +tags and can throw at render time: + +```csharp +static string EscapeMarkup(string t) => t.Replace("[", "[[").Replace("]", "]]"); +// or: MarkupParser.Escape(t) +static int VisibleWidth(string markup) => /* strip [tags] */ ...; // for alignment math +``` + +Add self-authored markup verbatim; escape everything else. + +## Threading discipline (never block the UI thread) + +`system.Run()` is a single cooperative UI loop. Never call `.Result` / `.Wait()` +/ `Thread.Sleep` / sync I/O in a handler — it freezes rendering and input and +trips the watchdog. `await` real async work; marshal any resulting UI mutation +with `system.EnqueueOnUIThread(...)` (fire-and-forget) or `system.InvokeAsync(...)` +(marshal + await). Turn on `InstallSynchronizationContext: true` so `await` +continuations in handlers resume on the UI thread automatically. See +`references/architecture.md`. + +## Never crash the TUI + +Wrap every fire-and-forget action (navigation, hotkey handler, menu pick) so a +thrown exception can't tear down the app: treat `OperationCanceledException` as a +quiet cancel; for anything else, log it, show a toast +(`NotificationSeverity.Danger`), and return to a safe screen (`PopToRoot()`). + +## Checklist for a polished app + +- One maximized window; screens swap content; you own the header/breadcrumb/hints. +- Every screen: a hint bar matching its keys, an actionable empty state, `Esc` goes back. +- All color via a `Styles`/`Palette`/theme layer — no hex in screens. +- Every dialog clamped to `DesktopDimensions`; modals closed with `CloseModalWindow`. +- Untrusted text escaped; UI mutations from background work marshalled. +- Every async action wrapped so it can never crash the loop. diff --git a/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/architecture.md b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/architecture.md new file mode 100644 index 0000000..c970615 --- /dev/null +++ b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/architecture.md @@ -0,0 +1,119 @@ +# SharpConsoleUI architecture + +How the framework renders, lays out, threads, and stays AOT-safe. This governs +the correctness rules an agent must respect when writing SharpConsoleUI code. + +## Rendering pipeline + +`ConsoleWindowSystem.Run()` drives a tight main loop (frame-limited, ~60 FPS by +default via `FrameDelayMilliseconds`). Each frame `ProcessOnce()` handles input, +drains queued events, and conditionally renders. Rendering is a multi-stage, +double-buffered pipeline with dirty tracking at three levels (a per-window +`PendingWork` intent accumulator of `Repaint`/`Relayout`, plus per-cell and +per-line dirty tracking) and occlusion culling. + +```mermaid +flowchart TD + A["Application: window creation, control invalidation"] --> B["ConsoleWindowSystem: event loop, frame limiting, UpdateDisplay()"] + B --> C["Renderer: build render list (Z-order), occlusion cull, 3 passes (Normal -> Active -> AlwaysOnTop)"] + C --> D["Window content (DOM): Measure -> Arrange -> Paint into per-window CharacterBuffer; Pre/PostBufferPaint compositor hooks"] + D --> E["ConsoleBuffer (screen double buffer): direct cell copy, diff-based (Cell/Line/Smart)"] + E --> F["Physical console: raw libc write (Unix) / Console.Write (Windows); ANSI generated once"] +``` + +Key components: `ConsoleWindowSystem` (loop + coordinator), `Renderer` +(multi-pass, occlusion), `VisibleRegions` (rectangle subtraction / overlap), +`Window` (content + DOM rebuild), `CharacterBuffer` (per-window cell buffer, +DOM paint target), `ConsoleBuffer` (screen-level double buffer, diff render), +`NetConsoleDriver` (console abstraction, I/O). + +Because output is a diff-based cell copy (only changed cells emit ANSI, once at +the end) rather than direct string writes to stdout, redraws stay flicker-free +and cheap — including over SSH, where latency tracks what actually changed, not a +full-screen repaint. + +## Compositor + +Each window owns its own `CharacterBuffer`. The compositor merges per-window +buffers with occlusion culling and per-cell Porter-Duff **alpha blending**, so +overlapping windows, animated desktop backgrounds, and tween/easing animations +composite cleanly. Gradient backgrounds propagate through transparent controls +automatically. `PreBufferPaint` / `PostBufferPaint` hooks let effects (blur, +fade, custom) run at the window-buffer stage. + +## DOM layout system + +Layout is a WPF-style two-pass model over a `LayoutNode` tree that mirrors the +control hierarchy: + +1. **Build tree** — `LayoutNode` tree mirrors controls. +2. **Measure (bottom-up)** — "how much space do you need?" Controls implement + `MeasureDOM(LayoutConstraints)` returning desired `LayoutSize`. +3. **Arrange (top-down)** — "here is your final rect." +4. **Paint** — `PaintDOM(buffer, bounds, clipRect, defaultFg, defaultBg)` renders + to the character buffer at computed positions. + +Track sizing (used by `GridControl` / `HorizontalGridControl`) is `GridLength`: +`Cells(n)` fixed, `Auto()` size-to-content, `Star(weight)` proportional share of +leftover space. Star tracks collapse to 0 when measured unbounded (WinUI +contract); set `ContentSizedStars = true` to self-size at measure and fill at +arrange. Cells support `rowSpan`/`colSpan`, `RowGap`/`ColumnGap`, and per-cell +alignment/margin; splitters make columns resizable. + +## Threading and async model + +The framework is **cooperative single-threaded**: one UI thread (the caller of +`Run()`) owns all rendering and event dispatch. + +| Runs ON the UI thread | Runs OFF it | +|---|---| +| Input dispatch (`ProcessKey` / `ProcessMouseEvent`) | `Window.WindowThreadDelegateAsync` (background `Task`) | +| `Button.Click` and other control handlers | `Task.Run(...)` — anything you push off-thread | +| Paint hooks (`PreBufferPaint` / `PostBufferPaint`) | Timers, file watchers, socket receive loops | +| Window lifecycle (`Activated`, `OnResize`) | | +| Actions marshalled via `EnqueueOnUIThread` / `InvokeAsync` | | + +Golden rules: + +- **Never block the UI thread.** Do not call `.Result` / `.Wait()` / + `.GetAwaiter().GetResult()` inside a handler. With a UI `SynchronizationContext` + installed, an awaited continuation is posted back to the UI thread; if that + thread is blocked waiting on the same task, the continuation never runs and the + loop deadlocks. A watchdog detects the stall. +- **Marshal UI mutations from background threads** with `EnqueueOnUIThread` / + `InvokeAsync`. By default (`InstallSynchronizationContext = false`) awaited + continuations resume on the thread pool, so you marshal back yourself. +- Opt-in `ConsoleWindowSystemOptions.InstallSynchronizationContext = true` makes + `async` handlers resume on the UI thread automatically (WinForms/WPF model). + It is off by default so upgrading apps that block on async freeze-then-recover + instead of deadlocking; it is slated to become the default in a future major + version, so writing `await`-based handlers now is correct under both. +- Per-window opt-in `WithWindowThreadOnUI(...)` runs a window's async delegate on + the UI thread (no marshalling needed) but must never block it; use + `WithAsyncWindowThread` for CPU-bound/blocking work and marshal mutations. A + window may have only one window thread. +- Most notification events have an async twin `Async` (delegate + `AsyncEventHandler`); subscribe to `Click` or `ClickAsync` (or both). + Prefer async handlers when you need to await I/O: `await task`, not `.Result`. +- Thread checks work regardless of the option: `IsOnUIThread` is the + `InvokeRequired` analogue (`!IsOnUIThread` ≡ WinForms `InvokeRequired`), + `VerifyAccess` asserts UI-thread affinity, and `SynchronizationContextInstalled` + reports the *resolved* async mode after `Run()` starts. The WinForms + check-then-marshal pattern translates directly: + `if (!system.IsOnUIThread) system.EnqueueOnUIThread(() => UpdateUI()); else UpdateUI();` + +## NativeAOT + +The library is marked `true` (full trim/AOT +analyzer suite on) and a Release build produces zero `IL` warnings, with no +`[RequiresUnreferencedCode]` / `[RequiresDynamicCode]` in the public surface. CI +publishes a native `AotSmoke` binary (`PublishAot=true`, +`IlcTreatWarningsAsErrors=true`) on every push and runs it headlessly, exercising +the broad control set plus the real AOT-risk paths (`[markdown]`/Markdig, syntax +highlighters, `SpectreRenderableControl`, `ImageControl`/ImageSharp, the +data-binding engine, `[gradient=…]`). Under NativeAOT, expression-tree binding +falls back to the LINQ interpreter rather than `Reflection.Emit`. + +Caveats: this guarantees the *library* won't break your AOT publish — your own +reflection/dynamic-code/serialization is still your responsibility. +`HtmlControl` is the one documented AOT exception. diff --git a/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/controls.md b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/controls.md new file mode 100644 index 0000000..67ed736 --- /dev/null +++ b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/controls.md @@ -0,0 +1,89 @@ +# SharpConsoleUI control reference + +SharpConsoleUI ships 40+ built-in controls. All implement `IWindowControl` and +are added to a window (or container) with `AddControl(...)`. Interactive controls +add `IInteractiveControl` (keyboard), `IFocusableControl` (focus), and/or +`IMouseAwareControl` (mouse). Most controls implement `IRoleableControl`, so a +semantic `Role` (a `ColorRole` value: Primary, Success, Danger, …) pulls colors +from the active theme instead of hand-set colors. Prefer the `Controls` static factory + fluent +builders (`Controls.Button("Quit").OnClick(...).Build()`). + +Common properties on every control: `Name` (for `FindControl(name)`), +`Visible`, `Container`, `Tag`, and layout properties (`Width`, `Margin`, +`Alignment`, `StickyPosition`). To move focus programmatically, the simplest call +is `window.FocusControl(control)` (a convenience over `window.FocusManager.SetFocus(...)`). + +## Text and markup + +Use when: displaying styled text, editing text, or rendering markdown/HTML. + +- `MarkupControl` — `Controls.Markup()` / `Controls.Markup("[bold]hi[/]")`. Rich `[tag]text[/]` styling, clickable/keyboard links (`[link=url]`), inline `[markdown]`, `[spinner]`, `[gradient=…]`; optional border/header via `.WithBorder()`. The default label control. Shorthand for a one-line static label: `Controls.Label("[bold]text[/]")` returns a pre-wrapped `MarkupControl`. +- `MultilineEditControl` — multi-line editor with syntax highlighting (13 languages via `SyntaxHighlighters.For(...)`), gutter, find/replace, undo/redo, word wrap. +- `PromptControl` — single-line text input; Enter events, validation, max length. +- `HtmlControl` — parse & render HTML (images, links, tables). One documented NativeAOT caveat. +- `FigleControl` — large ASCII-art (Figlet) text in multiple fonts. + +## Input + +Use when: capturing user actions or values. + +- `ButtonControl` — `Controls.Button("Text").OnClick((sender, e, win) => …)`. Focusable; Enter/click fires `Click`. The `win` arg reaches sibling controls via `FindControl()`. +- `CheckboxControl` — toggle with label; checked/unchecked change events. +- `RadioControl` — single-select group via typed `RadioGroup`; `Required`/`AllowDeselect`, cross-layout grouping. +- `DropdownControl` — click-to-expand selection list; keyboard nav; renders via portal. +- `SliderControl` / `RangeSliderControl` — value slider / dual-thumb range slider; step/large-step, keyboard + mouse drag; `RangeSlider` enforces `MinRange`. +- `DatePickerControl` / `TimePickerControl` — locale-aware pickers; segmented editing, calendar popup, 12h/24h, min/max. +- `FormControl` — labeled two-column form; typed field overloads (text/multiline/checkbox/dropdown/radio/slider), sections, validation, `GetValues`/`Submit`/`Submitted`. Also loadable from declarative XML (see `references/recipes.md`). + +## Data and collections + +Use when: showing rows, lists, or hierarchies. + +- `TableControl` — interactive data grid: virtual data, sorting, filtering (AND/OR), inline editing, multi-select, cell navigation, scrollbars. +- `ListControl` — scrollable single-select list with item activation and keyboard nav. +- `TreeControl` — hierarchical tree; expand/collapse, selection, keyboard nav. +- `GridControl` — WinUI-``-style 2D layout: Fixed/Auto/Star tracks, row/col spans, gaps, any control per cell. `Controls.Grid()`; see the grid recipe in `references/recipes.md`. +- `HorizontalGridControl` — single-row multi-column layout with variable-width columns and splitters. + +## Navigation and layout + +Use when: organizing screens, panes, and chrome. + +- `MenuControl` — menu bar with dropdowns/submenus, separators, keyboard shortcuts. +- `NavigationView` — WinUI-inspired sidebar nav + content area; responsive Expanded/Compact/Minimal modes; content factories. +- `TabControl` — multi-page tabs; keyboard/mouse switching; per-tab state preserved. +- `WizardControl` / `FlowControl` — run a multi-step wizard / a composed flow inline in a pane (vs. modal); `wizard.Run(Flow.Wizard()…)`. +- `CollapsiblePanel` — click-to-expand container; can also serve as a plain panel via `.NonCollapsible()` / `.HideHeader()`. +- `ScrollablePanelControl` — vertical scrolling content area hosting multiple controls (compose with `GridControl` for the ``-in-`` pattern). +- `PanelControl` — bordered container hosting child controls. +- `ToolbarControl` — horizontal button toolbar; auto-height, wrapping, Tab nav. +- `StatusBarControl` — three-zone (left/center/right) status bar; clickable items, shortcut hints. +- `RuleControl` / `SeparatorControl` — horizontal rule/separator, optional title. Factory `Controls.Rule()` / `Controls.RuleBuilder()` (a heavily used divider in real apps). + +## Charts and status + +Use when: showing metrics, progress, or logs. + +- `LineGraphControl` — multi-series line graph; braille/ASCII modes, gradients, Y-axis labels, live updates. +- `BarGraphControl` — horizontal bar graph with gradient thresholds and value labels. +- `SparklineControl` — compact time-series sparkline (block/braille/bidirectional). +- `SpinnerControl` — animated indeterminate-progress spinner; presets or custom frames; also the inline `[spinner]` markup tag. +- `ProgressBarControl` — determinate progress bar; `Controls.ProgressBar()`, `Value` 0.0–1.0 (single solid fill; use `CanvasControl` for a gradient bar). +- `LogViewerControl` — log viewer with auto-scroll, filtering, severity colors. + +## Drawing and media + +Use when: custom graphics, images, or video. + +- `CanvasControl` — free-form drawing surface; 30+ primitives, retained & immediate modes, thread-safe async painting. +- `ImageControl` — image display via Kitty/WezTerm/Ghostty full-resolution with half-block fallback; PNG/JPEG/BMP/GIF/WebP/TIFF; async load. (Not `IRoleableControl`.) +- `VideoControl` — terminal video player; Kitty graphics + half-block/ASCII/braille fallbacks (auto-detected); FFmpeg decode; overlay bar; looping. +- `SpectreRenderableControl` — wrap and display any Spectre.Console renderable (Table, Tree, Panel, Chart, …). +- `ChatTranscriptControl` — agent/chat transcript: role-tagged messages, token-by-token streaming, collapsible tool messages, thinking indicator. + +## Terminal and portal (special) + +- `TerminalControl` — PTY-backed terminal emulator; full xterm-256color, keyboard/mouse passthrough, auto-resize. Two build paths: `Controls.Terminal("bash"|"pwsh"|…).Open(ws)` opens it in its own window; or `Controls.Terminal().WithWorkingDirectory(dir).WithExe(...).WithArgs(...).Build()` returns a `TerminalControl` you place like any control (e.g. `tabControl.AddTab("Shell", term)`). Default exe: `bash` on Linux, `cmd.exe` on Windows. Runs on **Linux and Windows 10 1809+** (openpty on Linux, ConPTY on Windows); throws `PlatformNotSupportedException` elsewhere. **On Linux you must call `PtyShim.RunIfShim(args)` as the very first line of `Main`** (before any UI init) or the child process never starts — it is a safe no-op on Windows, so keep it in cross-platform code. +- `PortalContentContainer` — host child controls in [portal overlays](https://nickprotop.github.io/ConsoleEx/) (dropdowns, menus, tooltips) with mouse/keyboard routing and focus tracking. See the portal recipe in `references/recipes.md`. + +For wiring these into windows and layouts, see `references/recipes.md`. diff --git a/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/features.md b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/features.md new file mode 100644 index 0000000..2c744dd --- /dev/null +++ b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/features.md @@ -0,0 +1,117 @@ +# SharpConsoleUI visual features + +Gradients, transparency/compositing, animations, desktop background, and +image/video/syntax rendering. Exact API names are as they appear in the docs. + +## Gradients + +Built on `ColorGradient`. Works in text (markup) and window/desktop backgrounds. + +- Markup tag: `[gradient=name]`, `[gradient=c1->c2]`, `[gradient=c1->c2->c3]` (separator `->` or `→`; nested `[bold]`/`[underline]` preserved). +- `ColorGradient.Predefined["spectrum"|"warm"|"cool"]`; `ColorGradient.FromColors(Color, Color, …)`; `ColorGradient.Parse("red->blue" | "#FF0000->#0000FF" | "spectrum")`; `.Interpolate(t)` for a single sampled color. +- `GradientDirection`: `Horizontal`, `Vertical`, `DiagonalDown`, `DiagonalUp`. +- Window backgrounds: `WindowBuilder.WithBackgroundGradient(gradient, direction)` or `window.BackgroundGradient = new GradientBackground(gradient, direction)` (set `null` to remove; painted via `PreBufferPaint`, controls paint on top). +- Direct buffer fill: `buffer.FillGradient(LayoutRect, gradient, direction)`. +- Gradients can fade to transparent — `ColorGradient.BlendColors` interpolates all 4 RGBA channels: `ColorGradient.FromColors(Color.Cyan, Color.FromArgb(0,0,255,255))`. + +```csharp +new WindowBuilder(ws) + .WithBackgroundGradient(ColorGradient.FromColors(Color.DarkBlue, Color.Black), GradientDirection.Vertical) + .Build(); +``` + +## Alpha blending and transparency + +Per-cell Porter-Duff "over" compositing; the result is always opaque (A=255). + +- Colors: `new Color(r,g,b,a)`, `Color.Transparent` (A=0), `Color.Red.WithAlpha(128)`; `Color.Blend(src, dst)` (src.A==255→src, ==0→dst, else RGB blend). Foreground blends against the *resolved* background. +- Transparent window: `WindowBuilder.WithBackgroundColor(new Color(r,g,b,a))` with a<255. Default (no brush) = true transparency: empty cells bubble up characters from below with a parabolic foreground fade. +- `TransparencyBrush` overrides the compositing style (not the color/alpha): `Acrylic()` (Gaussian fg+bg blend, `Acrylic(fadeExponent:…)`/`Acrylic(textCoverage:…)`), `Mica()` (color blend, no character bubble-up), `Tinted()` (flat bg overlay), `WithCustom((topCell, cellBelow, alpha) => new Cell(...))`. Apply via `WindowBuilder.WithTransparencyBrush(...)`. +- Control-level alpha via markup: `[#RRGGBBAA]text[/]` (fg), `[on #RRGGBBAA]text[/]` (bg). +- Terminal-native transparency: set `windowSystem.DesktopBackgroundColor = Color.Transparent` and/or window `.WithBackgroundColor(Color.Transparent)` — emits ANSI `\x1b[49m` (terminal default bg) when a cell's alpha is 0. Needs emulator support (Kitty `background_opacity`, Alacritty `opacity`, WezTerm `window_background_opacity`). `ConsoleWindowSystemOptions.TerminalTransparencyMode`: `PreserveWindowColor` (default) vs `PreserveTerminalTransparency`. +- Perf: opaque windows take a zero-overhead fast path; transparent windows pay a per-cell `ResolveCellBelow` cost that scales with overlap. Keep alpha ~160–220 for a usable effect. + +```csharp +new WindowBuilder(ws) + .WithBackgroundColor(new Color(0, 20, 60, 180)) + .WithTransparencyBrush(TransparencyBrush.Mica()) + .BuildAndShow(); +``` + +## Compositor effects + +Hook the window's `CharacterBuffer` before/after control painting for blur, fade, +glow, overlays, or screenshots. + +- Order per frame: rebuild DOM → layout → `Buffer.Clear()` → **`PreBufferPaint`** → `PaintDOM()` → **`PostBufferPaint`** → convert to lines. +- `Window.Renderer` (`WindowRenderer?`) exposes `Buffer` and the two events. `PreBufferPaint` is for backgrounds (fires after clear); `PostBufferPaint` is for post-processing (fires after controls, within the render lock — defer heavy work via `Task.Run`). Delegate signature: `(CharacterBuffer buffer, LayoutRect dirtyRegion, LayoutRect clipRect)`. +- `CharacterBuffer`: `GetCell/SetCell(x,y,…)`, `WriteString(x,y,text,fg,bg)`, `CopyFrom(other, rect)`, `CreateSnapshot()` → deep-copied `BufferSnapshot` (safe for screenshots/recording). `Cell` has `Rune Character`, `Foreground`, `Background`, `Decorations`, `Dirty`, `IsWideContinuation`, `Combiners` — always skip/preserve `IsWideContinuation` cells so you don't split wide (CJK/emoji) pairs. +- Best practice: process only `dirtyRegion`; use a temp buffer + `CopyFrom` to avoid feedback loops. `ColorBlendHelper.ApplyColorOverlay(buffer, color, intensity, …)` for tints. `CanvasControl` is the per-control alternative (local coords, `BeginPaint()/EndPaint()`, 30+ drawing primitives, thread-safe). + +## Animations + +Time-based tweens integrated into the render loop via `ConsoleWindowSystem.Animations` (`AnimationManager`). + +- `ws.Animations.Animate(from, to, duration, easing?, onUpdate, onComplete?)` — overloads for `float`/`int`/`byte`/`Color` or a custom `IInterpolator`. Also `HasActiveAnimations`, `ActiveCount`, `Cancel(anim)`, `CancelAll()`, `Add(IAnimation)`. +- `EasingFunctions`: `Linear`, `EaseIn`, `EaseOut`, `EaseInOut`, `Bounce`, `Elastic`, `SinePulse` (flash/pulse). Any `t => …` delegate works as custom easing. +- `WindowAnimations` transitions (use `PostBufferPaint` internally): `FadeIn(window, …)`, `FadeOut(window, …)`, `SlideIn(window, SlideDirection, …)`, `SlideOut(window, SlideDirection, …)`. `SlideDirection`: `Left/Right/Top/Bottom`. +- Delta capped at `MaxFrameDeltaMs` (33ms) so animations don't jump-complete after idle. Disable globally with `ConsoleWindowSystemOptions.EnableAnimations`. + +```csharp +WindowAnimations.FadeOut(window, onComplete: () => ws.CloseWindow(window)); +WindowAnimations.SlideIn(window, SlideDirection.Left, easing: EasingFunctions.Bounce); +``` + +## Desktop background + +The area behind all windows, managed by `DesktopBackgroundService` (cached buffer, +re-rendered only on change). Layers: base fill → gradient overlay → pattern overlay. + +- Startup: `ConsoleWindowSystemOptions(DesktopBackground: DesktopBackgroundConfig.FromGradient(gradient, direction))`. Runtime: `windowSystem.DesktopBackground = DesktopBackgroundConfig.FromGradient(...)` (set `null` to revert). +- Patterns: `DesktopBackgroundConfig.FromPattern(DesktopPattern)`. Built-ins in `DesktopPatterns`: `Checkerboard`, `Dots`, `HatchDown`/`HatchUp`, `Crosshatch`, `LightShade`/`MediumShade`/`DenseShade`, `HorizontalLines`, `VerticalLines`, `Grid`. Custom: `new DesktopPattern(char[,])` with optional per-cell color grids. +- Animated: set `PaintCallback` on `DesktopBackgroundConfig` (runs on the render thread every `AnimationIntervalMs`, default 100ms — keep it fast). Built-in effects: `DesktopEffects.ColorCycling(...)`, `DesktopEffects.Pulse(...)`, `DesktopEffects.DriftingGradient(...)`. + +```csharp +windowSystem.DesktopBackground = DesktopEffects.ColorCycling(cycleDurationSeconds: 12.0, direction: GradientDirection.Vertical); +``` + +## Syntax highlighting + +Lexical (token-based) highlighting; namespace `SharpConsoleUI.Highlighting`. 13 +built-ins (`csharp`/`cs`, `bash`/`sh`/`shell`/`zsh`, `json`, `javascript`/`js`, +`css`, `html`, `xml`, `yaml`/`yml`, `razor`/`cshtml`, `dockerfile`/`docker`, `sln`, +`diff`/`patch`, `markdown`/`md`), all `ISyntaxHighlighter`. + +- Registry: `SyntaxHighlighters.For(lang)` → `ISyntaxHighlighter?`, `Register(lang, highlighter)` (additive), `Has(lang)`. +- Consumers: Markdown fenced code blocks (auto), and `MultilineEditControl` via `.WithSyntaxHighlighter(SyntaxHighlighters.For("csharp"))` or the `SyntaxHighlighter` property. +- Custom: implement `ISyntaxHighlighter.Tokenize(line, lineIndex, startState)` → `(tokens, endState)`; register to reach both consumers. + +## Image rendering + +`ImageControl` over a `PixelBuffer`, auto-selecting Kitty graphics (full-res) or +Unicode half-block fallback. + +- Load: `PixelBuffer.FromFile(path)` / `FromStream(stream)` / `FromImageSharp(...)` / `FromArgbArray(...)` (ImageSharp: PNG/JPEG/BMP/GIF/TIFF/TGA/PBM/WebP). `Controls.Image(pixelBuffer)`. +- `ImageControl.Source` (`PixelBuffer?`), `ScaleMode` (`ImageScaleMode`: `Fit` default, `Fill`, `Stretch`, `None`); `InvalidateImageCache()` to swap at runtime. +- Kitty protocol (Kitty/WezTerm/Ghostty) is detected automatically; half-block (`▀`) fallback gives 2× vertical resolution elsewhere. Cap height explicitly (e.g. `.Height = 12`) so an image doesn't crowd out other controls. + +```csharp +window.AddControl(Controls.Image(PixelBuffer.FromFile("photo.png"))); +``` + +## Video playback + +`VideoControl` plays files/streams via an FFmpeg subprocess (FFmpeg must be on +PATH; otherwise the control shows an in-place install hint). + +- `Controls.Video()` / `Controls.Video("path")`; builder `.WithSource(...)`/`.WithFile(...)`, `.WithRenderMode(mode)`, `.WithTargetFps(n)`, `.WithLooping()`, `.WithOverlay()`, `.Fill()`, `.OnPlaybackStateChanged(...)`, `.OnPlaybackEnded(...)`. +- `VideoRenderMode`: `Auto` (default — Kitty where capable, else HalfBlock), `Kitty`, `HalfBlock`, `Ascii`, `Braille`. Requesting Kitty on a non-Kitty terminal silently falls back to HalfBlock. +- Methods: `Play()`, `Pause()`, `TogglePlayPause()`, `Stop()`, `CycleRenderMode()`, `PlayFile(path)`, `Stream(url)` (HTTP/HLS/RTSP/RTMP; live streams have no seek, `DurationSeconds`=0). Keys when focused: Space play/pause, M cycle mode, L loop, R refresh, Esc stop. +- Threading: playback runs on a background thread and calls `Container?.Invalidate(Invalidation.Relayout)`; marshal state changes to the UI thread with `EnqueueOnUIThread`. Dispose on window close. + +```csharp +var video = Controls.Video("video.mp4").Fill().Build(); +window.AddControl(video); +video.Play(); +window.OnClosed += (_, _) => { video.Stop(); video.Dispose(); }; +``` diff --git a/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/markup.md b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/markup.md new file mode 100644 index 0000000..31680b0 --- /dev/null +++ b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/markup.md @@ -0,0 +1,130 @@ +# SharpConsoleUI markup language + +Markup is a first-class, cross-cutting feature: a native Spectre-compatible +`[tag]text[/]` parser that produces typed `Cell` structs directly (no ANSI +intermediate, no external dependency). **It works everywhere text is rendered** — +`MarkupControl` labels, window titles, status bars, table cells, tree nodes, +list items, tooltips, and any control that renders markup — and the same string +can mix colors, decorations, links, spinners, gradients, and Markdown. + +Prefer markup for all styled text instead of manual color handling. + +## Basic syntax + +``` +[style]text[/] +``` + +- `[style]` = any space-separated combination of color names, hex/RGB colors, and decorations. +- `[/]` closes the most recent opening tag; tags nest and `[/]` pops the innermost. +- Escape literal brackets by doubling: `[[` → `[`, `]]` → `]` (e.g. `items[[0]]` shows `items[0]`). + +```csharp +"[bold red]Error:[/] Something went wrong" +"[italic underline blue]Fancy blue text[/]" +"[bold]Bold [red]bold+red[/] just bold again[/]" // nesting +``` + +## Colors + +- **Named:** basic 16 (`red`, `lime`, `cyan`/`aqua`, `grey`/`gray`, …) plus aliases (`darkred`→maroon, etc.) and a large extended set (`orange1`, `deepskyblue1`, `hotpink`, `gold1`, …). +- **Grey scale:** `grey0` (black) … `grey100` (white). +- **Hex:** `[#RRGGBB]`, `[#RGB]`, `[#RRGGBBAA]` (8-digit sets foreground alpha, Porter-Duff "over" the cell background; `AA=00` invisible, `AA=FF` opaque). +- **RGB:** `[rgb(r,g,b)]`. +- **Background:** `on` — `[white on red]`, `[on green] OK [/]`. + +```csharp +"[#FF8000]Orange[/]" +"[#00DCDC80]Semi-transparent cyan[/]" // 50% opacity, composites onto background +"[white on red]Error banner[/]" +``` + +### Fill background to end of line + +`[fillwidth]` is a **self-closing** marker (no `[/]`, emits nothing) placed at +the end of a line; it extends that line's trailing background to the full width +(turns a per-line tint into a solid block). Only affects row-painting hosts like +`MarkupControl`; a no-op in plain parsing. Escape as `[[fillwidth]]`. + +```csharp +"[on grey19] Build succeeded [/][fillwidth]" // full-width shaded banner +``` + +## Text decorations + +`bold`, `dim`, `italic`, `underline`, `strikethrough` (`strike`), `invert` +(`reverse`), `blink` (`slowblink`/`rapidblink`). Combine freely: +`[bold yellow on darkblue]Warning[/]`. + +## Inline spinner + +`[spinner]` embeds an animated spinner glyph inline in any markup text — it +animates wherever markup renders (labels, status bars, titles, table cells) with +no separate control. Styles: `[spinner]`/`braille` (default), `circle`, `dots`, +`line`, `arc`, `bounce`, `star`, `growvertical`, `growhorizontal`, `toggle`, +`arrow`, `bouncingbar`, `aestheticbar`, `brailledots`, `dotsbounce`. Override +speed with a trailing ms value: `[spinner dots 250]`. The glyph inherits the +surrounding color scope and reserves a fixed width (no reflow). Requires a +running `ConsoleWindowSystem` with animations enabled; renders a static glyph +otherwise. Escape as `[[spinner]]`. For a placeable control, use `SpinnerControl`. + +```csharp +"[yellow]Saving [spinner][/]" +``` + +## Inline Markdown + +`[markdown]…[/]` parses its inner content as Markdown (via Markdig) and renders +it as native markup — so it works anywhere markup is accepted and mixes with +ordinary markup. Supports headings (H1–H6), emphasis, inline code, links (text +shown, URL dropped), bullet/numbered/nested lists, blockquotes, horizontal rules, +fenced/indented code blocks, and GitHub-style pipe tables. + +- **Non-nesting:** a region ends at the *first* `[/]`. Unclosed → renders to end of string. Escape as `[[markdown]]`. +- **Code-block highlighting:** a fenced block with a language hint is auto syntax-highlighted. Built-ins include `csharp`/`cs`, `bash`/`sh`, `json`, `javascript`/`js`, `css`, `html`, `xml`, `yaml`/`yml`, `razor`, `dockerfile`, `sln`, `diff`, `markdown`/`md`. No/unknown hint → flat shaded block. Register more globally with `SyntaxHighlighters.Register("toml", …)` or per-style via `MarkdownStyle.CodeHighlighters`. +- **Styling** via the `SharpConsoleUI.Configuration.MarkdownStyle` record (theme-agnostic — the parser is static): `CodeForeground`/`CodeBackground`, `QuoteColor`, `LinkColor`, `BorderColor`, `TableRowSeparators` (false=header rule only, true=full grid), `BulletGlyph`, `ListIndent`, `QuoteGlyph`, `H1Color`…`H6Color`. Set `MarkdownStyle.Default` globally or `.WithMarkdownStyle(s => s with { … })` per build. + +```csharp +var c = Controls.Markdown("# Report\n\n**Status:** OK\n\n- one\n- two") + .WithMarkdownStyle(s => s with { LinkColor = Color.Cyan1, TableRowSeparators = true }) + .Build(); +window.AddControl(c); +``` + +Fluent helpers: `Controls.Markdown(text)`, `.AddMarkdown(text)` / `.WithMarkdown(text)`, +`MarkupControl.SetMarkdown(text)`, `MarkupControl.MarkdownStyle`. + +## Updating MarkupControl content + +`MarkupControl` updates live with a `StringBuilder`/`Console`-style API: + +- `Append(text)` — inline append onto the current last line (new line only at embedded `\n`). +- `AppendLine(text)` — append as its own new line. +- `AppendLines(lines)` — each item as its own line. +- `SetContent(lines)` — replace all content. + +`Append`/`AppendLine` are the recommended pair (`AppendText`/`AppendInline` remain +as aliases). The builder mirrors this: `Controls.Markup().Append(...).AddLine(...)`. + +```csharp +var c = new MarkupControl(new List()); +c.Append("[green]●[/] "); +c.Append("all healthy"); // -> "● all healthy" (same line) +c.AppendLine("[grey]done[/]"); // -> next line +``` + +## MarkupParser API (SharpConsoleUI.Parsing) + +For programmatic parsing/measuring outside a control: + +- `MarkupParser.Parse(text, defaultFg, defaultBg)` → `List` (char + fg + bg + decoration). +- `MarkupParser.ParseLines(text, width, defaultFg, defaultBg)` → `List>` with word-wrap; style stack carries across line breaks. +- `MarkupParser.StripLength(text)` → visible length (tags stripped; max line length for multi-line). +- `MarkupParser.Truncate(text, maxVisible)` → truncates to visible length, preserving/closing tags. +- `MarkupParser.Escape(text)` → doubles brackets so plain text isn't interpreted (`array[0]` → `array[[0]]`). +- `MarkupParser.Remove(text)` → strips all tags to plain text (escaped brackets become single). + +Always run untrusted/dynamic text through `MarkupParser.Escape(...)` before +embedding it in markup, or stray brackets will be parsed as tags. + +For `[gradient=…]` see `references/features.md`. diff --git a/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/recipes.md b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/recipes.md new file mode 100644 index 0000000..d5fd8f5 --- /dev/null +++ b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/recipes.md @@ -0,0 +1,291 @@ +# SharpConsoleUI recipes + +Verified patterns for wiring windows, layout, dialogs, portals/toasts, and forms. +All code uses APIs from the SharpConsoleUI docs; do not invent method names. +Assumes `windowSystem` is a `ConsoleWindowSystem` and `using` directives for +`SharpConsoleUI`, `SharpConsoleUI.Builders`, `SharpConsoleUI.Controls`, +`SharpConsoleUI.Drivers`. + +Two idioms real apps use that keep code shorter: + +- **`.Build()`** — control builders have an implicit conversion to their control, + so `.Build()` is technically optional in an `AddControl(...)` argument. Real apps + still call it explicitly for clarity and because you usually keep the control in a + variable (to look it up, focus, or bind it later); prefer showing `.Build()`. +- **Run the loop off the entry thread:** `await Task.Run(() => windowSystem.Run())` + is the common way to start it so `Main` can stay `async`. + +If you use `TerminalControl` anywhere, make the **first line of `Main`** +`if (SharpConsoleUI.PtyShim.RunIfShim(args)) return 127;` — on Linux the host +re-launches itself as the PTY child, and this short-circuits that re-entry (a safe +no-op returning `false` on Windows). See `references/controls.md`. + +## Window builder configuration + +`WindowBuilder` is a fluent API; `.Build()` returns the window (add it with +`windowSystem.AddWindow(window)`), and `.BuildAndShow()` builds and shows in one +call. + +```csharp +var window = new WindowBuilder(windowSystem) + .WithTitle("My Window") + .WithSize(80, 25) + .AtPosition(4, 2) // or .Centered() + .WithMinimumSize(40, 10) + .WithColors(Color.White, Color.Grey11) + .WithPadding(1) // inner space; also (h, v) or new Padding(l,t,r,b) + .Movable(true) + .Resizable(true) + .Build(); +``` + +Border/chrome options: `.Borderless()` keeps the invisible 1-cell frame (still +interactive); `.Frameless()` reclaims the frame entirely (chrome-less, +non-interactive frame — content fills the whole rect). + +## Full-screen / single-window app + +SharpConsoleUI is well suited to full-screen TUIs, not just floating windows. +For a full-screen app the main window should have **no title bar and no title +buttons** — use `.Frameless()`, which reclaims the border/title frame entirely: +no title bar, no drag handle, no resize grip, and no title buttons, with content +filling the whole rect. Combine it with `.Maximized()` (size to the whole +desktop): + +```csharp +new WindowBuilder(windowSystem) + .Frameless() // no title bar, no title buttons — content owns the whole rect + .Maximized() // fills the terminal + .WithPadding(1) // optional breathing room + .AddControl(rootLayout) // e.g. a GridControl that owns the whole screen + .BuildAndShow(); +``` + +Do not use `.Borderless()` for this: it blanks the border but keeps the invisible +1-cell interactive frame. `.Frameless()` is the chrome-less, title-less choice for +a full-screen main window. + +Real single-window apps often keep a bordered window but lock it down instead of +going frameless — the same visual result with a title kept for the theme: + +```csharp +var system = new ConsoleWindowSystem( + new NetConsoleDriver(RenderMode.Buffer), + myTheme, // ITheme overload (see references/system.md) + new ConsoleWindowSystemOptions(InstallSynchronizationContext: true, + ShowTopPanel: false, ShowBottomPanel: false)); + +var window = new WindowBuilder(system) + .WithTitle("app") + .Maximized() + .Movable(false).Resizable(false).Closable(false).HideTitle() + .WithPadding(1, 0) + .Build(); +system.AddWindow(window); + +system.RegisterGlobalShortcut(ConsoleModifiers.Control, ConsoleKey.Q, () => system.RequestExit(0)); +var exitCode = system.Run(); // blocks; returns the code passed to RequestExit/Shutdown +``` + +`RegisterGlobalShortcut(modifiers, key, Action | Func)` registers an +app-wide hotkey; `system.RequestExit(code)` (or `system.Shutdown(code)`) ends the +loop and makes `Run()` return that code. `.HideTitle()` hides the title text while +keeping the bordered frame; `.Closable(false)` removes the close button. + +## Multi-window desktop + +Full-screen and multi-window are equally first-class. A `ConsoleWindowSystem` +owns any number of bordered windows that stack in z-order and can be +moved/resized/minimized/maximized. Add several windows; each is a `WindowBuilder` +that `.BuildAndShow()`s onto the desktop: + +```csharp +var explorer = new WindowBuilder(windowSystem) + .WithTitle("Explorer") + .WithBounds(2, 2, 40, 20) // x, y, width, height in desktop coords + .WithBorderStyle(BorderStyle.Rounded) + .AddControl(tree) + .BuildAndShow(); + +var editor = new WindowBuilder(windowSystem) + .WithTitle("Editor") + .WithBounds(44, 2, 60, 24) + .Movable(true) + .Resizable(true) + .AddControl(multilineEdit) + .BuildAndShow(); +``` + +Windows draw a border and title bar and are interactive by default (drag the +title to move, edges to resize). Border styles: `DoubleLine` (default), `Single`, +`Rounded`, `None` (invisible frame, still interactive), `Frameless` (no frame). +For a window the user must dismiss before returning to the rest, make it modal +with `.AsModal()` (or `.WithModal(true)`). Runtime state control: +`window.Maximize()`, `window.Minimize()`, `window.Restore()`; positions are in +desktop coordinates (excluding any status bars). + +Window state also has `.Minimized()` and `.WithState(WindowState.Normal | +Maximized | Minimized)`; at runtime `window.Maximize()`, `window.Minimize()`, +`window.Restore()`. Move/resize still work in code on a frameless window +(`SetPosition` / `SetSize`) — only the mouse grab surface is gone. + +## Grid layout with a data control + +`GridControl` (`Controls.Grid()`) is the 2D layout primitive. Tracks are +`GridLength.Cells(n)` (fixed), `.Auto()` (size-to-content), `.Star(weight)` +(proportional). Place controls explicitly with `Place(control, row, col, +rowSpan, colSpan)` or append in row-major order with `.Add(control)`. + +```csharp +var table = Controls.Table() + // ... configure columns/rows per TableControl ... + .Build(); + +var grid = Controls.Grid() + .Columns(GridLength.Cells(20), GridLength.Star(1), GridLength.Star(2)) + .Rows(GridLength.Auto(), GridLength.Star(1, min: 5)) + .RowGap(1) + .ColumnGap(2) + .Place(Controls.Markup("[bold]Report[/]").Build(), 0, 0, colSpan: 3) // header row + .Place(sidebar, 1, 0) + .Place(mainPanel, 1, 1) + .Place(table, 1, 2) + .Build(); + +window.AddControl(grid); +``` + +If a Star grid is placed in a content-sizing parent and renders as nothing, set +`grid.ContentSizedStars = true` (self-size at measure, fill at arrange). The grid +never scrolls; wrap it in a `ScrollablePanelControl` for the +``-in-`` pattern. + +## Built-in dialogs (confirm / prompt / progress) + +`Dialogs` (`SharpConsoleUI.Dialogs`) provides typed, themed modal dialogs usable +from any async handler — no flow setup required. + +```csharp +using SharpConsoleUI.Dialogs; + +button.ClickAsync += async (s, e) => +{ + bool ok = await Dialogs.ConfirmAsync(windowSystem, "Save changes", "Save before closing?"); + if (!ok) return; + + string? name = await Dialogs.PromptAsync(windowSystem, "Your name", + "What should we call you?", initial: "World"); + + string? result = await Dialogs.RunWithProgressAsync(windowSystem, + "Syncing", "Connecting…", + async (ct, progress) => + { + progress.Report("Downloading…"); + await Task.Delay(1000, ct); + return "done"; + }); +}; +``` + +`ConfirmAsync` returns `bool`, `PromptAsync` returns `string?` (null on cancel), +`RunWithProgressAsync` returns the worker's result. All take an optional +`NotificationSeverityEnum severity` and `Window? parent`. + +## Custom modal dialog with a result + +Once a dialog needs custom content or buttons beyond confirm/prompt/progress, the +common real-world pattern is a modal window plus a `TaskCompletionSource` that +`Run()`-style callers can `await`. Build the window `.AsModal()`, close it with +`window.Close()`, and complete the source with the result: + +```csharp +async Task AskNameAsync(ConsoleWindowSystem ws) +{ + var tcs = new TaskCompletionSource(); + + var input = Controls.Prompt("Name").WithName("name").Build(); + var dialog = new WindowBuilder(ws) + .WithTitle("Rename") + .WithSize(40, 8) + .Centered() + .AsModal() + .Build(); + + dialog.AddControl(input); + dialog.AddControl(Controls.Button("OK") + .OnClick((s, e, win) => { tcs.TrySetResult(input.Input); ws.CloseModalWindow(dialog); }) + .Build()); + dialog.AddControl(Controls.Button("Cancel") + .OnClick((s, e, win) => { tcs.TrySetResult(null); ws.CloseModalWindow(dialog); }) + .Build()); + + ws.AddWindow(dialog); + dialog.FocusControl(input); + return await tcs.Task; +} +``` + +Close a **modal** window with `ws.CloseModalWindow(window)` (not `CloseWindow`) so +the modal stack unwinds correctly; use `ws.CloseWindow(window)` for non-modal +windows. Resolve the result in `OnClosed` or in the button handler via a +`TaskCompletionSource` so the `await`ing caller gets it. Clamp a dialog's size to +the terminal with `ws.DesktopDimensions` before building if content can overflow. + +Reach for this only when the built-in `Dialogs` helpers above don't fit; they +cover the common confirm/prompt/progress cases with less code. For file/folder +selection use the built-in `FileDialogs` (namespace `SharpConsoleUI.Dialogs`): +`FileDialogs.ShowFilePickerAsync(ws, …)`, `FileDialogs.ShowFolderPickerAsync(ws, …)`, +`FileDialogs.ShowSaveFileAsync(ws, …)` — each returns `Task` (null on cancel). + +## Toasts and notifications + +Two systems, both on the `ConsoleWindowSystem` instance: + +```csharp +// ToastService — non-blocking, auto-dismissing corner overlays for transient status. +windowSystem.ToastService.Show("Saved successfully", NotificationSeverity.Success); +string id = windowSystem.ToastService.Show("Sync started", NotificationSeverity.Info); +// keep `id` to dismiss it later + +// NotificationStateService — title+message, optionally modal (blockUi: true) for +// messages the user must acknowledge. +windowSystem.NotificationStateService.ShowNotification("Error", "Disk full", NotificationSeverity.Error, blockUi: true); +``` + +Use `ToastService` for "Saved" / "Sync started"; use `NotificationStateService` +for errors/confirmations that must be read. + +## Portals (overlays) + +Portals render content above the normal layout, unclipped by parent containers — +the mechanism behind dropdowns, context menus, and tooltips. Most controls that +need overlays (e.g. `DropdownControl`, `MenuControl`) manage their own portals. +To host custom overlay content, use `PortalContentContainer`, whose children get +mouse/keyboard routing and focus tracking; portal nodes attach to the root +`LayoutNode` (`AddPortalChild`) and paint last, on top. Reach for a portal when +content must escape its container bounds; reach for `Dialogs` / notifications for +standard modal/transient messages. + +## Forms + +Build a labeled form imperatively with `FormControl`, or declaratively from XML +with `FormXml` (a thin, NativeAOT-safe call-through to the same runtime). + +```csharp +using SharpConsoleUI.Controls.Forms; + +var form = FormXml.FromXml(@" +
+ + + +"); + +window.AddControl(form); +``` + +The XML root must be `
`; fields (``, ``, ``, +``, ``, ``) map to the typed `FormControl` field +overloads, with structure elements `
`, ``, ``. Read values +via the `FormControl` API (`GetValues` / `Submit` / `Submitted`). Use +`FormXml.FromXmlFile(path)` to load from a file. diff --git a/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/system.md b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/system.md new file mode 100644 index 0000000..3bdc86c --- /dev/null +++ b/catalog/Frameworks/SharpConsoleUI/skills/sharpconsoleui/references/system.md @@ -0,0 +1,184 @@ +# SharpConsoleUI system, services, and patterns + +Application-level services, configuration, data binding, flows, distribution, and +when to choose this framework. Exact API names are as they appear in the docs. + +## State services (on `ConsoleWindowSystem`) + +Reached as properties on the `ConsoleWindowSystem` instance (except `FocusManager`, +which is per-`Window`): + +- `PanelStateService` — `TopPanel`/`BottomPanel`, `ShowTopPanel`/`ShowBottomPanel`, `TopStatus`/`BottomStatus` (set-only; updates first `StatusTextElement`), `MarkDirty()`. Shorthand: `windowSystem.BottomPanel`. +- `WindowStateService` — `Windows`, `ActiveWindow`, `IsDragging`/`IsResizing`; `RegisterWindow`, `BringToFront`, `GetWindowsInZOrder()`, `StartDrag/EndDrag`, … +- `window.FocusManager` (per-window; replaced the old system-wide focus service) — `FocusedControl`, `FocusPath`, `SetFocus(control, FocusReason)`, `MoveFocus(bool backward)`, `HandleClick(hit)`, `FocusChanged` event. +- `ModalStateService` — `HasModals`, `TopModal`, `ModalCount`; `PushModal`/`RemoveModal`, `IsModal`, `IsBlockedByModal`. `WindowBuilder.AsModal()` calls `PushModal` internally. +- `NotificationStateService` — `ShowNotification(title, message, severity, blockUi = false, timeout = 5000)`. Separate corner-toast system is `windowSystem.ToastService`. +- `ThemeStateService` — `CurrentTheme`, `SetTheme(ITheme)`, `SwitchTheme(string name)`, `ThemeChanged` event. +- `CursorStateService`, `InputStateService` — cursor visibility/position; key/mouse-button state queries. +- `PluginStateService`, `RegistryStateService` — see below (registry is null unless configured). + +```csharp +windowSystem.PanelStateService.TopStatus = "[bold cyan]Connected[/]"; +windowSystem.NotificationStateService.ShowNotification("Saved", "Doc saved", NotificationSeverity.Success); +``` + +## Panels (top/bottom bars) + +Composable screen-level bars built from **elements** via `PanelBuilder`, configured +through `ConsoleWindowSystemOptions.TopPanelConfig`/`BottomPanelConfig` +(`Func`). Each panel has `Left`/`Center`/`Right` zones. + +- `SharpConsoleUI.Panel.Elements` factories: `StatusText(text)`, `Separator()`, `TaskBar()` (Alt+1–9 window switch), `Clock()`, `Performance()`, `StartMenu()`, `Custom(name)`. Each has fluent config (`.WithColor`, `.WithFormat`, etc.). +- `StartMenuElement.RegisterAction(name, callback, category?, order?)`; a loaded plugin contributes actions via `IPlugin.GetActionProviders()` → `IPluginActionProvider` (`GetAvailableActions()` returning `ActionDescriptor` records, `ExecuteAction(name, context?)`). +- Runtime: `windowSystem.BottomPanel!.FindElement("startmenu")`, `panel.AddLeft/AddCenter/AddRight(...)`, `ClearLeft()`, `MarkDirty()`. +- When no config is supplied, default panels are created with a `StatusTextElement` so `TopStatus`/`BottomStatus` work out of the box. (Replaces the old `StatusBarOptions` model.) + +```csharp +var options = new ConsoleWindowSystemOptions( + TopPanelConfig: p => p.Left(Elements.StatusText("[bold cyan]My App[/]")).Right(Elements.Performance()), + BottomPanelConfig: p => p.Left(Elements.StartMenu()).Center(Elements.TaskBar()).Right(Elements.Clock())); +var windowSystem = new ConsoleWindowSystem(new NetConsoleDriver(RenderMode.Buffer), options: options); +``` + +## Constructing the window system + +`ConsoleWindowSystem` has four constructor overloads — pass the theme and/or a +custom driver at construction: + +- `new ConsoleWindowSystem(RenderMode, options?, registryConfiguration?)` +- `new ConsoleWindowSystem(IConsoleDriver, options?, registryConfiguration?)` +- `new ConsoleWindowSystem(IConsoleDriver, string themeName, options?, registryConfiguration?)` +- `new ConsoleWindowSystem(IConsoleDriver, ITheme, options?, registryConfiguration?)` + +Lifecycle: `system.Run()` blocks the calling thread and returns the exit code. +`system.RequestExit(code)` / `system.Shutdown(code)` end the loop. +`system.RegisterGlobalShortcut(ConsoleModifiers, ConsoleKey, Action | Func)` +registers app-wide hotkeys (e.g. Ctrl+Q to quit). `system.DesktopDimensions` +gives the usable desktop `Size` (for clamping window sizes). + +## Configuration + +`ConsoleWindowSystemOptions` (passed as `options:`): `EnablePerformanceMetrics`, +`EnableFrameRateLimiting`, `TargetFPS` (default 60; 0 = unlimited), +`ShowTopPanel`/`ShowBottomPanel`, `TopPanelConfig`/`BottomPanelConfig`, +`DesktopBackground`, `EnableAnimations`, `InstallSynchronizationContext`, +`TerminalTransparencyMode`. Factories: `.Default`, `.Create(...)`, `.WithMetrics`, +`.WithoutFrameRateLimiting`, `.WithTargetFPS(n)`. Env vars: +`SHARPCONSOLEUI_DEBUG_LOG=`, `SHARPCONSOLEUI_DEBUG_LEVEL=...`, +`SHARPCONSOLEUI_PERF_METRICS=true`. Guidance: 30 FPS for dashboards, 60 general, +unlimited for games/animation. + +## Registry (persistent settings) + +JSON-backed hierarchical key-value store; survives restarts. Enable by passing +`registryConfiguration:` to the `ConsoleWindowSystem` ctor; access via +`windowSystem.RegistryStateService` (null if not configured). + +- `RegistryConfiguration(FilePath = "registry.json", EagerFlush = false, FlushInterval = null, Storage = null)`; `.Default` (per-process platform path) / `.ForFile(path)`. +- `RegistryStateService`: `OpenSection(path)` → `RegistrySection`, `Save()`, `Load()` (auto-load on init, auto-save on dispose). +- `RegistrySection`: typed `GetString/SetString`, `GetInt/SetInt`, `GetBool/SetBool`, `GetDouble/SetDouble`, `GetDateTime/SetDateTime`; AOT-safe `Get/Set(key, …, JsonTypeInfo)`; `HasKey`, `GetKeys()`, `DeleteKey`, `DeleteSection`. `Get*` never throw (return default). Nested paths via `OpenSection("app/ui")`. +- Thread safety: the registry root is thread-safe, but a `RegistrySection` instance is NOT — open a fresh section per thread. `MemoryStorage` for tests. + +```csharp +var registry = windowSystem.RegistryStateService!; +var ui = registry.OpenSection("app/ui"); +string theme = ui.GetString("theme", "ModernGray"); +ui.SetString("theme", "Solarized"); +``` + +## Data binding (MVVM) + +`.Bind()` / `.BindTwoWay()` wire an `INotifyPropertyChanged` view model to control +properties (on controls, builders, and `MenuItem`). + +- One-way: `control.Bind(vm, v => v.Prop, c => c.TargetProp)` (applies initial value immediately); with converter `(…, Func)`. +- Two-way: `control.BindTwoWay(vm, v => v.Prop, c => c.TargetProp)` (re-entrancy guarded); with `toTarget:`/`toSource:` converters. +- Two-way-capable properties include `CheckboxControl.Checked`, `SliderControl.Value`, `DropdownControl.SelectedIndex`, `ListControl.SelectedIndex`, `TabControl.ActiveTabIndex`, `PromptControl.Input` (note: `Input`, not `Text`), `MultilineEditControl.Content`, `TableControl.SelectedRowIndex`, `CollapsiblePanel.IsExpanded`, `TreeNode.IsExpanded/Text`. Display-only one-way targets: `MarkupControl.Text`, `ProgressBarControl.Value`, `BarGraphControl.Value`. +- Every control derives from `BaseControl` (already `INotifyPropertyChanged`) — only your view model must implement it. Bindings are `IDisposable`, stored in the control's `Bindings`, disposed with the control (no manual unsubscribe). AOT-safe (LINQ interpreter fallback). + +```csharp +bar.Bind(vm, v => v.Cpu, c => c.Value); // one-way +prompt.BindTwoWay(vm, v => v.Name, c => c.Input); // two-way +``` + +## Flows (multi-step workflows) + +`SharpConsoleUI.Flows` removes Back-stack / cancellation / navigation boilerplate. + +- `Flow.Run(ws, parent, async ctx => …)` → `FlowResult` (`Completed`, `Value`, `Cancelled`, `Faulted`, `Error`). `FlowContext`: `Token`, `Show(content, title, buttons)`, `Confirm(...)`, `Prompt(...)`, `RunWithProgress(...)`, `Commit()` (Back barrier). Throw `OperationCanceledException` to cancel. +- `Flow.Wizard()` (`TState : new()`) → `FlowWizardBuilder`: `.Seed(state)`, `.WithStepIndicator()`, `.WithTitle(...)`, `.WithSeamlessHost()` (one shared window), `.Step(...)` (code step or content step with `.CanGoNext(...)`/`.OnNext/.OnBack/.OnCancel(...)`), `.Run(ws, parent)`. `FlowVerdict`: `Next`, `Finish`, `Back`, `Cancel`, `Stay`. +- Default host is a modal window per step (`ModalWindowHost`); `SwapContentHost` reuses one window. For **inline** (non-modal) flows, embed `FlowControl` / `WizardControl` in a window region instead. + +```csharp +var result = await Flow.Run(ws, myWindow, async ctx => +{ + if (!await ctx.Confirm("Deploy", "Deploy to staging?", ok: "Deploy")) throw new OperationCanceledException(); + return await ctx.RunWithProgress("Deploying", "Connecting…", + async (ct, progress) => { progress.Report("Uploading…"); await DeployAsync(ct); return "OK"; }); +}); +``` + +## Plugins + +Extend with themes/controls/windows/services without touching core. Implement +`IPlugin` / `PluginBase` (`Info`, `Initialize(ws)`, `GetThemes()`, `GetControls()`, +`GetWindows()`, `GetServicePlugins()`, `Dispose()`). Register via +`windowSystem.PluginStateService.LoadPlugin()` / `LoadPlugin(dllPath)` / +`LoadPluginsFromDirectory(...)`; create with `CreateControl(name)` / +`CreateWindow(name)`; call agnostic services with +`GetService(name).Execute(op, parameters)` (reflection-free). Auto-load via +`PluginConfiguration(AutoLoad, PluginsDirectory)` on the ctor. + +## Clipboard + +`SharpConsoleUI.Helpers.ClipboardHelper.SetText(text)` / `GetText()`. Copy works +transparently over SSH via OSC 52 (falls back to `clip.exe`/`pbcopy`/`wl-copy`/ +`xclip`/`xsel`). Knobs: `ClipboardHelper.Osc52Mode` (`Auto`/`Enabled`/`Disabled`), +`MaxOsc52Bytes` (default ~74000). Bracketed paste is enabled at startup; focused +controls implementing `IPasteTarget` (`MultilineEditControl`, `PromptControl`, +`TableControl`) receive `Ctrl+V` and pasted blocks. + +## Shell scripting and distribution + +- **Pipelines:** a SharpConsoleUI app can be an interactive picker/wizard inside a shell pipeline (like `fzf`/`gum`). On Unix the driver opens `/dev/tty` when stdin/stdout are redirected, keeping the pipe free for data. Read `windowSystem.PipedInput` / `PipedLines` **before** `Run()`, write results **after** it returns. Exit codes follow the `fzf`/`gum` convention (0 selected, 1 cancelled, 2 invalid). .NET 10 file-based apps (`#:package SharpConsoleUI@…` + `dotnet run script.cs`) suit one-off scripts; templates live under the docs `scripting/templates/`. +- **schost:** a separate CLI tool (`SharpConsoleUI.Host` NuGet, `dotnet tool install -g SharpConsoleUI.Host`) that launches an app inside a configured terminal window and packages it for desktop distribution. Commands: `schost init`, `schost run [--inline]`, `schost pack [--installer]`, `schost install`. Config in `schost.json`. `dotnet new schost-app` scaffolds a full-screen NavigationView starter. schost is a launcher/packager, not a renderer — the app still uses the real `NetConsoleDriver`. + +## When to choose SharpConsoleUI (vs other .NET TUI libs) + +The docs position it as "a desktop" — overlapping windows + a real compositor — +distinct from Spectre.Console ("a printer" for rich static output), Terminal.Gui +("a dialog box", single-screen forms with the widest mature control library), and +XenoAtom.Terminal.UI ("a WPF for the terminal", source-generated reactive +bindings, .NET 10 only). They are complementary — SharpConsoleUI can host +Spectre renderables via `SpectreRenderableControl`. + +Prefer SharpConsoleUI for: multi-window desktop-style apps; full-screen apps +(`.Frameless().Maximized()`); visual effects/compositing/transparency; dashboards +and monitoring (independent async window threads); IDE-like tools; an embedded +terminal (`TerminalControl`) alongside UI; terminal video; markup/Markdown +everywhere; MVVM data binding; plugin architectures. + +Prefer something else when: you only need pretty CLI output (Spectre.Console); +you want the widest mature single-screen control set (Terminal.Gui); you need +source-generated reactive bindings (XenoAtom); you target .NET 6 or older +(SharpConsoleUI requires .NET 8+); or you need maximum community/ecosystem size. +Documented gaps: proportional sizing only via Grid `Star` tracks (no flex +allocator), vertical-only dock, and no built-in ColorPicker/HexView. + +## Pattern catalog + +The docs' patterns cookbook (from real apps) covers, among others: app bootstrap +(`NetConsoleDriver(RenderMode.Buffer)` → `ConsoleWindowSystem` → maximized +`WindowBuilder` + `WithAsyncWindowThread` + `OnKeyPressed` → `Run()`); split layout +with a resizable splitter (`HorizontalGrid` + `.WithSplitterAfter`); async data +updates (continuous `WithAsyncWindowThread` loop; fire-and-forget with a +`CancellationTokenSource`; `EnqueueOnUIThread` from external threads — only +`Container?.Invalidate(...)` is safe to call directly off-thread); modal dialog +with result (`ModalBase` + `TaskCompletionSource`); global keyboard +shortcuts (`OnKeyPressed` / `PreviewKeyPressed` before controls consume keys); +debounced search; width-based responsive relayout; control discovery by name +(`.WithName(...)` + `window.FindControl("name")`); rolling log viewer +(`MarkupControl.SetContent` + `MarkupParser.Escape`); and explicit handler cleanup +in an `OnCleanup()` override. Key gotcha for `WithAsyncWindowThread`: its lambda +is built during `Build()`, so every captured control must be declared *before* the +`WindowBuilder` call, while `AddControl` wiring happens *after* `Build()`. diff --git a/external-sources/vendir.lock.yml b/external-sources/vendir.lock.yml index eb29960..05c6c67 100644 --- a/external-sources/vendir.lock.yml +++ b/external-sources/vendir.lock.yml @@ -2,10 +2,10 @@ apiVersion: vendir.k14s.io/v1alpha1 directories: - contents: - git: - commitTitle: Fix migrate-static-to-wrapper skill activation on evals (#864)... - sha: 1270722c951dece787074c8fde3bdab2f8ef54db + commitTitle: Update to Vally 0.7.0 (#854) + sha: 52ba152ed6e3a80cac59bf4f9827a3ed555292e1 tags: - - skill-validator-nightly-5-g1270722 + - skill-validator-nightly path: dotnet-skills - git: commitTitle: Add Cursor rules that reference the existing skill docs...