#nullable enable
using Terminal.Gui;
using Attribute = Terminal.Gui.Attribute;
///
/// 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
{
///
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)
{
CanFocus = true;
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
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)
{
if (Parent?.IsInitialized == false)
{
// When initialized Parent.LayoutSubViews will cause a LayoutAdornments
Parent?.LayoutAdornments ();
}
else
{
Parent?.SetNeedsLayout ();
Parent?.LayoutSubviews ();
}
OnThicknessChanged ();
}
}
}
/// Fired whenever the property changes.
[CanBeNull]
public event EventHandler? ThicknessChanged;
/// Called whenever the property changes.
public void OnThicknessChanged ()
{
ThicknessChanged?.Invoke (this, EventArgs.Empty);
}
#endregion Thickness
#region View Overrides
///
/// Adornments cannot be used as sub-views (see ); setting this property will throw
/// .
///
public override View SuperView
{
get => null!;
set => throw new InvalidOperationException (@"Adornments can not be Subviews or have SuperViews. Use Parent instead.");
}
//internal override Adornment CreateAdornment (Type adornmentType)
//{
// /* Do nothing - Adornments do not have Adornments */
// return null;
//}
internal override void LayoutAdornments ()
{
/* Do nothing - Adornments do not have Adornments */
}
///
/// 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 => Frame with { Location = Point.Empty };
set => throw new InvalidOperationException (@"The Viewport of an Adornment cannot be modified.");
}
///
public override Rectangle FrameToScreen ()
{
if (Parent is null)
{
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)
{
return Parent!.ScreenToFrame (new (location.X - Frame.X, location.Y - Frame.Y));
}
/// Does nothing for Adornment
///
public override bool OnDrawAdornments () { return false; }
/// Redraws the Adornments that comprise the .
public override void OnDrawContent (Rectangle viewport)
{
if (Thickness == Thickness.Empty)
{
return;
}
Rectangle prevClip = SetClip ();
Rectangle screen = ViewportToScreen (viewport);
Attribute normalAttr = GetNormalColor ();
Driver.SetAttribute (normalAttr);
// This just draws/clears the thickness, not the insides.
Thickness.Draw (screen, ToString ());
if (!string.IsNullOrEmpty (TextFormatter.Text))
{
if (TextFormatter is { })
{
TextFormatter.ConstrainToSize = Frame.Size;
TextFormatter.NeedsFormat = true;
}
}
TextFormatter?.Draw (screen, normalAttr, normalAttr, Rectangle.Empty);
if (Subviews.Count > 0)
{
base.OnDrawContent (viewport);
}
if (Driver is { })
{
Driver.Clip = prevClip;
}
ClearLayoutNeeded ();
ClearNeedsDisplay ();
}
/// Does nothing for Adornment
///
public override bool OnRenderLineCanvas () { return false; }
///
/// 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.");
}
#endregion View Overrides
#region Mouse Support
///
/// 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)
{
if (Parent is null)
{
return false;
}
Rectangle frame = Frame;
frame.Offset (Parent.Frame.Location);
return Thickness.Contains (frame, location);
}
///
protected internal override bool? OnMouseEnter (MouseEvent mouseEvent)
{
// Invert Normal
if (Diagnostics.HasFlag (ViewDiagnosticFlags.MouseEnter) && ColorScheme != null)
{
var cs = new ColorScheme (ColorScheme)
{
Normal = new (ColorScheme.Normal.Background, ColorScheme.Normal.Foreground)
};
ColorScheme = cs;
}
return base.OnMouseEnter (mouseEvent);
}
///
protected internal override bool OnMouseLeave (MouseEvent mouseEvent)
{
// Invert Normal
if (Diagnostics.FastHasFlags (ViewDiagnosticFlags.MouseEnter) && ColorScheme != null)
{
var cs = new ColorScheme (ColorScheme)
{
Normal = new (ColorScheme.Normal.Background, ColorScheme.Normal.Foreground)
};
ColorScheme = cs;
}
return base.OnMouseLeave (mouseEvent);
}
#endregion Mouse Support
}