#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; } }