mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
* 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>
321 lines
14 KiB
C#
321 lines
14 KiB
C#
// ReSharper disable AccessToDisposedClosure
|
|
#nullable enable
|
|
|
|
namespace UICatalog.Scenarios;
|
|
|
|
[ScenarioMetadata ("Transparent", "Demonstrates View Transparency")]
|
|
public sealed class Transparent : Scenario
|
|
{
|
|
public override void Main ()
|
|
{
|
|
// Init
|
|
Application.Init ();
|
|
|
|
// Setup - Create a top-level application window and configure it.
|
|
Window appWindow = new ()
|
|
{
|
|
Title = GetQuitKeyAndName (),
|
|
};
|
|
appWindow.BorderStyle = LineStyle.None;
|
|
appWindow.SchemeName = "Error";
|
|
|
|
appWindow.Text = "App Text - Centered Vertically and Horizontally.\n2nd Line of Text.\n3rd Line of Text.";
|
|
appWindow.TextAlignment = Alignment.Center;
|
|
appWindow.VerticalTextAlignment = Alignment.Center;
|
|
appWindow.ClearingViewport += (s, e) =>
|
|
{
|
|
if (s is View sender)
|
|
{
|
|
sender.FillRect (sender.Viewport, Glyphs.Stipple);
|
|
}
|
|
|
|
e.Cancel = true;
|
|
};
|
|
ViewportSettingsEditor viewportSettingsEditor = new ViewportSettingsEditor ()
|
|
{
|
|
Y = Pos.AnchorEnd (),
|
|
//X = Pos.Right (adornmentsEditor),
|
|
AutoSelectViewToEdit = true
|
|
};
|
|
appWindow.Add (viewportSettingsEditor);
|
|
|
|
Button appButton = new Button ()
|
|
{
|
|
X = 10,
|
|
Y = 4,
|
|
Title = "_AppButton",
|
|
};
|
|
appButton.Accepting += (sender, args) =>
|
|
{
|
|
MessageBox.Query ((sender as View)?.App, "AppButton", "Transparency is cool!", "_Ok");
|
|
args.Handled = true;
|
|
};
|
|
appWindow.Add (appButton);
|
|
|
|
var tv = new TransparentView ()
|
|
{
|
|
X = 2,
|
|
Y = 2,
|
|
Width = Dim.Fill (10),
|
|
Height = Dim.Fill (10)
|
|
};
|
|
|
|
appWindow.ViewportChanged += (sender, args) =>
|
|
{
|
|
// Little hack to convert the Dim.Fill to actual size
|
|
// So resizing works
|
|
tv.Width = appWindow!.Frame.Width - 10;
|
|
tv.Height = appWindow!.Frame.Height - 10;
|
|
};
|
|
appWindow.Add (tv);
|
|
|
|
// Run - Start the application.
|
|
Application.Run (appWindow);
|
|
appWindow.Dispose ();
|
|
|
|
// Shutdown - Calling Application.Shutdown is required.
|
|
Application.Shutdown ();
|
|
}
|
|
|
|
public class TransparentView : FrameView
|
|
{
|
|
public TransparentView ()
|
|
{
|
|
Title = "Transparent View - Move and Resize To See Transparency In Action";
|
|
base.Text = "View.Text.\nThis should be opaque. Note how clipping works?";
|
|
Arrangement = ViewArrangement.Overlapped | ViewArrangement.Resizable | ViewArrangement.Movable;
|
|
ViewportSettings |= ViewportSettingsFlags.Transparent | ViewportSettingsFlags.TransparentMouse;
|
|
BorderStyle = LineStyle.RoundedDotted;
|
|
SchemeName = "Base";
|
|
|
|
var transparentSubView = new View ()
|
|
{
|
|
Text = "Sizable/Movable SubView with border and shadow.",
|
|
Id = "transparentSubView",
|
|
X = Pos.Center (),
|
|
Y = Pos.Center (),
|
|
Width = 20,
|
|
Height = 8,
|
|
BorderStyle = LineStyle.Dashed,
|
|
Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable,
|
|
ShadowStyle = ShadowStyle.Transparent,
|
|
};
|
|
transparentSubView.Border!.Thickness = new (1, 1, 1, 1);
|
|
transparentSubView.SchemeName = "Dialog";
|
|
|
|
Button button = new Button ()
|
|
{
|
|
Title = "_Opaque Shadow",
|
|
X = Pos.Center (),
|
|
Y = 2,
|
|
SchemeName = "Dialog",
|
|
};
|
|
button.Accepting += (sender, args) =>
|
|
{
|
|
MessageBox.Query (App, "Clicked!", "Button in Transparent View", "_Ok");
|
|
args.Handled = true;
|
|
};
|
|
|
|
var shortcut = new Shortcut ()
|
|
{
|
|
Id = "shortcut",
|
|
X = Pos.Center (),
|
|
Y = Pos.AnchorEnd (),
|
|
Title = "A _Shortcut",
|
|
HelpText = "Help!",
|
|
Key = Key.F11,
|
|
SchemeName = "Base"
|
|
};
|
|
|
|
button.ClearingViewport += (sender, args) =>
|
|
{
|
|
args.Cancel = true;
|
|
};
|
|
|
|
// Subscribe to DrawingContent event to draw "TUI"
|
|
DrawingContent += TransparentView_DrawingContent;
|
|
|
|
base.Add (button);
|
|
base.Add (shortcut);
|
|
base.Add (transparentSubView);
|
|
|
|
Padding!.Thickness = new (1);
|
|
Padding.Text = "This is the Padding";
|
|
}
|
|
|
|
private void TransparentView_DrawingContent (object? sender, DrawEventArgs e)
|
|
{
|
|
// Draw "TUI" text using rectangular regions, positioned after "Hi"
|
|
// Letter "T"
|
|
Rectangle tTop = new (20, 5, 7, 2); // Top horizontal bar
|
|
Rectangle tStem = new (23, 7, 2, 8); // Vertical stem
|
|
|
|
// Letter "U"
|
|
Rectangle uLeft = new (30, 5, 2, 8); // Left vertical bar
|
|
Rectangle uBottom = new (32, 13, 3, 2); // Bottom horizontal bar
|
|
Rectangle uRight = new (35, 5, 2, 8); // Right vertical bar
|
|
|
|
// Letter "I"
|
|
Rectangle iTop = new (39, 5, 4, 2); // Bar on top
|
|
Rectangle iStem = new (40, 7, 2, 6); // Vertical stem
|
|
Rectangle iBottom = new (39, 13, 4, 2); // Bar on Bottom
|
|
|
|
// Draw "TUI" using the HotActive attribute
|
|
SetAttributeForRole (VisualRole.HotActive);
|
|
FillRect (tTop, Glyphs.BlackCircle);
|
|
FillRect (tStem, Glyphs.BlackCircle);
|
|
FillRect (uLeft, Glyphs.BlackCircle);
|
|
FillRect (uBottom, Glyphs.BlackCircle);
|
|
FillRect (uRight, Glyphs.BlackCircle);
|
|
FillRect (iTop, Glyphs.BlackCircle);
|
|
FillRect (iStem, Glyphs.BlackCircle);
|
|
FillRect (iBottom, Glyphs.BlackCircle);
|
|
|
|
Region tuiRegion = new Region (ViewportToScreen (tTop));
|
|
tuiRegion.Union (ViewportToScreen (tStem));
|
|
tuiRegion.Union (ViewportToScreen (uLeft));
|
|
tuiRegion.Union (ViewportToScreen (uBottom));
|
|
tuiRegion.Union (ViewportToScreen (uRight));
|
|
tuiRegion.Union (ViewportToScreen (iTop));
|
|
tuiRegion.Union (ViewportToScreen (iStem));
|
|
tuiRegion.Union (ViewportToScreen (iBottom));
|
|
|
|
// Register the drawn region for "TUI" to enable transparency effects
|
|
e.DrawContext?.AddDrawnRegion (tuiRegion);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override bool OnDrawingContent (DrawContext? context)
|
|
{
|
|
base.OnDrawingContent (context);
|
|
|
|
// Draw "Hi" text using rectangular regions
|
|
// Letter "H"
|
|
Rectangle hLeft = new (5, 5, 2, 10); // Left vertical bar
|
|
Rectangle hMiddle = new (7, 9, 3, 2); // Middle horizontal bar
|
|
Rectangle hRight = new (10, 5, 2, 10); // Right vertical bar
|
|
|
|
// Letter "i" (with some space between H and i)
|
|
Rectangle iDot = new (15, 5, 2, 2); // Dot on top
|
|
Rectangle iStem = new (15, 9, 2, 6); // Vertical stem
|
|
|
|
// Draw "Hi" using the Highlight attribute
|
|
SetAttributeForRole (VisualRole.Highlight);
|
|
FillRect (hLeft, Glyphs.BlackCircle);
|
|
FillRect (hMiddle, Glyphs.BlackCircle);
|
|
FillRect (hRight, Glyphs.BlackCircle);
|
|
FillRect (iDot, Glyphs.BlackCircle);
|
|
FillRect (iStem, Glyphs.BlackCircle);
|
|
|
|
// Register the drawn region for "Hi" to enable transparency effects
|
|
Region hiRegion = new Region (ViewportToScreen (hLeft));
|
|
hiRegion.Union (ViewportToScreen (hMiddle));
|
|
hiRegion.Union (ViewportToScreen (hRight));
|
|
hiRegion.Union (ViewportToScreen (iDot));
|
|
hiRegion.Union (ViewportToScreen (iStem));
|
|
context?.AddDrawnRegion (hiRegion);
|
|
|
|
// Return false to allow DrawingContent event to fire
|
|
return false;
|
|
}
|
|
|
|
protected override bool OnRenderingLineCanvas ()
|
|
{
|
|
// Draw "dotnet" using LineCanvas
|
|
Point screenPos = ViewportToScreen (new Point (7, 16));
|
|
DrawDotnet (LineCanvas, screenPos.X, screenPos.Y, LineStyle.Single, GetAttributeForRole (VisualRole.Normal));
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override bool OnClearingViewport () { return false; }
|
|
|
|
/// <inheritdoc />
|
|
protected override bool OnMouseEvent (MouseEventArgs mouseEvent) { return false; }
|
|
|
|
|
|
/// <summary>
|
|
/// Draws "dotnet" text using LineCanvas. The 'd' is 8 cells high.
|
|
/// </summary>
|
|
/// <param name="canvas">The LineCanvas to draw on</param>
|
|
/// <param name="x">Starting X position</param>
|
|
/// <param name="y">Starting Y position</param>
|
|
/// <param name="style">Line style to use</param>
|
|
/// <param name="attribute">Optional attribute for the lines</param>
|
|
private void DrawDotnet (LineCanvas canvas, int x, int y, LineStyle style = LineStyle.Single, Attribute? attribute = null)
|
|
{
|
|
int currentX = x;
|
|
int letterHeight = 8;
|
|
int letterSpacing = 2;
|
|
|
|
// Letter 'd' - lowercase, height 8
|
|
// Vertical stem on right (goes up full 8 cells)
|
|
canvas.AddLine (new (currentX + 3, y), letterHeight, Orientation.Vertical, style, attribute);
|
|
// Top horizontal
|
|
canvas.AddLine (new (currentX, y + 3), 4, Orientation.Horizontal, style, attribute);
|
|
// Left vertical (only bottom 5 cells, leaving top 3 for ascender space)
|
|
canvas.AddLine (new (currentX, y + 3), 5, Orientation.Vertical, style, attribute);
|
|
// Bottom horizontal
|
|
canvas.AddLine (new (currentX, y + 7), 4, Orientation.Horizontal, style, attribute);
|
|
currentX += 4 + letterSpacing;
|
|
|
|
// Letter 'o' - height 5 (x-height)
|
|
int oY = y + 3; // Align with x-height (leaving 3 cells for ascenders)
|
|
// Top
|
|
canvas.AddLine (new (currentX, oY), 4, Orientation.Horizontal, style, attribute);
|
|
// Left
|
|
canvas.AddLine (new (currentX, oY), 5, Orientation.Vertical, style, attribute);
|
|
// Right
|
|
canvas.AddLine (new (currentX + 3, oY), 5, Orientation.Vertical, style, attribute);
|
|
// Bottom
|
|
canvas.AddLine (new (currentX, oY + 4), 4, Orientation.Horizontal, style, attribute);
|
|
currentX += 4 + letterSpacing;
|
|
|
|
// Letter 't' - height 7 (has ascender above x-height)
|
|
int tY = y + 1; // Starts 1 cell above x-height
|
|
// Vertical stem
|
|
canvas.AddLine (new (currentX + 1, tY), 7, Orientation.Vertical, style, attribute);
|
|
// Top cross bar (at x-height)
|
|
canvas.AddLine (new (currentX, tY + 2), 3, Orientation.Horizontal, style, attribute);
|
|
// Bottom horizontal (foot)
|
|
canvas.AddLine (new (currentX + 1, tY + 6), 2, Orientation.Horizontal, style, attribute);
|
|
currentX += 3 + letterSpacing;
|
|
|
|
// Letter 'n' - height 5 (x-height)
|
|
int nY = y + 3;
|
|
// Left vertical
|
|
canvas.AddLine (new (currentX, nY), 5, Orientation.Vertical, style, attribute);
|
|
// Top horizontal
|
|
canvas.AddLine (new (currentX + 1, nY), 3, Orientation.Horizontal, style, attribute);
|
|
// Right vertical
|
|
canvas.AddLine (new (currentX + 3, nY), 5, Orientation.Vertical, style, attribute);
|
|
currentX += 4 + letterSpacing;
|
|
|
|
// Letter 'e' - height 5 (x-height)
|
|
int eY = y + 3;
|
|
// Top
|
|
canvas.AddLine (new (currentX, eY), 4, Orientation.Horizontal, style, attribute);
|
|
// Left
|
|
canvas.AddLine (new (currentX, eY), 5, Orientation.Vertical, style, attribute);
|
|
// Right
|
|
canvas.AddLine (new (currentX + 3, eY), 3, Orientation.Vertical, style, attribute);
|
|
// Middle horizontal bar
|
|
canvas.AddLine (new (currentX, eY + 2), 4, Orientation.Horizontal, style, attribute);
|
|
// Bottom
|
|
canvas.AddLine (new (currentX, eY + 4), 4, Orientation.Horizontal, style, attribute);
|
|
currentX += 4 + letterSpacing;
|
|
|
|
// Letter 't' - height 7 (has ascender above x-height) - second 't'
|
|
int t2Y = y + 1;
|
|
// Vertical stem
|
|
canvas.AddLine (new (currentX + 1, t2Y), 7, Orientation.Vertical, style, attribute);
|
|
// Top cross bar (at x-height)
|
|
canvas.AddLine (new (currentX, t2Y + 2), 3, Orientation.Horizontal, style, attribute);
|
|
// Bottom horizontal (foot)
|
|
canvas.AddLine (new (currentX + 1, t2Y + 6), 2, Orientation.Horizontal, style, attribute);
|
|
}
|
|
}
|
|
|
|
}
|