#nullable enable
namespace Terminal.Gui;
///
/// Adornments are a special form of that appear outside the :
/// , , and . They are defined using the
/// class, which specifies the thickness of the sides of a rectangle.
///
///
///
/// Each of , , and has slightly different
/// behavior relative to , , keyboard input, and
/// mouse input. Each can be customized by manipulating their SubViews.
///
///
public class Adornment : View, IDesignable
{
///
public Adornment ()
{
/* Do nothing; A parameter-less constructor is required to support all views unit tests. */
}
/// Constructs a new adornment for the view specified by .
///
public Adornment (View parent)
{
// By default, Adornments can't get focus; has to be enabled specifically.
CanFocus = false;
TabStop = TabBehavior.NoStop;
Parent = parent;
}
/// The Parent of this Adornment (the View this Adornment surrounds).
///
/// Adornments are distinguished from typical View classes in that they are not sub-views, but have a parent/child
/// relationship with their containing View.
///
public View? Parent { get; set; }
#region Thickness
///
/// Gets or sets whether the Adornment will draw diagnostic information. This is a bit-field of
/// .
///
///
/// The static property is used as the default value for this property.
///
public new ViewDiagnosticFlags Diagnostics { get; set; } = View.Diagnostics;
private Thickness _thickness = Thickness.Empty;
/// Defines the rectangle that the will use to draw its content.
public Thickness Thickness
{
get => _thickness;
set
{
Thickness current = _thickness;
_thickness = value;
if (current != _thickness)
{
Parent?.SetAdornmentFrames ();
SetNeedsLayout ();
SetNeedsDraw ();
OnThicknessChanged ();
}
}
}
/// Fired whenever the property changes.
public event EventHandler? ThicknessChanged;
/// Called whenever the property changes.
public void OnThicknessChanged () { ThicknessChanged?.Invoke (this, EventArgs.Empty); }
#endregion Thickness
#region View Overrides
///
/// Gets the rectangle that describes the area of the Adornment. The Location is always (0,0).
/// The size is the size of the .
///
///
/// The Viewport of an Adornment cannot be modified. Attempting to set this property will throw an
/// .
///
public override Rectangle Viewport
{
get => base.Viewport;
set => throw new InvalidOperationException (@"The Viewport of an Adornment cannot be modified.");
}
///
public override Rectangle FrameToScreen ()
{
if (Parent is null)
{
// While there are no real use cases for an Adornment being a subview, we support it for
// testing. E.g. in AllViewsTester.
if (SuperView is { })
{
Point super = SuperView.ViewportToScreen (Frame.Location);
return new (super, Frame.Size);
}
return Frame;
}
// Adornments are *Children* of a View, not SubViews. Thus View.FrameToScreen will not work.
// To get the screen-relative coordinates of an Adornment, we need get the parent's Frame
// in screen coords, ...
Rectangle parentScreen = Parent.FrameToScreen ();
// ...and add our Frame location to it.
return new (new (parentScreen.X + Frame.X, parentScreen.Y + Frame.Y), Frame.Size);
}
///
public override Point ScreenToFrame (in Point location)
{
View? parentOrSuperView = Parent;
if (parentOrSuperView is null)
{
// While there are no real use cases for an Adornment being a subview, we support it for
// testing. E.g. in AllViewsTester.
parentOrSuperView = SuperView;
if (parentOrSuperView is null)
{
return Point.Empty;
}
}
return parentOrSuperView.ScreenToFrame (new (location.X - Frame.X, location.Y - Frame.Y));
}
///
/// Called when the of the Adornment is to be cleared.
///
/// to stop further clearing.
protected override bool OnClearingViewport ()
{
if (Thickness == Thickness.Empty)
{
return true;
}
// This just draws/clears the thickness, not the insides.
Thickness.Draw (ViewportToScreen (Viewport), Diagnostics, ToString ());
NeedsDraw = true;
return true;
}
///
protected override bool OnDrawingText () { return Thickness == Thickness.Empty; }
///
protected override bool OnDrawingSubViews () { return Thickness == Thickness.Empty; }
/// Does nothing for Adornment
///
protected override bool OnRenderingLineCanvas () { return true; }
///
/// Adornments only render to their 's or Parent's SuperView's LineCanvas, so setting this
/// property throws an .
///
public override bool SuperViewRendersLineCanvas
{
get => false;
set => throw new InvalidOperationException (@"Adornment can only render to their Parent or Parent's Superview.");
}
///
/// Indicates whether the specified Parent's SuperView-relative coordinates are within the Adornment's Thickness.
///
///
/// The is relative to the PARENT's SuperView.
///
///
///
/// if the specified Parent's SuperView-relative coordinates are within the Adornment's
/// Thickness.
///
public override bool Contains (in Point location)
{
View? parentOrSuperView = Parent;
if (parentOrSuperView is null)
{
// While there are no real use cases for an Adornment being a subview, we support it for
// testing. E.g. in AllViewsTester.
parentOrSuperView = SuperView;
if (parentOrSuperView is null)
{
return false;
}
}
Rectangle outside = Frame;
outside.Offset (parentOrSuperView.Frame.Location);
return Thickness.Contains (outside, location);
}
#endregion View Overrides
///
bool IDesignable.EnableForDesign ()
{
// This enables AllViewsTester to show something useful.
Thickness = new (3);
Frame = new (0, 0, 10, 10);
Diagnostics = ViewDiagnosticFlags.Thickness;
return true;
}
}