namespace Terminal.Gui.ViewBase;
///
/// 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
///
protected override IApplication? GetApp () => Parent?.App;
///
protected override IDriver? GetDriver () => Parent?.Driver ?? base.GetDriver ();
// If a scheme is explicitly set, use that. Otherwise, use the scheme of the parent view.
private Scheme? _scheme;
///
protected override bool OnGettingScheme (out Scheme? scheme)
{
scheme = _scheme ?? Parent?.GetScheme () ?? SchemeManager.GetScheme (Schemes.Base);
return true;
}
///
protected override bool OnSettingScheme (ValueChangingEventArgs args)
{
Parent?.SetNeedsDraw ();
_scheme = args.NewValue;
return false;
}
///
/// 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.
if (Driver is { })
{
Thickness.Draw (Driver, ViewportToScreen (Viewport), Diagnostics, ToString ());
}
SetNeedsDraw ();
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);
}
///
/// INTERNAL: Gets all Views (Subviews and Adornments) in the of hierarchcy that are at ,
/// regardless of whether they will be drawn or see mouse events or not. Views with set to will not be included.
/// The list is ordered by depth. The deepest View is at the end of the list (the topmost View is at element 0).
///
/// The root Adornment from which the search for subviews begins.
/// The screen-relative location where the search for views is focused.
/// A list of views that are located under the specified point.
internal static List GetViewsAtLocation (Adornment? adornment, in Point screenLocation)
{
List result = [];
if (adornment is null || adornment.Thickness == Thickness.Empty)
{
return result;
}
Point superViewRelativeLocation = adornment.Parent!.SuperView?.ScreenToViewport (screenLocation) ?? screenLocation;
if (adornment.Contains (superViewRelativeLocation))
{
List adornmentResult = GetViewsAtLocation (adornment as View, screenLocation);
if (adornmentResult.Count > 0)
{
result.AddRange (adornmentResult);
}
}
return result;
}
#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;
}
}