namespace Terminal.Gui; public partial class View { // The view-relative region that needs to be redrawn. Marked internal for unit tests. internal Rectangle _needsDisplayRect = Rectangle.Empty; private ColorScheme _colorScheme; /// The color scheme for this view, if it is not defined, it returns the 's color scheme. public virtual ColorScheme ColorScheme { get { if (_colorScheme is null) { return SuperView?.ColorScheme; } return _colorScheme; } set { if (_colorScheme != value) { _colorScheme = value; SetNeedsDisplay (); } } } /// The canvas that any line drawing that is to be shared by subviews of this view should add lines to. /// adds border lines to this LineCanvas. public LineCanvas LineCanvas { get; } = new (); /// Gets or sets whether the view needs to be redrawn. public bool NeedsDisplay { get => _needsDisplayRect != Rectangle.Empty; set { if (value) { SetNeedsDisplay (); } else { ClearNeedsDisplay (); } } } /// Gets whether any Subviews need to be redrawn. public bool SubViewNeedsDisplay { get; private set; } /// /// Gets or sets whether this View will use it's SuperView's for rendering any border /// lines. If the rendering of any borders drawn by this Frame will be done by its parent's /// SuperView. If (the default) this View's method will be /// called to render the borders. /// public virtual bool SuperViewRendersLineCanvas { get; set; } = false; /// Draws the specified character in the specified viewport-relative column and row of the View. /// /// The top-left corner of the visible content area is ViewPort.Location. /// /// Column (viewport-relative). /// Row (viewport-relative). /// Ch. public void AddRune (int col, int row, Rune ch) { if (row < 0 || col < 0) { return; } // BUGBUG: This should be Viewport.Size if (row > _frame.Height - 1 || col > _frame.Width - 1) { return; } Move (col, row); Driver.AddRune (ch); } /// Clears with the normal background. /// public void Clear () { Clear (Viewport); } /// Clears the specified -relative rectangle with the normal background. /// /// The Viewport-relative rectangle to clear. public void Clear (Rectangle viewport) { if (Driver is null) { return; } Attribute prev = Driver.SetAttribute (GetNormalColor ()); // Clamp the region to the bounds of the view viewport = Rectangle.Intersect (viewport, Viewport); Driver.FillRect (ViewportToScreen (viewport)); Driver.SetAttribute (prev); } /// Expands the 's clip region to include . /// /// The current screen-relative clip region, which can be then re-applied by setting /// . /// /// /// /// If and do not intersect, the clip region will be set to /// . /// /// public Rectangle ClipToViewport () { if (Driver is null) { return Rectangle.Empty; } Rectangle previous = Driver.Clip; Driver.Clip = Rectangle.Intersect (previous, ViewportToScreen (Viewport)); return previous; } /// /// Draws the view. Causes the following virtual methods to be called (along with their related events): /// , . /// /// /// /// Always use (view-relative) when calling , NOT /// (superview-relative). /// /// /// Views should set the color that they want to use on entry, as otherwise this will inherit the last color that /// was set globally on the driver. /// /// /// Overrides of must ensure they do not set Driver.Clip to a clip /// region larger than the property, as this will cause the driver to clip the entire /// region. /// /// public void Draw () { if (!CanBeVisible (this)) { return; } OnDrawAdornments (); Rectangle prevClip = ClipToViewport (); if (ColorScheme is { }) { //Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ()); Driver?.SetAttribute (GetNormalColor ()); } // Invoke DrawContentEvent var dev = new DrawEventArgs (Viewport); DrawContent?.Invoke (this, dev); if (!dev.Cancel) { OnDrawContent (Viewport); } if (Driver is { }) { Driver.Clip = prevClip; } OnRenderLineCanvas (); // Invoke DrawContentCompleteEvent OnDrawContentComplete (Viewport); // BUGBUG: v2 - We should be able to use View.SetClip here and not have to resort to knowing Driver details. ClearLayoutNeeded (); ClearNeedsDisplay (); } /// Event invoked when the content area of the View is to be drawn. /// /// Will be invoked before any subviews added with have been drawn. /// /// Rect provides the view-relative rectangle describing the currently visible viewport into the /// . /// /// public event EventHandler DrawContent; /// Event invoked when the content area of the View is completed drawing. /// /// Will be invoked after any subviews removed with have been completed drawing. /// /// Rect provides the view-relative rectangle describing the currently visible viewport into the /// . /// /// public event EventHandler DrawContentComplete; /// Utility function to draw strings that contain a hotkey. /// String to display, the hotkey specifier before a letter flags the next letter as the hotkey. /// Hot color. /// Normal color. /// /// /// The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by /// default. /// /// The hotkey specifier can be changed via /// public void DrawHotString (string text, Attribute hotColor, Attribute normalColor) { Rune hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier; Application.Driver.SetAttribute (normalColor); foreach (Rune rune in text.EnumerateRunes ()) { if (rune == new Rune (hotkeySpec.Value)) { Application.Driver.SetAttribute (hotColor); continue; } Application.Driver.AddRune (rune); Application.Driver.SetAttribute (normalColor); } } /// /// Utility function to draw strings that contains a hotkey using a and the "focused" /// state. /// /// String to display, the underscore before a letter flags the next letter as the hotkey. /// /// If set to this uses the focused colors from the color scheme, otherwise /// the regular ones. /// /// The color scheme to use. public void DrawHotString (string text, bool focused, ColorScheme scheme) { if (focused) { DrawHotString (text, scheme.HotFocus, scheme.Focus); } else { DrawHotString ( text, Enabled ? scheme.HotNormal : scheme.Disabled, Enabled ? scheme.Normal : scheme.Disabled ); } } /// Determines the current based on the value. /// /// if is or /// if is . If it's /// overridden can return other values. /// public virtual Attribute GetFocusColor () { ColorScheme cs = ColorScheme; if (ColorScheme is null) { cs = new (); } return Enabled ? cs.Focus : cs.Disabled; } /// Determines the current based on the value. /// /// if is or /// if is . If it's /// overridden can return other values. /// public virtual Attribute GetHotNormalColor () { ColorScheme cs = ColorScheme; if (ColorScheme is null) { cs = new (); } return Enabled ? cs.HotNormal : cs.Disabled; } /// Determines the current based on the value. /// /// if is or /// if is . If it's /// overridden can return other values. /// public virtual Attribute GetNormalColor () { ColorScheme cs = ColorScheme; if (ColorScheme is null) { cs = new (); } return Enabled ? cs.Normal : cs.Disabled; } /// This moves the cursor to the specified view-relative column and row in the view. /// /// The top-left corner of the visible content area is ViewPort.Location. /// /// Column (viewport-relative). /// Row (viewport-relative). public void Move (int col, int row) { if (Driver is null || Driver?.Rows == 0) { return; } Rectangle screen = ViewportToScreen (new (col, row, 0, 0)); // TODO: Clamp this to stay within the View's Viewport Driver?.Move (screen.X, screen.Y); } // TODO: Make this cancelable /// /// Prepares . If is true, only the /// of this view's subviews will be rendered. If is /// false (the default), this method will cause the be prepared to be rendered. /// /// public virtual bool OnDrawAdornments () { if (!IsInitialized) { return false; } // Each of these renders lines to either this View's LineCanvas // Those lines will be finally rendered in OnRenderLineCanvas Margin?.OnDrawContent (Margin.Viewport); Border?.OnDrawContent (Border.Viewport); Padding?.OnDrawContent (Padding.Viewport); return true; } /// /// Draws the view's content, including Subviews. /// /// /// /// The parameter is provided as a convenience; it has the same values as the /// property. /// /// /// The Location and Size indicate what part of the View's virtual content area, defined /// by , is visible and should be drawn. The coordinates taken by and /// are relative to , thus if ViewPort.Location.Y is 5 /// the 5th row of the content should be drawn using MoveTo (x, 5). /// /// /// If is larger than ViewPort.Size drawing code should use /// to constrain drawing for better performance. /// /// /// The may define smaller area than ; complex drawing code can be more /// efficient by using to constrain drawing for better performance. /// /// /// Overrides should loop through the subviews and call . /// /// /// /// The rectangle describing the currently visible viewport into the ; has the same value as /// . /// public virtual void OnDrawContent (Rectangle viewport) { if (NeedsDisplay) { if (SuperView is { }) { Clear (viewport); } if (!string.IsNullOrEmpty (TextFormatter.Text)) { if (TextFormatter is { }) { TextFormatter.NeedsFormat = true; } } // This should NOT clear TextFormatter?.Draw ( ViewportToScreen (viewport), HasFocus ? GetFocusColor () : GetNormalColor (), HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (), Rectangle.Empty ); SetSubViewNeedsDisplay (); } // TODO: Move drawing of subviews to a separate OnDrawSubviews virtual method // Draw subviews // TODO: Implement OnDrawSubviews (cancelable); if (_subviews is { } && SubViewNeedsDisplay) { IEnumerable subviewsNeedingDraw = _subviews.Where ( view => view.Visible && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded) ); foreach (View view in subviewsNeedingDraw) { //view.Frame.IntersectsWith (bounds)) { // && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) { if (view.LayoutNeeded) { view.LayoutSubviews (); } // Draw the subview // Use the view's bounds (view-relative; Location will always be (0,0) //if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) { view.Draw (); //} } } } /// /// Called after to enable overrides. /// /// /// The viewport-relative rectangle describing the currently visible viewport into the /// /// public virtual void OnDrawContentComplete (Rectangle viewport) { DrawContentComplete?.Invoke (this, new (viewport)); } // TODO: Make this cancelable /// /// Renders . If is true, only the /// of this view's subviews will be rendered. If is /// false (the default), this method will cause the to be rendered. /// /// public virtual bool OnRenderLineCanvas () { if (!IsInitialized) { return false; } // If we have a SuperView, it'll render our frames. if (!SuperViewRendersLineCanvas && LineCanvas.Viewport != Rectangle.Empty) { foreach (KeyValuePair p in LineCanvas.GetCellMap ()) { // Get the entire map Driver.SetAttribute (p.Value.Attribute ?? ColorScheme.Normal); Driver.Move (p.Key.X, p.Key.Y); // TODO: #2616 - Support combining sequences that don't normalize Driver.AddRune (p.Value.Rune); } LineCanvas.Clear (); } if (Subviews.Any (s => s.SuperViewRendersLineCanvas)) { foreach (View subview in Subviews.Where (s => s.SuperViewRendersLineCanvas)) { // Combine the LineCanvas' LineCanvas.Merge (subview.LineCanvas); subview.LineCanvas.Clear (); } foreach (KeyValuePair p in LineCanvas.GetCellMap ()) { // Get the entire map Driver.SetAttribute (p.Value.Attribute ?? ColorScheme.Normal); Driver.Move (p.Key.X, p.Key.Y); // TODO: #2616 - Support combining sequences that don't normalize Driver.AddRune (p.Value.Rune); } LineCanvas.Clear (); } return true; } /// Sets the area of this view needing to be redrawn to . /// /// If the view has not been initialized ( is ), this method /// does nothing. /// public void SetNeedsDisplay () { if (IsInitialized) { SetNeedsDisplay (Viewport); } } /// Expands the area of this view needing to be redrawn to include . /// /// /// The location of are relative to the View's content, bound by Size.Empty and /// . /// /// /// If the view has not been initialized ( is ), the area to be /// redrawn will be the . /// /// /// The content-relative region that needs to be redrawn. public virtual void SetNeedsDisplay (Rectangle region) { if (!IsInitialized) { _needsDisplayRect = region; return; } if (_needsDisplayRect.IsEmpty) { _needsDisplayRect = region; } else { int x = Math.Min (_needsDisplayRect.X, region.X); int y = Math.Min (_needsDisplayRect.Y, region.Y); int w = Math.Max (_needsDisplayRect.Width, region.Width); int h = Math.Max (_needsDisplayRect.Height, region.Height); _needsDisplayRect = new (x, y, w, h); } _superView?.SetSubViewNeedsDisplay (); Margin?.SetNeedsDisplay (Margin.Viewport); Border?.SetNeedsDisplay (Border.Viewport); Padding?.SetNeedsDisplay (Padding.Viewport); foreach (View subview in Subviews) { if (subview.Frame.IntersectsWith (region)) { Rectangle subviewRegion = Rectangle.Intersect (subview.Frame, region); subviewRegion.X -= subview.Frame.X; subviewRegion.Y -= subview.Frame.Y; subview.SetNeedsDisplay (subviewRegion); } } } /// Indicates that any Subviews (in the list) need to be repainted. public void SetSubViewNeedsDisplay () { SubViewNeedsDisplay = true; if (_superView is { } && !_superView.SubViewNeedsDisplay) { _superView.SetSubViewNeedsDisplay (); } } /// Clears and . protected virtual void ClearNeedsDisplay () { _needsDisplayRect = Rectangle.Empty; SubViewNeedsDisplay = false; } }