using System;
using System.Linq;
using System.Text;
namespace Terminal.Gui {
public partial class View {
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 == null) {
return SuperView?.ColorScheme;
}
return _colorScheme;
}
set {
if (_colorScheme != value) {
_colorScheme = value;
SetNeedsDisplay ();
}
}
}
///
/// 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 == null) {
cs = new ColorScheme ();
}
return Enabled ? cs.Normal : 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 GetFocusColor ()
{
return Enabled ? ColorScheme.Focus : ColorScheme.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 ()
{
return Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled;
}
///
/// Displays the specified character in the specified column and row of the View.
///
/// Column (view-relative).
/// Row (view-relative).
/// Ch.
public void AddRune (int col, int row, Rune ch)
{
if (row < 0 || col < 0)
return;
if (row > _frame.Height - 1 || col > _frame.Width - 1)
return;
Move (col, row);
Driver.AddRune (ch);
}
///
/// Clears and .
///
protected void ClearNeedsDisplay ()
{
_needsDisplayRect = Rect.Empty;
_subViewNeedsDisplay = false;
}
// The view-relative region that needs to be redrawn. Marked internal for unit tests.
internal Rect _needsDisplayRect = Rect.Empty;
///
/// Gets or sets whether the view needs to be redrawn.
///
public bool NeedsDisplay {
get => _needsDisplayRect != Rect.Empty;
set {
if (value) {
SetNeedsDisplay ();
} else {
ClearNeedsDisplay ();
}
}
}
///
/// Sets the area of this view needing to be redrawn to .
///
public void SetNeedsDisplay ()
{
if (!IsInitialized) {
return;
}
SetNeedsDisplay (Bounds);
}
///
/// Expands the area of this view needing to be redrawn to include .
///
/// The view-relative region that needs to be redrawn.
public void SetNeedsDisplay (Rect region)
{
if (_needsDisplayRect.IsEmpty) {
_needsDisplayRect = region;
} else {
var x = Math.Min (_needsDisplayRect.X, region.X);
var y = Math.Min (_needsDisplayRect.Y, region.Y);
var w = Math.Max (_needsDisplayRect.Width, region.Width);
var h = Math.Max (_needsDisplayRect.Height, region.Height);
_needsDisplayRect = new Rect (x, y, w, h);
}
_superView?.SetSubViewNeedsDisplay ();
if (_needsDisplayRect.X < Bounds.X ||
_needsDisplayRect.Y < Bounds.Y ||
_needsDisplayRect.Width > Bounds.Width ||
_needsDisplayRect.Height > Bounds.Height) {
Margin?.SetNeedsDisplay (Margin.Bounds);
Border?.SetNeedsDisplay (Border.Bounds);
Padding?.SetNeedsDisplay (Padding.Bounds);
}
if (_subviews == null) {
return;
}
foreach (var subview in _subviews) {
if (subview.Frame.IntersectsWith (region)) {
var subviewRegion = Rect.Intersect (subview.Frame, region);
subviewRegion.X -= subview.Frame.X;
subviewRegion.Y -= subview.Frame.Y;
subview.SetNeedsDisplay (subviewRegion);
}
}
}
///
/// Gets whether any Subviews need to be redrawn.
///
public bool SubViewNeedsDisplay {
get => _subViewNeedsDisplay;
}
bool _subViewNeedsDisplay;
///
/// Indicates that any Subviews (in the list) need to be repainted.
///
public void SetSubViewNeedsDisplay ()
{
_subViewNeedsDisplay = true;
if (_superView != null && !_superView._subViewNeedsDisplay) {
_superView.SetSubViewNeedsDisplay ();
}
}
///
/// Clears the with the normal background color.
///
///
///
/// This clears the Bounds used by this view.
///
///
public void Clear () => Clear (ViewToScreen(Bounds));
// BUGBUG: This version of the Clear API should be removed. We should have a tenet that says
// "View APIs only deal with View-relative coords". This is only used by ComboBox which can
// be refactored to use the View-relative version.
///
/// Clears the specified screen-relative rectangle with the normal background.
///
///
///
/// The screen-relative rectangle to clear.
public void Clear (Rect regionScreen)
{
var prev = Driver.SetAttribute (GetNormalColor ());
Driver.FillRect (regionScreen);
Driver.SetAttribute (prev);
}
// Clips a rectangle in screen coordinates to the dimensions currently available on the screen
internal Rect ScreenClip (Rect regionScreen)
{
var x = regionScreen.X < 0 ? 0 : regionScreen.X;
var y = regionScreen.Y < 0 ? 0 : regionScreen.Y;
var w = regionScreen.X + regionScreen.Width >= Driver.Cols ? Driver.Cols - regionScreen.X : regionScreen.Width;
var h = regionScreen.Y + regionScreen.Height >= Driver.Rows ? Driver.Rows - regionScreen.Y : regionScreen.Height;
return new Rect (x, y, w, h);
}
///
/// 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 Rect ClipToBounds ()
{
var previous = Driver.Clip;
Driver.Clip = Rect.Intersect (previous, ViewToScreen (Bounds));
return previous;
}
///
/// 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)
{
var hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier;
Application.Driver.SetAttribute (normalColor);
foreach (var rune in text) {
if (rune == hotkeySpec.Value) {
Application.Driver.SetAttribute (hotColor);
continue;
}
Application.Driver.AddRune ((Rune)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);
}
///
/// This moves the cursor to the specified column and row in the view.
///
/// The move.
/// The column to move to, in view-relative coordinates.
/// the row to move to, in view-relative coordinates.
/// Whether to clip the result of the ViewToScreen method,
/// If , the and values are clamped to the screen (terminal) dimensions (0..TerminalDim-1).
public void Move (int col, int row, bool clipped = true)
{
if (Driver.Rows == 0) {
return;
}
ViewToScreen (col, row, out var rCol, out var rRow, clipped);
Driver.Move (rCol, rRow);
}
///
/// 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 virtual LineCanvas LineCanvas { get; set; } = new LineCanvas ();
///
/// 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 it's parent's SuperView. If (the default)
/// this View's method will be called to render the borders.
///
public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
// 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 OnDrawFrames ()
{
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.Bounds);
Border?.OnDrawContent (Border.Bounds);
Padding?.OnDrawContent (Padding.Bounds);
return true;
}
///
/// 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;
}
OnDrawFrames ();
var prevClip = ClipToBounds ();
if (ColorScheme != null) {
//Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
Driver.SetAttribute (GetNormalColor ());
}
// Invoke DrawContentEvent
var dev = new DrawEventArgs (Bounds);
DrawContent?.Invoke (this, dev);
if (!dev.Cancel) {
OnDrawContent (Bounds);
}
Driver.Clip = prevClip;
OnRenderLineCanvas ();
// Invoke DrawContentCompleteEvent
OnDrawContentComplete (Bounds);
// BUGBUG: v2 - We should be able to use View.SetClip here and not have to resort to knowing Driver details.
ClearLayoutNeeded ();
ClearNeedsDisplay ();
}
// 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.Bounds != Rect.Empty) {
foreach (var 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.Runes [0]);
}
LineCanvas.Clear ();
}
if (Subviews.Any (s => s.SuperViewRendersLineCanvas)) {
foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas == true)) {
// Combine the LineCanvas'
LineCanvas.Merge (subview.LineCanvas);
subview.LineCanvas.Clear ();
}
foreach (var 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.Runes [0]);
}
LineCanvas.Clear ();
}
return true;
}
///
/// 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;
///
/// Enables overrides to draw infinitely scrolled content and/or a background behind added controls.
///
/// The view-relative rectangle describing the currently visible viewport into the
///
/// This method will be called before any subviews added with have been drawn.
///
public virtual void OnDrawContent (Rect contentArea)
{
if (NeedsDisplay) {
if (SuperView != null) {
Clear (ViewToScreen (Bounds));
}
if (!string.IsNullOrEmpty (TextFormatter.Text)) {
if (TextFormatter != null) {
TextFormatter.NeedsFormat = true;
}
}
// This should NOT clear
TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (),
HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
Rect.Empty, false);
SetSubViewNeedsDisplay ();
}
// Draw subviews
// TODO: Implement OnDrawSubviews (cancelable);
if (_subviews != null && SubViewNeedsDisplay) {
var subviewsNeedingDraw = _subviews.Where (
view => view.Visible &&
(view.NeedsDisplay ||
view.SubViewNeedsDisplay ||
view.LayoutNeeded)
);
foreach (var 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 ();
//}
}
}
}
///
/// 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;
///
/// Enables overrides after completed drawing infinitely scrolled content and/or a background behind removed controls.
///
/// The view-relative rectangle describing the currently visible viewport into the
///
/// This method will be called after any subviews removed with have been completed drawing.
///
public virtual void OnDrawContentComplete (Rect contentArea)
{
DrawContentComplete?.Invoke (this, new DrawEventArgs (contentArea));
}
}
}