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 }