#nullable enable
namespace Terminal.Gui;
/// The Margin for a . Accessed via
///
/// See the class.
///
public class Margin : Adornment
{
///
public Margin ()
{ /* Do nothing; A parameter-less constructor is required to support all views unit tests. */
}
///
public Margin (View parent) : base (parent)
{
/* Do nothing; View.CreateAdornment requires a constructor that takes a parent */
// BUGBUG: We should not set HighlightStyle.Pressed here, but wherever it is actually needed
// HighlightStyle |= HighlightStyle.Pressed;
Highlight += Margin_Highlight;
SubviewLayout += Margin_LayoutStarted;
// Margin should not be focusable
CanFocus = false;
}
private bool _pressed;
private ShadowView? _bottomShadow;
private ShadowView? _rightShadow;
///
public override void BeginInit ()
{
base.BeginInit ();
if (Parent is null)
{
return;
}
ShadowStyle = base.ShadowStyle;
}
///
/// The color scheme for the Margin. If set to , gets the 's
/// scheme. color scheme.
///
public override ColorScheme? ColorScheme
{
get
{
if (base.ColorScheme is { })
{
return base.ColorScheme;
}
return (Parent?.SuperView?.ColorScheme ?? Colors.ColorSchemes ["TopLevel"])!;
}
set
{
base.ColorScheme = value;
Parent?.SetNeedsDraw ();
}
}
///
protected override bool OnClearingViewport (Rectangle viewport)
{
if (Thickness == Thickness.Empty)
{
return true;
}
Rectangle screen = ViewportToScreen (viewport);
if (ShadowStyle != ShadowStyle.None)
{
// Don't clear where the shadow goes
screen = Rectangle.Inflate (screen, -1, -1);
}
// This just draws/clears the thickness, not the insides.
Thickness.Draw (screen, Diagnostics, ToString ());
return true;
}
/////
////protected override bool OnDrawSubviews (Rectangle viewport) { return true; }
//protected override bool OnDrawComplete (Rectangle viewport)
//{
// DoDrawSubviews (viewport);
// return true;
//}
///
/// Sets whether the Margin includes a shadow effect. The shadow is drawn on the right and bottom sides of the
/// Margin.
///
public ShadowStyle SetShadow (ShadowStyle style)
{
if (_rightShadow is { })
{
Remove (_rightShadow);
_rightShadow.Dispose ();
_rightShadow = null;
}
if (_bottomShadow is { })
{
Remove (_bottomShadow);
_bottomShadow.Dispose ();
_bottomShadow = null;
}
if (ShadowStyle != ShadowStyle.None)
{
// Turn off shadow
Thickness = new (Thickness.Left, Thickness.Top, Thickness.Right - 1, Thickness.Bottom - 1);
}
if (style != ShadowStyle.None)
{
// Turn on shadow
Thickness = new (Thickness.Left, Thickness.Top, Thickness.Right + 1, Thickness.Bottom + 1);
}
if (style != ShadowStyle.None)
{
_rightShadow = new ()
{
X = Pos.AnchorEnd (1),
Y = 0,
Width = 1,
Height = Dim.Fill (),
ShadowStyle = style,
Orientation = Orientation.Vertical
};
_bottomShadow = new ()
{
X = 0,
Y = Pos.AnchorEnd (1),
Width = Dim.Fill (),
Height = 1,
ShadowStyle = style,
Orientation = Orientation.Horizontal
};
Add (_rightShadow, _bottomShadow);
}
return style;
}
///
public override ShadowStyle ShadowStyle
{
get => base.ShadowStyle;
set
{
base.ShadowStyle = SetShadow (value);
}
}
private const int PRESS_MOVE_HORIZONTAL = 1;
private const int PRESS_MOVE_VERTICAL = 0;
private void Margin_Highlight (object? sender, CancelEventArgs e)
{
if (Thickness == Thickness.Empty || ShadowStyle == ShadowStyle.None)
{
return;
}
if (_pressed && e.NewValue == HighlightStyle.None)
{
// If the view is pressed and the highlight is being removed, move the shadow back.
// Note, for visual effects reasons, we only move horizontally.
// TODO: Add a setting or flag that lets the view move vertically as well.
Thickness = new (Thickness.Left - PRESS_MOVE_HORIZONTAL, Thickness.Top - PRESS_MOVE_VERTICAL, Thickness.Right + PRESS_MOVE_HORIZONTAL, Thickness.Bottom + PRESS_MOVE_VERTICAL);
if (_rightShadow is { })
{
_rightShadow.Visible = true;
}
if (_bottomShadow is { })
{
_bottomShadow.Visible = true;
}
_pressed = false;
return;
}
if (!_pressed && e.NewValue.HasFlag (HighlightStyle.Pressed))
{
// If the view is not pressed and we want highlight move the shadow
// Note, for visual effects reasons, we only move horizontally.
// TODO: Add a setting or flag that lets the view move vertically as well.
Thickness = new (Thickness.Left + PRESS_MOVE_HORIZONTAL, Thickness.Top + PRESS_MOVE_VERTICAL, Thickness.Right - PRESS_MOVE_HORIZONTAL, Thickness.Bottom - PRESS_MOVE_VERTICAL);
_pressed = true;
if (_rightShadow is { })
{
_rightShadow.Visible = false;
}
if (_bottomShadow is { })
{
_bottomShadow.Visible = false;
}
}
}
private void Margin_LayoutStarted (object? sender, LayoutEventArgs e)
{
// Adjust the shadow such that it is drawn aligned with the Border
if (_rightShadow is { } && _bottomShadow is { })
{
switch (ShadowStyle)
{
case ShadowStyle.Transparent:
// BUGBUG: This doesn't work right for all Border.Top sizes - Need an API on Border that gives top-right location of line corner.
_rightShadow.Y = Parent!.Border.Thickness.Top > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).Y + 1 : 0;
break;
case ShadowStyle.Opaque:
// BUGBUG: This doesn't work right for all Border.Top sizes - Need an API on Border that gives top-right location of line corner.
_rightShadow.Y = Parent!.Border.Thickness.Top > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).Y + 1 : 0;
_bottomShadow.X = Parent.Border.Thickness.Left > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).X + 1 : 0;
break;
case ShadowStyle.None:
default:
_rightShadow.Y = 0;
_bottomShadow.X = 0;
break;
}
}
}
}