Skip to content

Commit 741fb16

Browse files
committed
Add BaseControl abstract class, PortalContentBase, and PortalPositioner
Extract shared IWindowControl/IDOMPaintable fields and properties into BaseControl abstract class, eliminating ~2300 lines of duplicated boilerplate across 31 controls. Add PortalContentBase for portal overlay controls and PortalPositioner utility for reusable portal placement with auto-flip and clamping logic.
1 parent 0707c76 commit 741fb16

34 files changed

Lines changed: 1315 additions & 3125 deletions

SharpConsoleUI/Controls/BarGraphControl.cs

Lines changed: 20 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public record struct ColorThreshold(double Threshold, Color Color);
2828
/// A horizontal bar graph control for visualizing percentage-based data.
2929
/// Displays a filled/unfilled bar with optional label, value, and custom colors.
3030
/// </summary>
31-
public class BarGraphControl : IWindowControl, IDOMPaintable
31+
public class BarGraphControl : BaseControl
3232
{
3333
private const int DEFAULT_BAR_WIDTH = 20;
3434
private const char FILLED_CHAR = '█';
@@ -39,30 +39,19 @@ public class BarGraphControl : IWindowControl, IDOMPaintable
3939
private IContainer? _container;
4040
private Color _filledColor = Color.Cyan1;
4141
private Color? _foregroundColorValue;
42-
private HorizontalAlignment _horizontalAlignment = HorizontalAlignment.Left;
4342
private string _label = string.Empty;
4443
private int? _labelWidth;
45-
private Margin _margin = new Margin(0, 0, 0, 0);
4644
private double _maxValue = 100.0;
4745
private bool _showLabel = true;
4846
private bool _showValue = true;
49-
private StickyPosition _stickyPosition = StickyPosition.None;
5047
private Color _unfilledColor = Color.Grey35;
5148
private double _value;
5249
private string _valueFormat = "F1";
5350
private List<ColorThreshold>? _colorThresholds;
54-
private VerticalAlignment _verticalAlignment = VerticalAlignment.Top;
55-
private bool _visible = true;
56-
private int? _width;
5751

5852
// Gradient support
5953
private ColorGradient? _smoothGradient;
6054

61-
private int _actualX;
62-
private int _actualY;
63-
private int _actualWidth;
64-
private int _actualHeight;
65-
6655
/// <summary>
6756
/// Initializes a new instance of the <see cref="BarGraphControl"/> class.
6857
/// </summary>
@@ -279,25 +268,13 @@ public ColorGradient? SmoothGradient
279268
}
280269
}
281270

282-
#region IWindowControl Implementation
283-
284-
/// <inheritdoc/>
285-
public int? ContentWidth => _width;
286-
287-
/// <inheritdoc/>
288-
public int ActualX => _actualX;
289-
290-
/// <inheritdoc/>
291-
public int ActualY => _actualY;
271+
#region BaseControl Overrides
292272

293273
/// <inheritdoc/>
294-
public int ActualWidth => _actualWidth;
274+
public override int? ContentWidth => Width;
295275

296276
/// <inheritdoc/>
297-
public int ActualHeight => _actualHeight;
298-
299-
/// <inheritdoc/>
300-
public IContainer? Container
277+
public override IContainer? Container
301278
{
302279
get => _container;
303280
set
@@ -308,94 +285,46 @@ public IContainer? Container
308285
}
309286

310287
/// <inheritdoc/>
311-
public HorizontalAlignment HorizontalAlignment
288+
public override Margin Margin
312289
{
313-
get => _horizontalAlignment;
314-
set => PropertySetterHelper.SetEnumProperty(ref _horizontalAlignment, value, Container);
315-
}
316-
317-
/// <inheritdoc/>
318-
public Margin Margin
319-
{
320-
get => _margin;
290+
get => base.Margin;
321291
set
322292
{
323-
_margin = value;
324-
Container?.Invalidate(true);
293+
base.Margin = value;
325294
}
326295
}
327296

328297
/// <inheritdoc/>
329-
public string? Name { get; set; }
330-
331-
/// <inheritdoc/>
332-
public StickyPosition StickyPosition
333-
{
334-
get => _stickyPosition;
335-
set => PropertySetterHelper.SetEnumProperty(ref _stickyPosition, value, Container);
336-
}
337-
338-
/// <inheritdoc/>
339-
public object? Tag { get; set; }
340-
341-
/// <inheritdoc/>
342-
public VerticalAlignment VerticalAlignment
343-
{
344-
get => _verticalAlignment;
345-
set => PropertySetterHelper.SetEnumProperty(ref _verticalAlignment, value, Container);
346-
}
347-
348-
/// <inheritdoc/>
349-
public bool Visible
298+
public override int? Width
350299
{
351-
get => _visible;
352-
set => PropertySetterHelper.SetBoolProperty(ref _visible, value, Container);
353-
}
354-
355-
/// <inheritdoc/>
356-
public int? Width
357-
{
358-
get => _width;
300+
get => base.Width;
359301
set
360302
{
361303
var validatedValue = value.HasValue ? Math.Max(1, value.Value) : (int?)null;
362-
if (_width != validatedValue)
304+
if (base.Width != validatedValue)
363305
{
364-
_width = validatedValue;
365-
Container?.Invalidate(true);
306+
base.Width = validatedValue;
366307
}
367308
}
368309
}
369310

370311
/// <inheritdoc/>
371-
public void Dispose()
372-
{
373-
Container = null;
374-
}
375-
376-
/// <inheritdoc/>
377-
public Size GetLogicalContentSize()
312+
public override Size GetLogicalContentSize()
378313
{
379314
int contentWidth = CalculateContentWidth();
380-
return new Size(contentWidth + _margin.Left + _margin.Right, 1 + _margin.Top + _margin.Bottom);
381-
}
382-
383-
/// <inheritdoc/>
384-
public void Invalidate()
385-
{
386-
Container?.Invalidate(false);
315+
return new Size(contentWidth + Margin.Left + Margin.Right, 1 + Margin.Top + Margin.Bottom);
387316
}
388317

389318
#endregion
390319

391320
#region IDOMPaintable Implementation
392321

393322
/// <inheritdoc/>
394-
public LayoutSize MeasureDOM(LayoutConstraints constraints)
323+
public override LayoutSize MeasureDOM(LayoutConstraints constraints)
395324
{
396325
int contentWidth = CalculateContentWidth();
397-
int width = contentWidth + _margin.Left + _margin.Right;
398-
int height = 1 + _margin.Top + _margin.Bottom;
326+
int width = contentWidth + Margin.Left + Margin.Right;
327+
int height = 1 + Margin.Top + Margin.Bottom;
399328

400329
return new LayoutSize(
401330
Math.Clamp(width, constraints.MinWidth, constraints.MaxWidth),
@@ -404,19 +333,16 @@ public LayoutSize MeasureDOM(LayoutConstraints constraints)
404333
}
405334

406335
/// <inheritdoc/>
407-
public void PaintDOM(CharacterBuffer buffer, LayoutRect bounds, LayoutRect clipRect, Color defaultFg, Color defaultBg)
336+
public override void PaintDOM(CharacterBuffer buffer, LayoutRect bounds, LayoutRect clipRect, Color defaultFg, Color defaultBg)
408337
{
409-
_actualX = bounds.X;
410-
_actualY = bounds.Y;
411-
_actualWidth = bounds.Width;
412-
_actualHeight = bounds.Height;
338+
SetActualBounds(bounds);
413339

414340
// Resolve colors
415341
Color bgColor = _backgroundColorValue ?? Container?.BackgroundColor ?? defaultBg;
416342
Color fgColor = _foregroundColorValue ?? Container?.ForegroundColor ?? defaultFg;
417343

418-
int startX = bounds.X + _margin.Left;
419-
int startY = bounds.Y + _margin.Top;
344+
int startX = bounds.X + Margin.Left;
345+
int startY = bounds.Y + Margin.Top;
420346
int paintY = startY;
421347

422348
if (paintY < clipRect.Y || paintY >= clipRect.Bottom || paintY >= bounds.Bottom)
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// -----------------------------------------------------------------------
2+
// ConsoleEx - A simple console window system for .NET Core
3+
//
4+
// Author: Nikolaos Protopapas
5+
// Email: nikolaos.protopapas@gmail.com
6+
// License: MIT
7+
// -----------------------------------------------------------------------
8+
9+
using System.Drawing;
10+
using SharpConsoleUI.Helpers;
11+
using SharpConsoleUI.Layout;
12+
using Color = Spectre.Console.Color;
13+
using HorizontalAlignment = SharpConsoleUI.Layout.HorizontalAlignment;
14+
using VerticalAlignment = SharpConsoleUI.Layout.VerticalAlignment;
15+
16+
namespace SharpConsoleUI.Controls
17+
{
18+
/// <summary>
19+
/// Abstract base class for all UI controls, providing shared layout fields, properties,
20+
/// and default implementations of <see cref="IWindowControl"/> and <see cref="IDOMPaintable"/>.
21+
/// </summary>
22+
public abstract class BaseControl : IWindowControl, IDOMPaintable
23+
{
24+
private int _actualX;
25+
private int _actualY;
26+
private int _actualWidth;
27+
private int _actualHeight;
28+
private HorizontalAlignment _horizontalAlignment = HorizontalAlignment.Left;
29+
private VerticalAlignment _verticalAlignment = VerticalAlignment.Top;
30+
private Margin _margin = new Margin(0, 0, 0, 0);
31+
private StickyPosition _stickyPosition = StickyPosition.None;
32+
private bool _visible = true;
33+
private int? _width;
34+
private bool _disposed;
35+
36+
/// <inheritdoc/>
37+
public abstract int? ContentWidth { get; }
38+
39+
/// <inheritdoc/>
40+
public int ActualX => _actualX;
41+
42+
/// <inheritdoc/>
43+
public int ActualY => _actualY;
44+
45+
/// <inheritdoc/>
46+
public int ActualWidth => _actualWidth;
47+
48+
/// <inheritdoc/>
49+
public int ActualHeight => _actualHeight;
50+
51+
/// <inheritdoc/>
52+
public virtual HorizontalAlignment HorizontalAlignment
53+
{
54+
get => _horizontalAlignment;
55+
set => PropertySetterHelper.SetEnumProperty(ref _horizontalAlignment, value, Container);
56+
}
57+
58+
/// <inheritdoc/>
59+
public virtual VerticalAlignment VerticalAlignment
60+
{
61+
get => _verticalAlignment;
62+
set => PropertySetterHelper.SetEnumProperty(ref _verticalAlignment, value, Container);
63+
}
64+
65+
/// <inheritdoc/>
66+
public virtual IContainer? Container { get; set; }
67+
68+
/// <inheritdoc/>
69+
public virtual Margin Margin
70+
{
71+
get => _margin;
72+
set => PropertySetterHelper.SetProperty(ref _margin, value, Container);
73+
}
74+
75+
/// <inheritdoc/>
76+
public virtual StickyPosition StickyPosition
77+
{
78+
get => _stickyPosition;
79+
set => PropertySetterHelper.SetEnumProperty(ref _stickyPosition, value, Container);
80+
}
81+
82+
/// <inheritdoc/>
83+
public string? Name { get; set; }
84+
85+
/// <inheritdoc/>
86+
public object? Tag { get; set; }
87+
88+
/// <inheritdoc/>
89+
public virtual bool Visible
90+
{
91+
get => _visible;
92+
set => PropertySetterHelper.SetBoolProperty(ref _visible, value, Container);
93+
}
94+
95+
/// <inheritdoc/>
96+
public virtual int? Width
97+
{
98+
get => _width;
99+
set => PropertySetterHelper.SetDimensionProperty(ref _width, value, Container);
100+
}
101+
102+
/// <inheritdoc/>
103+
public virtual System.Drawing.Size GetLogicalContentSize()
104+
{
105+
int width = ContentWidth ?? 0;
106+
int height = 1 + _margin.Top + _margin.Bottom;
107+
return new System.Drawing.Size(width, height);
108+
}
109+
110+
/// <inheritdoc/>
111+
public void Invalidate()
112+
{
113+
Container?.Invalidate(true);
114+
}
115+
116+
/// <summary>
117+
/// Sets the actual rendered bounds from the layout system.
118+
/// Call this at the start of <see cref="PaintDOM"/> to record the control's position.
119+
/// </summary>
120+
/// <param name="bounds">The layout bounds assigned by the layout engine.</param>
121+
protected void SetActualBounds(LayoutRect bounds)
122+
{
123+
_actualX = bounds.X;
124+
_actualY = bounds.Y;
125+
_actualWidth = bounds.Width;
126+
_actualHeight = bounds.Height;
127+
}
128+
129+
/// <inheritdoc/>
130+
public void Dispose()
131+
{
132+
if (_disposed) return;
133+
_disposed = true;
134+
OnDisposing();
135+
Container = null;
136+
}
137+
138+
/// <summary>
139+
/// Called during <see cref="Dispose"/> before <c>Container</c> is set to null.
140+
/// Override to perform control-specific cleanup (null events, close portals, clear data, etc.).
141+
/// </summary>
142+
protected virtual void OnDisposing() { }
143+
144+
/// <inheritdoc/>
145+
public abstract LayoutSize MeasureDOM(LayoutConstraints constraints);
146+
147+
/// <inheritdoc/>
148+
public abstract void PaintDOM(CharacterBuffer buffer, LayoutRect bounds, LayoutRect clipRect, Color defaultForeground, Color defaultBackground);
149+
}
150+
}

0 commit comments

Comments
 (0)