namespace Terminal.Gui;
// TODO: v2 - Missing 3D effect - 3D effects will be drawn by a mechanism separate from Adornments
// TODO: v2 - If a Adornment has focus, navigation keys (e.g Command.NextView) should cycle through SubViews of the Adornments
// QUESTION: How does a user navigate out of an Adornment to another Adornment, or back into the Parent's SubViews?
///
/// Adornments are a special form of that appear outside of the :
/// , , and . They are defined using the
/// class, which specifies the thickness of the sides of a rectangle.
///
///
///
/// There is no prevision for creating additional subclasses of Adornment. It is not abstract to enable unit
/// testing.
///
/// Each of , , and can be customized.
///
public class Adornment : View
{
private Thickness _thickness = Thickness.Empty;
///
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) { Parent = parent; }
/// Gets the rectangle that describes the inner area of the Adornment. The Location is always (0,0).
public override Rectangle Bounds
{
get => new Rectangle (Point.Empty, Thickness?.GetInside (new (Point.Empty, Frame.Size)).Size ?? Frame.Size);
// QUESTION: So why even have a setter then?
set => throw new InvalidOperationException ("It makes no sense to set Bounds of a Thickness.");
}
/// 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; }
///
/// Adornments cannot be used as sub-views (see ); this method always throws an
/// . TODO: Are we sure?
///
public override View SuperView
{
get => null;
set => throw new NotImplementedException ();
}
///
/// Adornments only render to their 's or Parent's SuperView's LineCanvas, so setting this
/// property throws an .
///
public override bool SuperViewRendersLineCanvas
{
get => false; // throw new NotImplementedException ();
set => throw new NotImplementedException ();
}
/// Defines the rectangle that the will use to draw its content.
public Thickness Thickness
{
get => _thickness;
set
{
Thickness prev = _thickness;
_thickness = value;
if (prev != _thickness)
{
Parent?.LayoutAdornments ();
OnThicknessChanged (prev);
}
}
}
///
public override void BoundsToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
{
// Adornments are *Children* of a View, not SubViews. Thus View.BoundsToScreen will not work.
// To get the screen-relative coordinates of a Adornment, we need to know who
// the Parent is
Rectangle parentFrame = Parent?.Frame ?? Frame;
rrow = row + parentFrame.Y;
rcol = col + parentFrame.X;
// We now have rcol/rrow in coordinates relative to our View's SuperView. If our View's SuperView has
// a SuperView, keep going...
Parent?.SuperView?.BoundsToScreen (rcol, rrow, out rcol, out rrow, clipped);
}
///
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 a Adornment, we need to know who
// the Parent is
Rectangle parent = Parent.FrameToScreen ();
// We now have coordinates relative to our View. If our View's SuperView has
// a SuperView, keep going...
return new (new (parent.X + Frame.X, parent.Y + Frame.Y), Frame.Size);
}
/// Does nothing for Adornment
///
public override bool OnDrawAdornments () { return false; }
/// Redraws the Adornments that comprise the .
public override void OnDrawContent (Rectangle contentArea)
{
if (Thickness == Thickness.Empty)
{
return;
}
Rectangle screenBounds = BoundsToScreen (Frame);
Attribute normalAttr = GetNormalColor ();
// This just draws/clears the thickness, not the insides.
Driver.SetAttribute (normalAttr);
Thickness.Draw (screenBounds, (string)(Data ?? string.Empty));
if (!string.IsNullOrEmpty (TextFormatter.Text))
{
if (TextFormatter is { })
{
TextFormatter.Size = Frame.Size;
TextFormatter.NeedsFormat = true;
}
}
TextFormatter?.Draw (screenBounds, normalAttr, normalAttr, Rectangle.Empty);
//base.OnDrawContent (contentArea);
}
/// Does nothing for Adornment
///
public override bool OnRenderLineCanvas () { return false; }
/// Called whenever the property changes.
public virtual void OnThicknessChanged (Thickness previousThickness)
{
ThicknessChanged?.Invoke (
this,
new ThicknessEventArgs { Thickness = Thickness, PreviousThickness = previousThickness }
);
}
/// Fired whenever the property changes.
public event EventHandler ThicknessChanged;
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 */
}
///
public override bool OnMouseEvent (MouseEvent mouseEvent)
{
var args = new MouseEventEventArgs (mouseEvent);
if (MouseEvent (mouseEvent))
{
return true;
}
if (mouseEvent.Flags == MouseFlags.Button1Clicked)
{
if (Parent.CanFocus && !Parent.HasFocus)
{
Parent.SetFocus ();
Parent.SetNeedsDisplay ();
}
return OnMouseClick (args);
}
if (mouseEvent.Flags == MouseFlags.Button2Clicked)
{
return OnMouseClick (args);
}
if (mouseEvent.Flags == MouseFlags.Button3Clicked)
{
return OnMouseClick (args);
}
if (mouseEvent.Flags == MouseFlags.Button4Clicked)
{
return OnMouseClick (args);
}
return false;
}
///
public override bool OnMouseEnter (MouseEvent mouseEvent)
{
var args = new MouseEventEventArgs (mouseEvent);
return args.Handled || base.OnMouseEnter (mouseEvent);
}
///
public override bool OnMouseLeave (MouseEvent mouseEvent)
{
var args = new MouseEventEventArgs (mouseEvent);
return args.Handled || base.OnMouseLeave (mouseEvent);
}
}