From 6883bc46927bf54dd962d37b3c3ea6047c55993e Mon Sep 17 00:00:00 2001 From: Miguel de Icaza Date: Sat, 16 Dec 2017 21:27:23 -0500 Subject: [PATCH] Improve label --- Core.cs | 54 ++++++++++++++++++++----------- Driver.cs | 2 ++ Views/Label.cs | 86 ++++++++++++++++++++++++++++++++++++++++++++++++-- demo.cs | 8 ++++- 4 files changed, 128 insertions(+), 22 deletions(-) diff --git a/Core.cs b/Core.cs index 2d92c0cff..fb753e4a2 100644 --- a/Core.cs +++ b/Core.cs @@ -7,6 +7,7 @@ // Optimziations // - Add rendering limitation to the exposed area using System; +using System.Collections; using System.Collections.Generic; namespace Terminal { @@ -22,7 +23,7 @@ namespace Terminal { public virtual void MouseEvent (Event.Mouse me) { } } - public class View : Responder { + public class View : Responder, IEnumerable { View container = null; View focused = null; public static ConsoleDriver Driver = Application.Driver; @@ -34,9 +35,6 @@ namespace Terminal { // The frame for the object Rect frame; - // The offset of the first child view inside the view - Point offset; - // The frame for this view public Rect Frame { get => frame; @@ -46,6 +44,12 @@ namespace Terminal { } } + public IEnumerator GetEnumerator () + { + foreach (var v in subviews) + yield return v; + } + public Rect Bounds { get => new Rect (Point.Empty, Frame.Size); set { @@ -75,7 +79,7 @@ namespace Terminal { /// /// /// - public void Add (View view) + public virtual void Add (View view) { if (view == null) return; @@ -150,8 +154,8 @@ namespace Terminal { internal void ViewToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true) { // Computes the real row, col relative to the screen. - rrow = row + frame.X; - rcol = col + frame.Y; + rrow = row + frame.Y; + rcol = col + frame.X; var ccontainer = container; while (ccontainer != null) { rrow += ccontainer.frame.Y; @@ -238,15 +242,17 @@ namespace Terminal { /// /// Performs a redraw of this view and its subviews, only redraws the views that have been flagged for a re-display. /// - public virtual void Redraw () + public virtual void Redraw (Rect region) { - var clipRect = new Rect (offset, frame.Size); + var clipRect = new Rect (Point.Empty, frame.Size); if (subviews != null) { foreach (var view in subviews) { if (view.NeedDisplay) { - if (view.Frame.IntersectsWith (clipRect)) { - view.Redraw (); + if (view.Frame.IntersectsWith (clipRect) && view.Frame.IntersectsWith (region)) { + + // TODO: optimize this by computing the intersection of region and view.Bounds + view.Redraw (view.Bounds); } view.NeedDisplay = false; } @@ -434,7 +440,7 @@ namespace Terminal { /// /// A toplevel view that draws a frame around its region /// - public class Window : Toplevel { + public class Window : Toplevel, IEnumerable { View contentView; string title; @@ -451,7 +457,12 @@ namespace Terminal { this.Title = title; frame.Inflate (-1, -1); contentView = new View (frame); - Add(contentView); + base.Add(contentView); + } + + public IEnumerator GetEnumerator () + { + return contentView.GetEnumerator (); } void DrawFrame () @@ -459,7 +470,12 @@ namespace Terminal { DrawFrame (new Rect(0, 0, Frame.Width, Frame.Height), true); } - public override void Redraw () + public override void Add (View view) + { + contentView.Add (view); + } + + public override void Redraw (Rect bounds) { Driver.SetColor (Colors.Base.Normal); DrawFrame (); @@ -474,7 +490,7 @@ namespace Terminal { Driver.AddCh (' '); } Driver.SetColor (Colors.Dialog.Normal); - contentView.Redraw (); + contentView.Redraw (contentView.Bounds); } } @@ -579,13 +595,13 @@ namespace Terminal { static void Redraw (View view) { - view.Redraw (); + view.Redraw (view.Bounds); Driver.Refresh (); } static void Refresh (View view) { - view.Redraw (); + view.Redraw (view.Bounds); Driver.Refresh (); } @@ -594,7 +610,7 @@ namespace Terminal { Driver.RedrawTop (); View last = null; foreach (var v in toplevels){ - v.Redraw (); + v.Redraw (v.Bounds); last = v; } if (last != null) @@ -635,7 +651,7 @@ namespace Terminal { } else if (wait == false) return; if (state.Toplevel.NeedDisplay) - state.Toplevel.Redraw (); + state.Toplevel.Redraw (state.Toplevel.Bounds); } } diff --git a/Driver.cs b/Driver.cs index d795d5f71..a03ece147 100644 --- a/Driver.cs +++ b/Driver.cs @@ -26,6 +26,8 @@ namespace Terminal { public static class Colors { public static ColorScheme Base, Dialog, Menu, Error; + + public } public abstract class ConsoleDriver { diff --git a/Views/Label.cs b/Views/Label.cs index 056acd0ed..9e992e6e1 100644 --- a/Views/Label.cs +++ b/Views/Label.cs @@ -1,5 +1,7 @@  using System; +using System.Collections.Generic; +using System.Linq; namespace Terminal { public enum TextAlignment { @@ -10,6 +12,8 @@ namespace Terminal { /// Label widget, displays a string at a given position, can include multiple lines. /// public class Label : View { + List lines = new List (); + bool recalcPending = true; string text; TextAlignment textAlignment; @@ -51,8 +55,64 @@ namespace Terminal { this.text = text; } - public override void Redraw () + static char [] whitespace = new char [] { ' ', '\t' }; + + string ClipAndJustify (string str) { + int slen = str.Length; + if (slen > Frame.Width) + return str.Substring (0, Frame.Width); + else { + if (textAlignment == TextAlignment.Justified) { + var words = str.Split (whitespace, StringSplitOptions.RemoveEmptyEntries); + int textCount = words.Sum ((arg) => arg.Length); + + var spaces = (Frame.Width - textCount) / (words.Length - 1); + var extras = (Frame.Width - textCount) % words.Length; + var s = new System.Text.StringBuilder (); + //s.Append ($"tc={textCount} sp={spaces},x={extras} - "); + for (int w = 0; w < words.Length; w++) { + var x = words [w]; + s.Append (x); + if (w + 1 < words.Length) + for (int i = 0; i < spaces; i++) + s.Append (' '); + if (extras > 0) { + s.Append ('_'); + extras--; + } + } + return s.ToString (); + } + return str; + } + } + + void Recalc () + { + lines.Clear (); + if (text.IndexOf ('\n') == -1) { + lines.Add (ClipAndJustify (text)); + return; + } + int textLen = text.Length; + int lp = 0; + for (int i = 0; i < textLen; i++) { + char c = text [i]; + + if (c == '\n') { + lines.Add (ClipAndJustify (text.Substring (lp, i - lp))); + lp = i + 1; + } + } + recalcPending = false; + } + + public override void Redraw (Rect region) + { + if (recalcPending) + Recalc (); + if (TextColor != -1) Driver.SetColor (TextColor); else @@ -60,7 +120,28 @@ namespace Terminal { Clear (); Move (Frame.X, Frame.Y); - Driver.AddStr (text); + for (int line = 0; line < lines.Count; line++) { + if (line < region.Top || line >= region.Bottom) + continue; + var str = lines [line]; + int x; + switch (textAlignment) { + case TextAlignment.Left: + case TextAlignment.Justified: + x = 0; + break; + case TextAlignment.Right: + x = Frame.Right - str.Length; + break; + case TextAlignment.Centered: + x = Frame.Left + (Frame.Width - str.Length) / 2; + break; + default: + throw new ArgumentOutOfRangeException (); + } + Move (x, line); + Driver.AddStr (str); + } } /// @@ -70,6 +151,7 @@ namespace Terminal { get => text; set { text = value; + recalcPending = true; SetNeedsDisplay (); } } diff --git a/demo.cs b/demo.cs index 5df80176e..243257280 100644 --- a/demo.cs +++ b/demo.cs @@ -5,7 +5,13 @@ class Demo { { Application.Init (); var top = Application.Top; - top.Add (new Window (new Rect (0, 0, 80, 24), "Hello")); + var win = new Window (new Rect (0, 0, 80, 24), "Hello") { + new Label (new Rect (0, 0, 40, 3), "1-Hello world, how are you doing today") { TextAlignment = TextAlignment.Left }, + new Label (new Rect (0, 4, 40, 3), "2-Hello world, how are you doing today") { TextAlignment = TextAlignment.Right}, + new Label (new Rect (0, 8, 40, 3), "3-Hello world, how are you doing today") { TextAlignment = TextAlignment.Centered }, + new Label (new Rect (0, 12, 40, 3), "4-Hello world, how are you doing today") { TextAlignment = TextAlignment.Justified} + }; + top.Add (win); Application.Run (); } } \ No newline at end of file