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
{
///
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)
{
Application.GrabbingMouse += Application_GrabbingMouse;
Application.UnGrabbingMouse += Application_UnGrabbingMouse;
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 prev = _thickness;
_thickness = value;
if (prev != _thickness)
{
if (Parent?.IsInitialized == false)
{
// When initialized Parent.LayoutSubViews will cause a LayoutAdornments
Parent?.LayoutAdornments ();
}
else
{
Parent?.SetNeedsLayout ();
Parent?.LayoutSubviews ();
}
OnThicknessChanged (prev);
}
}
}
/// Fired whenever the property changes.
public event EventHandler ThicknessChanged;
/// Called whenever the property changes.
public void OnThicknessChanged (Thickness previousThickness)
{
ThicknessChanged?.Invoke (
this,
new () { Thickness = Thickness, PreviousThickness = previousThickness }
);
}
#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 NotImplementedException ();
}
//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 .
///
public override Rectangle Viewport
{
get => Frame with { Location = Point.Empty };
set => throw new InvalidOperationException ("It makes no sense to set Viewport of a Thickness.");
}
///
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, and add our Frame location to it.
Rectangle parent = Parent.FrameToScreen ();
return new (new (parent.X + Frame.X, parent.Y + Frame.Y), Frame.Size);
}
///
public override Point ScreenToFrame (int x, int y) { return Parent.ScreenToFrame (x - Frame.X, 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 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.Size = Frame.Size;
TextFormatter.NeedsFormat = true;
}
}
TextFormatter?.Draw (screen, normalAttr, normalAttr, Rectangle.Empty);
if (Subviews.Count > 0)
{
base.OnDrawContent (viewport);
}
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; // throw new NotImplementedException ();
set => throw new NotImplementedException ();
}
#endregion View Overrides
#region Mouse Support
///
/// Indicates whether the specified Parent's SuperView-relative coordinates are within the Adornment's Thickness.
///
///
/// The and are relative to the PARENT's SuperView.
///
///
///
/// if the specified Parent's SuperView-relative coordinates are within the Adornment's Thickness.
public override bool Contains (int x, int y)
{
Rectangle frame = Frame;
frame.Offset (Parent.Frame.Location);
return Thickness.Contains (frame, x, y);
}
private Point? _dragPosition;
private Point _startGrabPoint;
///
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);
}
/// Called when a mouse event occurs within the Adornment.
///
///
/// The coordinates are relative to .
///
///
/// A mouse click on the Adornment will cause the Parent to focus.
///
///
/// A mouse drag on the Adornment will cause the Parent to move.
///
///
///
/// , if the event was handled, otherwise.
protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
{
var args = new MouseEventEventArgs (mouseEvent);
if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked))
{
if (Parent.CanFocus && !Parent.HasFocus)
{
Parent.SetFocus ();
Parent.SetNeedsDisplay ();
}
return OnMouseClick (args);
}
if (!Parent.CanFocus || !Parent.Arrangement.HasFlag (ViewArrangement.Movable))
{
return true;
}
// BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/3312
if (!_dragPosition.HasValue && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
{
Parent.SetFocus ();
Application.BringOverlappedTopToFront ();
// Only start grabbing if the user clicks in the Thickness area
// Adornment.Contains takes Parent SuperView=relative coords.
if (Contains (mouseEvent.X + Parent.Frame.X + Frame.X, mouseEvent.Y+ Parent.Frame.Y + Frame.Y))
{
// Set the start grab point to the Frame coords
_startGrabPoint = new (mouseEvent.X + Frame.X, mouseEvent.Y + Frame.Y);
_dragPosition = new (mouseEvent.X, mouseEvent.Y);
Application.GrabMouse (this);
}
return true;
}
if (mouseEvent.Flags is (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
{
if (Application.MouseGrabView == this && _dragPosition.HasValue)
{
if (Parent.SuperView is null)
{
// Redraw the entire app window.
Application.Top.SetNeedsDisplay ();
}
else
{
Parent.SuperView.SetNeedsDisplay ();
}
_dragPosition = new Point (mouseEvent.X, mouseEvent.Y);
Point parentLoc = Parent.SuperView?.ScreenToViewport (mouseEvent.ScreenPosition.X, mouseEvent.ScreenPosition.Y) ?? mouseEvent.ScreenPosition;
GetLocationEnsuringFullVisibility (
Parent,
parentLoc.X - _startGrabPoint.X,
parentLoc.Y - _startGrabPoint.Y,
out int nx,
out int ny,
out _
);
Parent.X = nx;
Parent.Y = ny;
return true;
}
}
if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && _dragPosition.HasValue)
{
_dragPosition = null;
Application.UngrabMouse ();
}
return false;
}
///
protected internal override bool OnMouseLeave (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.OnMouseLeave (mouseEvent);
}
///
protected override void Dispose (bool disposing)
{
Application.GrabbingMouse -= Application_GrabbingMouse;
Application.UnGrabbingMouse -= Application_UnGrabbingMouse;
_dragPosition = null;
base.Dispose (disposing);
}
private void Application_GrabbingMouse (object sender, GrabMouseEventArgs e)
{
if (Application.MouseGrabView == this && _dragPosition.HasValue)
{
e.Cancel = true;
}
}
private void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e)
{
if (Application.MouseGrabView == this && _dragPosition.HasValue)
{
e.Cancel = true;
}
}
#endregion Mouse Support
}