Files
Terminal.Gui/Examples/UICatalog
Copilot aab90e1fbc Fixes #4463 - Adds LineCanvas.GetRegion() - 10x performance regression in RenderLineCanvas (#4464)
* Initial plan

* Revert xunit maxParallelThreads to default to fix performance issue

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Fix 10x performance regression in RenderLineCanvas

Found via binary search of 51 commits that commit 027018368 introduced
the slowdown by calling context.AddDrawnRectangle() for every cell in
the LineCanvas (thousands of times per frame).

Fixed by reporting the entire LineCanvas.Bounds once instead of per-cell.

Test results:
- Before: 1m 33s (10x slower than v2_release)
- After: 9s (same as v2_release)

Fixes the performance issue in AllViews_Draw_Does_Not_Layout test.

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Add comment clarifying LineCanvas coordinate system

Addresses code review feedback about coordinate system conversion.
LineCanvas.Bounds is already in screen-relative coordinates since
cells are stored with screen positions (used with Driver.Move).

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Build non-rectangular Region from LineCanvas cells efficiently

Instead of reporting entire LineCanvas.Bounds (which includes empty space),
efficiently build a Region that encompasses only the actual line cells.

Algorithm groups contiguous horizontal cells into spans per row, avoiding
per-cell overhead while accurately representing the non-rectangular shape.

Performance maintained: 10s (same as v2_release)
All tests pass: 5695 drawing tests, 200 LineCanvas tests, 33 transparency tests

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Address code review feedback - improve robustness

- Remove redundant empty check (caller already verifies)
- Add safety check for empty row groups to prevent IndexOutOfRangeException
- Update documentation to clarify precondition

Tests still pass with 9s performance.

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Optimize BuildRegionFromLineCells - eliminate redundant sort

Remove redundant OrderBy since X values are already sorted from the
initial OrderBy().ThenBy() operation. Also remove unreachable empty
check since GroupBy guarantees non-empty groups.

Performance maintained at 9s.

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Final code review fixes - improve comments and add defensive check

- Clarify comment about sorting behavior
- Add defensive empty check for edge cases
- Performance maintained at 10s

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Move BuildRegionFromCells to LineCanvas class

Moved the BuildRegionFromCells method from View.Drawing.cs to LineCanvas
where it logically belongs as a public static method.

Updated View.Drawing.cs to call Drawing.LineCanvas.BuildRegionFromCells.

Performance maintained at 9s. All tests pass.

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Update documentation for BuildRegionFromCells

- Clarify that method handles empty cellMap gracefully
- Improve comment explaining the defensive empty check
- Performance maintained at 10s

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Rename BuildRegionFromCells to GetRegion

Renamed the method as requested to have a more concise name.

Performance maintained at 10s. All tests pass.

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Add GetCellMapWithRegion optimization for single-pass iteration

Implemented optimized method that builds both cell map and Region in a
single pass through the canvas bounds, eliminating the need for separate
GetCellMap() and GetRegion() calls.

Benefits:
- Reduces from 2 iterations to 1 (GetCellMap then GetRegion -> single pass)
- Eliminates sorting overhead (O(n log n) -> O(n))
- X values already sorted due to inner loop order
- Builds Region incrementally per row as cells are discovered

Performance maintained at 10s. All tests pass:
- AllViews_Draw_Does_Not_Layout: 10s
- 5695 drawing tests passed
- 200 LineCanvas tests passed
- 33 transparency tests passed

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Add comprehensive unit tests for GetRegion and GetCellMapWithRegion

Added 15 new unit tests covering:

GetRegion tests:
- Empty cell map handling
- Single cell regions
- Horizontal and vertical lines
- L-shaped regions
- Discontiguous cells creating separate spans
- Intersecting lines

GetCellMapWithRegion tests:
- Empty canvas handling
- Single horizontal/vertical lines
- Intersecting lines with proper intersection handling
- Complex shapes (boxes)
- Consistency with separate GetCellMap + GetRegion calls
- Negative coordinate handling
- Exclusion region support

All 215 LineCanvas tests pass. Performance maintained at 9s.

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Fix code style violations per CONTRIBUTING.md

- Replace var with explicit types (Dictionary<Point, Cell?>, LineCanvas, Region, etc.)
- Replace new TypeName() with target-typed new () where type is declared
- Fix foreach loop to use explicit IGrouping<int, Point> type
- Use StraightLine instead of var in foreach

All style violations fixed per CONTRIBUTING.md coding conventions.
Tests still pass: 215 LineCanvas tests, 9s performance maintained.

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Code cleanup.

* Upgrades Transparent Scenario to demonstrate LineCanvas transparency. "dotnet" is drawn using LineCanvas.

* Comment out debug assertions in View drawing logic

The debug assertions in the `View` class, which validated the relationships between `Margin`, `Border`, `Padding`, and their parent objects, as well as their `SubViewNeedsDraw` and `NeedsDraw` states, have been commented out. These assertions were conditionally executed when the current object and its `SuperView` were not of type `Adornment`.

The code has been retained as comments for potential future reference or debugging purposes, but the assertions are no longer active in the current implementation.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tig <585482+tig@users.noreply.github.com>
Co-authored-by: Tig <tig@users.noreply.github.com>
2025-12-08 17:13:58 -07:00
..

Terminal.Gui UI Catalog

UI Catalog is a comprehensive sample library for Terminal.Gui. It attempts to satisfy the following goals:

  1. Be an easy-to-use showcase for Terminal.Gui concepts and features.
  2. Provide sample code that illustrates how to properly implement said concepts & features.
  3. Make it easy for contributors to add additional samples in a structured way.

screenshot

Motivation

The original demo.cs sample app for Terminal.Gui is neither good to showcase, nor does it explain different concepts. In addition, because it is built on a single source file, it has proven to cause friction when multiple contributors are simultaneously working on different aspects of Terminal.Gui. See Issue #368 for more background.

API Reference

How To Use

Build and run UI Catalog by typing dotnet run from the UI Catalog folder or by using the Terminal.Gui Visual Studio solution.

Program.cs is the main UI Catalog app and provides a UI for selecting and running Scenarios. Each *Scenario is implemented as a class derived from Scenario and Program.cs uses reflection to dynamically build the UI.

Scenarios are tagged with categories using the [ScenarioCategory] attribute. The left pane of the main screen lists the categories. Clicking on a category shows all the scenarios in that category.

Scenarios can be run either from the UICatalog.exe app UI or by being specified on the command line:

UICatalog.exe <Scenario Name>

e.g.

UICatalog.exe Buttons

Hitting ENTER on a selected Scenario or double-clicking on a Scenario runs that scenario as though it were a stand-alone Terminal.Gui app.

When a Scenario is run, it runs as though it were a standalone Terminal.Gui app. However, scaffolding is provided (in the Scenario base class) that (optionally) takes care of Terminal.Gui initialization.

Contributing by Adding Scenarios

To add a new Scenario simply:

  1. Create a new .cs file in the Scenarios directory that derives from Scenario.
  2. Add a [ScenarioMetaData] attribute to the class specifying the scenario's name and description.
  3. Add one or more [ScenarioCategory] attributes to the class specifying which categories the sceanrio belongs to. If you don't specify a category the sceanrio will show up in "All".
  4. Implement the Setup override which will be called when a user selects the scenario to run.
  5. Optionally, implement the Init and/or Run overrides to provide a custom implementation.

The sample below is provided in the .\UICatalog\Scenarios directory as a generic sample that can be copied and re-named:


namespace UICatalog {
	[ScenarioMetadata (Name: "Generic", Description: "Generic sample - A template for creating new Scenarios")]
	[ScenarioCategory ("Controls")]
	class MyScenario : Scenario {
		public override void Setup ()
		{
			// Put your scenario code here, e.g.
			Win.Add (new Button () { 
Text = "Press me!", 
				X = Pos.Center (),
				Y = Pos.Center (),
				Clicked = () => MessageBox.Query (20, 7, "Hi", "Neat?", "Yes", "No")
			});
		}
	}
}

Scenario provides Win, a Window object that provides a canvas for the Scenario to operate.

The default Window shows the Scenario name and supports exiting the Scenario through the Esc key.

screenshot

To build a more advanced scenario, where control of the Runnable and Window is needed (e.g. for scenarios using MenuBar or StatusBar), simply use Application.Top per normal Terminal.Gui programming, as seen in the Notepad scenario.

For complete control, the Init and Run overrides can be implemented. The base.Init creates Win. The base.Run simply calls Application.Run(Application.Top).

Contribution Guidelines

  • Provide a terse, descriptive Name for Scenarios. Keep them short.
  • Provide a clear Description.
  • Comment Scenario code to describe to others why it's a useful Scenario.
  • Annotate Scenarios with [ScenarioCategory] attributes. Minimize the number of new categories created.
  • Use the Bug Repo Category for Scenarios that reproduce bugs.
    • Include the Github Issue # in the Description.
    • Once the bug has been fixed in develop submit another PR to remove the Scenario (or modify it to provide a good regression test/sample).
  • Tag bugs or suggestions for UI Catalog as Terminal.Gui Github Issues with "UICatalog: ".