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