Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a18f360
Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop
tig Apr 8, 2026
c72bba7
Cleans up examples.
tig Apr 11, 2026
918f98b
Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop
tig Apr 11, 2026
6238893
Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop
tig Apr 11, 2026
e78909b
Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop
tig Apr 12, 2026
d0b085d
Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop
tig Apr 12, 2026
db6347d
Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop
tig Apr 12, 2026
bfaef7c
Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop
tig Apr 12, 2026
aa0e09b
Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop
tig Apr 12, 2026
851ec1e
Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop
tig Apr 13, 2026
5446a3c
Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop
tig Apr 13, 2026
7a8cfb6
Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop
tig Apr 13, 2026
5501ed9
Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop
tig Apr 13, 2026
3f6a47b
Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop
tig Apr 14, 2026
fde0105
Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop
tig Apr 15, 2026
b27370a
Merge branch 'develop' of github.com:gui-cs/Terminal.Gui into develop
tig Apr 17, 2026
2cbf468
Merge branch 'develop' of github.com:gui-cs/Terminal.Gui into develop
tig Apr 17, 2026
d018f2d
Merge branch 'develop' of github.com:gui-cs/Terminal.Gui into develop
tig Apr 17, 2026
9580b0d
Merge branch 'develop' of github.com:gui-cs/Terminal.Gui into develop
tig Apr 19, 2026
fd9fbef
Merge branch 'develop' of github.com:gui-cs/Terminal.Gui into develop
tig Apr 19, 2026
a2e0d6f
Merge branch 'develop' of github.com:gui-cs/Terminal.Gui into develop
tig Apr 19, 2026
36e64f2
Merge branch 'develop' of github.com:gui-cs/Terminal.Gui into develop
tig Apr 19, 2026
7c68730
updated docs
tig Apr 19, 2026
c700d81
Merge branch 'develop' of github.com:gui-cs/Terminal.Gui into develop
tig Apr 20, 2026
c511c14
Merge branch 'develop' of github.com:gui-cs/Terminal.Gui into develop
tig Apr 20, 2026
fa3cefa
Initial plan
Copilot Apr 20, 2026
276463e
Fixes #4885. Button: avoid create-then-destroy shadow allocation via …
Copilot Apr 20, 2026
ca29a76
Merge branch 'develop' into copilot/fix-button-shadow-creation-issue
tig Apr 20, 2026
6c77b70
Merge branch 'develop' of github.com:gui-cs/Terminal.Gui into develop
tig Apr 20, 2026
fc86b5f
Update Terminal.Gui/Views/ScrollBar/ScrollButton.cs
tig Apr 20, 2026
0861fb7
Update Terminal.Gui/ViewBase/Adornment/ArrangerButton.cs
tig Apr 20, 2026
549d923
Update Tests/UnitTestsParallelizable/Views/ButtonTests.cs
tig Apr 20, 2026
6a4f0b3
Update Terminal.Gui/Views/Button.cs
tig Apr 20, 2026
5e5f020
Merge branch 'develop' into copilot/fix-button-shadow-creation-issue
tig Apr 20, 2026
4dd7d76
Merge branch 'develop' into copilot/fix-button-shadow-creation-issue
tig Apr 20, 2026
2b24272
Merge branch 'copilot/fix-button-shadow-creation-issue' of github.com…
tig Apr 20, 2026
59289b4
Redesign shadow initialization to use CWP event (OnInitializingShadow…
Copilot Apr 20, 2026
58e4148
Darken shadow effect in ShadowView for non-opaque style
tig Apr 20, 2026
6a96e46
Merge branch 'copilot/fix-button-shadow-creation-issue' of github.com…
tig Apr 20, 2026
1f88d04
Merge branch 'develop' into copilot/fix-button-shadow-creation-issue
tig Apr 20, 2026
c743916
Merge branch 'develop' of github.com:gui-cs/Terminal.Gui into develop
tig Apr 20, 2026
e7d807d
Merge branch 'develop' into copilot/fix-button-shadow-creation-issue
tig Apr 20, 2026
397f0c8
Merge branch 'copilot/fix-button-shadow-creation-issue' of github.com…
tig Apr 20, 2026
df899e7
Refactor docs and style in Button and ArrangerButton
tig Apr 20, 2026
8222464
Update ShadowTests expected ANSI output for background
tig Apr 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Terminal.Gui/ViewBase/Adornment/ArrangerButton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ public ArrangerButton ()
Height = 1;
NoDecorations = true;
NoPadding = true;
base.ShadowStyle = null;
base.Visible = false;

AddCommand (Command.Up, DefaultAcceptHandler);
Expand All @@ -85,6 +84,13 @@ public ArrangerButton ()
_orientationHelper = new OrientationHelper (this);
}

/// <inheritdoc/>
/// <remarks>
/// Sets <see cref="ValueChangingEventArgs{T}.NewValue"/> to <see langword="null"/> so that no shadow infrastructure is
/// allocated by default for arranger buttons.
/// </remarks>
protected override void OnInitializingShadowStyle (ValueChangingEventArgs<ShadowStyles?> args) => args.NewValue = null;

private ArrangeButtons _buttonType = (ArrangeButtons)(-1);

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/ViewBase/Adornment/ShadowView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ private Attribute GetAttributeUnderLocation (Point location)
Attribute attr = attribute.Value;

var newAttribute = new Attribute (ShadowStyle == ShadowStyles.Opaque ? Color.Black : attr.Foreground.GetDimmerColor (),
ShadowStyle == ShadowStyles.Opaque ? attr.Background : attr.Background.GetDimmerColor (0.05),
ShadowStyle == ShadowStyles.Opaque ? attr.Background : attr.Background.GetDimmerColor (0.9),
attr.Style);

// If the BG is DarkGray, GetDimmerColor gave up. Instead of using the attribute in the Driver under the shadow,
Expand All @@ -198,7 +198,7 @@ private Attribute GetAttributeUnderLocation (Point location)
attr = underView?.GetAttributeForRole (VisualRole.Normal) ?? Attribute.Default;

newAttribute = new Attribute (ShadowStyle == ShadowStyles.Opaque ? Color.Black : attr.Background.GetDimmerColor (),
ShadowStyle == ShadowStyles.Opaque ? attr.Background : attr.Foreground.GetDimmerColor (0.25),
ShadowStyle == ShadowStyles.Opaque ? attr.Background : attr.Foreground.GetDimmerColor (0.9),
attr.Style);

return newAttribute;
Expand Down
57 changes: 48 additions & 9 deletions Terminal.Gui/Views/Button.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
namespace Terminal.Gui.Views;

/// <summary>
/// Raises the <see cref="View.Accepting"/> and <see cref="View.Accepted"/> events when the user presses <see cref="View.HotKey"/>,
/// <c>Enter</c>, or <c>Space</c> or clicks with the mouse.
/// Raises the <see cref="View.Accepting"/> and <see cref="View.Accepted"/> events when the user presses
/// <see cref="View.HotKey"/>,
/// <c>Enter</c>, or <c>Space</c> or clicks with the mouse.
/// </summary>
/// <remarks>
/// <para>Use <see cref="View.HotKeySpecifier"/> to change the hot key specifier from the default of ('_').</para>
Expand Down Expand Up @@ -84,10 +85,51 @@ public Button ()

TitleChanged += Button_TitleChanged;

base.ShadowStyle = DefaultShadow;
// Determine and apply the initial shadow via the CWP InitializingShadowStyle event, so that
// subclasses and external subscribers can change or suppress the default allocation
// before any shadow infrastructure is created.
RaiseInitializingShadowStyle ();
MouseHighlightStates = DefaultMouseHighlightStates;
}

/// <summary>
/// Called before the Button's initial <see cref="View.ShadowStyle"/> is applied during construction.
/// Override to change or suppress the default shadow — set <see cref="ValueChangingEventArgs{T}.NewValue"/>
/// to the desired style, or set <see cref="ValueChangingEventArgs{T}.Handled"/> to
/// <see langword="true"/> to skip applying any shadow.
/// </summary>
/// <param name="args">
/// Event args whose <see cref="ValueChangingEventArgs{T}.NewValue"/> is pre-set to
/// <see cref="DefaultShadow"/>. Subclasses that never display a shadow
/// (e.g. <see cref="ScrollButton"/> or internal buttons used by arrangement UI)
/// should set <c>args.NewValue = null</c> to avoid the create-then-destroy allocation pattern.
/// </param>
protected virtual void OnInitializingShadowStyle (ValueChangingEventArgs<ShadowStyles?> args) { }

/// <summary>
/// Fired before the Button's initial <see cref="View.ShadowStyle"/> is applied during construction.
/// Subscribers can modify <see cref="ValueChangingEventArgs{T}.NewValue"/> or set
/// <see cref="ValueChangingEventArgs{T}.Handled"/> to <see langword="true"/> to suppress the shadow.
/// </summary>
public event EventHandler<ValueChangingEventArgs<ShadowStyles?>>? InitializingShadowStyle;

private void RaiseInitializingShadowStyle ()
{
ValueChangingEventArgs<ShadowStyles?> args = new (null, DefaultShadow);

// 1. Virtual method — subclasses override to change/suppress the default shadow.
OnInitializingShadowStyle (args);

// 2. Event — external subscribers get a chance to customize.
InitializingShadowStyle?.Invoke (this, args);

// 3. Apply the (potentially modified) shadow style unless already handled.
if (!args.Handled)
{
base.ShadowStyle = args.NewValue;
}
}

/// <inheritdoc/>
protected override void OnMouseHoldRepeatChanged (ValueChangedEventArgs<MouseFlags?> args) => SetMouseBindings (args.NewValue);

Expand Down Expand Up @@ -118,11 +160,8 @@ private void SetMouseBindings (MouseFlags? mouseHoldRepeat)
}
}

/// <inheritdoc />
protected override void OnHotKeyCommand (ICommandContext? commandContext)
{
InvokeCommand (Command.Accept);
}
/// <inheritdoc/>
protected override void OnHotKeyCommand (ICommandContext? commandContext) => InvokeCommand (Command.Accept);

private void Button_TitleChanged (object? sender, EventArgs<string> e)
{
Expand All @@ -136,7 +175,7 @@ private void Button_TitleChanged (object? sender, EventArgs<string> e)
/// <inheritdoc/>
public override Rune HotKeySpecifier { get => base.HotKeySpecifier; set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value; }

/// <inheritdoc />
/// <inheritdoc/>
public bool IsDefault
{
get;
Expand Down
5 changes: 4 additions & 1 deletion Terminal.Gui/Views/ScrollBar/ScrollButton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,17 @@ public ScrollButton ()
CanFocus = false;
NoDecorations = true;
NoPadding = true;
base.ShadowStyle = null;
MouseHoldRepeat = MouseFlags.LeftButtonReleased;

// ReSharper disable once UseObjectOrCollectionInitializer
_orientationHelper = new OrientationHelper (this);
SetGlyph ();
}

/// <inheritdoc/>
/// <remarks>Sets <see cref="ValueChangingEventArgs{T}.NewValue"/> to <see langword="null"/> so that no shadow infrastructure is allocated by default for scroll buttons.</remarks>
protected override void OnInitializingShadowStyle (ValueChangingEventArgs<ShadowStyles?> args) => args.NewValue = null;

/// <summary>
/// Gets or sets the direction this <see cref="ScrollButton"/> scrolls.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ public void TransparentShadow_OverWide_Draws_Transparent_At_Driver_Output ()
output.WriteLine (output1);

DriverAssert.AssertDriverOutputIs ("""
\x1b[30m\x1b[107m*\x1b[90m\x1b[107m \x1b[97m\x1b[40m \x1b[93m\x1b[100m \x1b[97m\x1b[40m🍎
\x1b[30m\x1b[107m*\x1b[90m\x1b[40m \x1b[97m\x1b[40m \x1b[93m\x1b[100m \x1b[97m\x1b[40m🍎
""",
output,
app.Driver);
Expand Down
75 changes: 75 additions & 0 deletions Tests/UnitTestsParallelizable/Views/ButtonTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1005,4 +1005,79 @@ public void Mouse_Click_Does_Not_Raise_Activating ()

button.Dispose ();
}

// Copilot
/// <summary>
/// Verifies that a newly-constructed Button has shadow infrastructure allocated by default,
/// because <see cref="Button.RaiseInitializingShadowStyle"/> fires with
/// <see cref="Button.DefaultShadow"/> and no handler suppresses it.
/// </summary>
[Fact]
public void ShadowStyle_Applied_In_Constructor_By_Default ()
{
Button button = new ();

// Default shadow is applied in the constructor via the InitializingShadowStyle CWP event.
Assert.Equal (Button.DefaultShadow, button.ShadowStyle);
Assert.NotNull (button.Margin.View); // MarginView was created.

button.Dispose ();
}

// Copilot
/// <summary>
/// Verifies that setting <see cref="View.ShadowStyle"/> to <see langword="null"/> after construction
/// clears the shadow views while keeping the existing <c>Margin.View</c> allocated.
/// </summary>
[Fact]
public void ShadowStyle_Null_After_Construction_Clears_ShadowViews_But_Keeps_MarginView ()
{
Button button = new ();
View marginView = button.Margin.View;

Assert.NotNull (marginView);
Assert.NotEmpty (marginView.SubViews);

button.ShadowStyle = null;

Assert.Null (button.ShadowStyle);
Assert.Same (marginView, button.Margin.View);
Assert.Empty (button.Margin.View.SubViews);

button.Dispose ();
}

/// <summary>
/// Verifies that <see cref="ScrollButton"/> does not create shadow infrastructure during construction
/// because its <see cref="Button.OnInitializingShadowStyle"/> override sets
/// <c>args.NewValue = null</c>, eliminating the create-then-destroy allocation pattern.
/// </summary>
[Fact]
public void ScrollButton_OnInitializingShadowStyle_Suppresses_Shadow ()
{
ScrollButton btn = new ();

// OnInitializingShadowStyle sets args.NewValue = null
// → base.ShadowStyle = null is a no-op → no shadow infrastructure created.
Assert.Null (btn.ShadowStyle);
Assert.Null (btn.Margin.View);

btn.Dispose ();
}

// Copilot
/// <summary>
/// Verifies that explicitly setting <see cref="View.ShadowStyle"/> to a non-default value
/// after construction correctly replaces the initial default.
/// </summary>
[Fact]
public void ShadowStyle_ExplicitValue_After_Construction_IsApplied ()
{
Button button = new ();
button.ShadowStyle = ShadowStyles.Transparent;

Assert.Equal (ShadowStyles.Transparent, button.ShadowStyle);

button.Dispose ();
}
}
Loading