From 7a0c522a209cdc0ebc691d488fd266b5764f20b6 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Wed, 3 Jun 2020 11:16:35 -0600 Subject: [PATCH 1/2] Upgraded ScrollView + Charmap (#601) Note this PR should not be merged until after #600 is in. I went on a rampage tonight. It all started with wanting to use more/better characters for frame and other UI elements like the round corners: ![image](https://user-images.githubusercontent.com/585482/83601742-659ba800-a52e-11ea-9ee9-c888a7db5444.png) I decided I needed a character map app that would let me test which fonts had which Unicode sets in them. As a result we have this PR - Fixes `ScrollView` in several key ways: - It now supports Computed layout and has constructors that don't require parameters. - `ScrollBarViews` are now positioned using Computed layout versus error prone absoulte - `ScrollBarViews` now correctly position themselves when one, either, or both are on/off. - `IsVertical` is now a public property that does the expected thing when changed - Mouse handling is better; there's still a bug where the mouse doesn't get grabbed by the `ScrollView` initially but I think this is a broader problem. I need @BDisp's help on this. - The `Scrolling` Scenario was enhanced to demo dynamically adding/removing horizontal/vertical scrollbars (and to prove it was working right). - I Enabled easy "infinite scroll capability" - CharMap literally lets you scroll over `int.MaxValue / 16` rows of data. Filling a `ContentView` with all of this and panning it around won't work. So I needed a way of having `Redraw` give me virtual coordinates. I did this by defining `OnDrawContent(Rect viewport)` and it's associated `event`: ```csharp /// /// 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 viewport) { DrawContent?.Invoke (this, viewport); } ``` I originally just implemented this pattern in `ScrollView`. Then I realized I wanted the same thing out of ALL `Views`. Namely: the ability to do drawing on an event, particularly to be able to paint something in the background. So I added it to `View`. Note, that these changes mean we are about 3 small steps away from moving the scollbars from `ScrollView` into ALL views. Which makes a lot of sense to me because I don't think we want to implement duplicative logic in, say `ListView` and `TextView` as well. Why not just do it once? Along the way I fixed some other things: - The `Checkbox.Toggled` event now passes state. Here's some gifs. ![](https://i.imgur.com/o5nP5Lo.gif) Note: - Scrollbars appear dynamically. - Fast scrolling of huge data (using no memory). - Static header - Dynamic scrollbars on/off - Note the bottom/right corner now draw correctly in all situations --- Example/demo.cs | 4 +- Terminal.Gui/Core/View.cs | 32 ++++ Terminal.Gui/Terminal.Gui.csproj | 9 + Terminal.Gui/Views/Checkbox.cs | 21 ++- Terminal.Gui/Views/ScrollView.cs | 178 +++++++++++++----- UICatalog/Scenarios/Buttons.cs | 10 + UICatalog/Scenarios/CharacterMap.cs | 123 ++++++++++++ UICatalog/Scenarios/Scrolling.cs | 35 +++- .../{TextAlignment.cs => TextAlignments.cs} | 2 +- UICatalog/Scenarios/Unicode.cs | 11 +- 10 files changed, 348 insertions(+), 77 deletions(-) create mode 100644 UICatalog/Scenarios/CharacterMap.cs rename UICatalog/Scenarios/{TextAlignment.cs => TextAlignments.cs} (97%) diff --git a/Example/demo.cs b/Example/demo.cs index 0e431422a..2e4be94e4 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -335,12 +335,12 @@ static class Demo { $"{mi.Title.ToString ()} selected. Is from submenu: {mi.GetMenuBarItem ()}", "Ok"); } - static void MenuKeysStyle_Toggled (object sender, EventArgs e) + static void MenuKeysStyle_Toggled (object sender, bool e) { menu.UseKeysUpDownAsKeysLeftRight = menuKeysStyle.Checked; } - static void MenuAutoMouseNav_Toggled (object sender, EventArgs e) + static void MenuAutoMouseNav_Toggled (object sender, bool e) { menu.WantMousePositionReports = menuAutoMouseNav.Checked; } diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 1d50cfe5b..c6936909e 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -929,6 +929,9 @@ namespace Terminal.Gui { { var clipRect = new Rect (Point.Empty, frame.Size); + // Invoke DrawContentEvent + OnDrawContent (bounds); + if (subviews != null) { foreach (var view in subviews) { if (view.NeedDisplay != null && (!view.NeedDisplay.IsEmpty || view.childNeedsDisplay)) { @@ -941,7 +944,11 @@ namespace Terminal.Gui { // Clip the sub-view var savedClip = ClipToBounds (); + + // Draw the subview view.Redraw (view.Bounds); + + // Undo the clip Driver.Clip = savedClip; } view.NeedDisplay = Rect.Empty; @@ -952,6 +959,31 @@ namespace Terminal.Gui { 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; + + /// + /// 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 viewport) + { + DrawContent?.Invoke (this, viewport); + } + /// /// Causes the specified subview to have focus. /// diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index 77c3a2342..d78c6950d 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -78,6 +78,15 @@ * More robust error handing in Pos/Dim. Fixes #355 stack overflow with Pos based on the size of windows at startup. Added a OnResized action to set the Pos after the terminal are resized. (Thanks @bdisp!) * Fixes #389 Window layouting breaks when resizing. (Thanks @bdisp!) * Fixes #557 MessageBox needs to take ustrings (BREAKING CHANGE). (Thanks @tig!) + * Fixes ScrollView in several key ways. (Thanks @tig!) + * Now supports Computed layout and has constructors that don't require parameters. + * ScrollBarViews are now positioned using Computed layout versus error prone absoulte + * ScrollBarViews now correctly position themselves when one, either, or both are on/off. + * IsVertical is now a public property that does the expected thing when changed + * Mouse handling is better; there's still a bug where the mouse doesn't get grabbed by the ScrollView initially but I think this is a broader problem. I need @BDisp's help on this. + * Supports "infinite scrolling" via the new OnDrawContent/DrawContent event on the View class. + * The Scrolling Scenario was enhanced to demo dynamically adding/removing horizontal/vertical scrollbars (and to prove it was working right). + * The Checkbox.Toggled event is now an EventHandler event and passes previous state. (Thanks @tig!) 0.81: * Fix ncurses engine for macOS/Linux, it works again diff --git a/Terminal.Gui/Views/Checkbox.cs b/Terminal.Gui/Views/Checkbox.cs index 57421db73..a9c18c045 100644 --- a/Terminal.Gui/Views/Checkbox.cs +++ b/Terminal.Gui/Views/Checkbox.cs @@ -23,9 +23,16 @@ namespace Terminal.Gui { /// /// Client code can hook up to this event, it is /// raised when the is activated either with - /// the mouse or the keyboard. + /// the mouse or the keyboard. The passed bool contains the previous state. /// - public event EventHandler Toggled; + public event EventHandler Toggled; + + /// + /// Called when the property changes. Invokes the event. + /// + public virtual void OnToggled (bool previousChecked) { + Toggled?.Invoke (this, previousChecked); + } /// /// Initializes a new instance of based on the given text, uses Computed layout and sets the height and width. @@ -122,11 +129,9 @@ namespace Terminal.Gui { public override bool ProcessKey (KeyEvent kb) { if (kb.KeyValue == ' ') { + var previousChecked = Checked; Checked = !Checked; - - if (Toggled != null) - Toggled (this, EventArgs.Empty); - + OnToggled (previousChecked); SetNeedsDisplay (); return true; } @@ -140,11 +145,11 @@ namespace Terminal.Gui { return false; SuperView.SetFocus (this); + var previousChecked = Checked; Checked = !Checked; + OnToggled (previousChecked); SetNeedsDisplay (); - if (Toggled != null) - Toggled (this, EventArgs.Empty); return true; } } diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 2e9c8e74d..bd63f2240 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -6,9 +6,7 @@ // // // TODO: -// - Mouse handling in scrollbarview // - focus in scrollview -// - keyboard handling in scrollview to scroll // - focus handling in scrollview to auto scroll to focused view // - Raise events // - Perhaps allow an option to not display the scrollbar arrow indicators? @@ -31,13 +29,26 @@ namespace Terminal.Gui { /// /// public class ScrollBarView : View { - bool vertical; - int size, position; + bool vertical = false; + int size = 0, position = 0; /// - /// The size that this scrollbar represents + /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. + /// + public bool IsVertical { + get => vertical; + set { + vertical = value; + SetNeedsDisplay (); + } + } + + /// + /// The size of content the scrollbar represents. /// /// The size. + /// The is typically the size of the virtual content. E.g. when a Scrollbar is + /// part of a the Size is set to the appropriate dimension of . public int Size { get => size; set { @@ -52,7 +63,7 @@ namespace Terminal.Gui { public event Action ChangedPosition; /// - /// The position to show the scrollbar at. + /// The position, relative to , to set the scrollbar at. /// /// The position. public int Position { @@ -70,13 +81,40 @@ namespace Terminal.Gui { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class using layout. /// /// Frame for the scrollbar. + public ScrollBarView (Rect rect) : this (rect, 0, 0, false) { } + + /// + /// Initializes a new instance of the class using layout. + /// + /// Frame for the scrollbar. + /// The size that this scrollbar represents. Sets the property. + /// The position within this scrollbar. Sets the property. + /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. Sets the property. + public ScrollBarView (Rect rect, int size, int position, bool isVertical) : base (rect) + { + Init (size, position, isVertical); + } + + /// + /// Initializes a new instance of the class using layout. + /// + public ScrollBarView () : this (0, 0, false) { } + + /// + /// Initializes a new instance of the class using layout. + /// /// The size that this scrollbar represents. /// The position within this scrollbar. /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. - public ScrollBarView (Rect rect, int size, int position, bool isVertical) : base (rect) + public ScrollBarView (int size, int position, bool isVertical) : base () + { + Init (size, position, isVertical); + } + + void Init (int size, int position, bool isVertical) { vertical = isVertical; this.position = position; @@ -84,11 +122,8 @@ namespace Terminal.Gui { WantContinuousButtonPressed = true; } - /// - /// Redraw the scrollbar - /// - /// Region to be redrawn. - public override void Redraw(Rect region) + /// + public override void Redraw (Rect region) { if (ColorScheme == null) return; @@ -113,7 +148,7 @@ namespace Terminal.Gui { special = Driver.Stipple; else special = Driver.Diamond; - Driver.AddRune(special); + Driver.AddRune (special); } } else { bh -= 2; @@ -125,7 +160,7 @@ namespace Terminal.Gui { Move (col, Bounds.Height - 1); Driver.AddRune ('v'); for (int y = 0; y < bh; y++) { - Move (col, y+1); + Move (col, y + 1); if (y < by1 - 1 || y > by2) special = Driver.Stipple; else { @@ -195,7 +230,7 @@ namespace Terminal.Gui { } /// - public override bool MouseEvent(MouseEvent me) + public override bool MouseEvent (MouseEvent me) { if (me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked && !me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) @@ -239,39 +274,66 @@ namespace Terminal.Gui { } /// - /// Scrollviews are views that present a window into a virtual space where children views are added. Similar to the iOS UIScrollView. + /// Scrollviews are views that present a window into a virtual space where subviews are added. Similar to the iOS UIScrollView. /// /// /// - /// The subviews that are added to this scrollview are offset by the - /// ContentOffset property. The view itself is a window into the - /// space represented by the ContentSize. + /// The subviews that are added to this are offset by the + /// property. The view itself is a window into the + /// space represented by the . /// /// - /// + /// Use the /// /// public class ScrollView : View { - View contentView; + View contentView = null; ScrollBarView vertical, horizontal; /// - /// Constructs a ScrollView + /// Initializes a new instance of the class using positioning. /// /// public ScrollView (Rect frame) : base (frame) + { + Init (frame); + } + + + /// + /// Initializes a new instance of the class using positioning. + /// + public ScrollView () : base () + { + Init (new Rect (0, 0, 0, 0)); + } + + void Init (Rect frame) { contentView = new View (frame); - vertical = new ScrollBarView (new Rect (frame.Width - 1, 0, 1, frame.Height), frame.Height, 0, isVertical: true); + vertical = new ScrollBarView (1, 0, isVertical: true) { + X = Pos.AnchorEnd (1), + Y = 0, + Width = 1, + Height = Dim.Fill (showHorizontalScrollIndicator ? 1 : 0) + }; vertical.ChangedPosition += delegate { ContentOffset = new Point (ContentOffset.X, vertical.Position); }; - horizontal = new ScrollBarView (new Rect (0, frame.Height-1, frame.Width-1, 1), frame.Width-1, 0, isVertical: false); + horizontal = new ScrollBarView (1, 0, isVertical: false) { + X = 0, + Y = Pos.AnchorEnd (1), + Width = Dim.Fill (showVerticalScrollIndicator ? 1 : 0), + Height = 1 + }; horizontal.ChangedPosition += delegate { ContentOffset = new Point (horizontal.Position, ContentOffset.Y); }; base.Add (contentView); CanFocus = true; + + MouseEnter += View_MouseEnter; + MouseLeave += View_MouseLeave; } Size contentSize; @@ -305,7 +367,7 @@ namespace Terminal.Gui { return contentOffset; } set { - contentOffset = new Point (-Math.Abs (value.X), -Math.Abs(value.Y)); + contentOffset = new Point (-Math.Abs (value.X), -Math.Abs (value.Y)); contentView.Frame = new Rect (contentOffset, contentSize); vertical.Position = Math.Max (0, -contentOffset.Y); horizontal.Position = Math.Max (0, -contentOffset.X); @@ -322,12 +384,9 @@ namespace Terminal.Gui { if (!IsOverridden (view)) { view.MouseEnter += View_MouseEnter; view.MouseLeave += View_MouseLeave; - vertical.MouseEnter += View_MouseEnter; - vertical.MouseLeave += View_MouseLeave; - horizontal.MouseEnter += View_MouseEnter; - horizontal.MouseLeave += View_MouseLeave; } contentView.Add (view); + SetNeedsLayout (); } void View_MouseLeave (object sender, MouseEventEventArgs e) @@ -359,11 +418,17 @@ namespace Terminal.Gui { return; showHorizontalScrollIndicator = value; - SetNeedsDisplay (); - if (value) + SetNeedsLayout (); + if (value) { base.Add (horizontal); - else + horizontal.MouseEnter += View_MouseEnter; + horizontal.MouseLeave += View_MouseLeave; + } else { Remove (horizontal); + horizontal.MouseEnter -= View_MouseEnter; + horizontal.MouseLeave -= View_MouseLeave; + } + vertical.Height = Dim.Fill (showHorizontalScrollIndicator ? 1 : 0); } } @@ -372,9 +437,9 @@ namespace Terminal.Gui { /// /// /// - public override void RemoveAll() + public override void RemoveAll () { - contentView.RemoveAll(); + contentView.RemoveAll (); } /// @@ -388,31 +453,46 @@ namespace Terminal.Gui { return; showVerticalScrollIndicator = value; - SetNeedsDisplay (); - if (value) + SetNeedsLayout (); + if (value) { base.Add (vertical); - else + vertical.MouseEnter += View_MouseEnter; + vertical.MouseLeave += View_MouseLeave; + } else { Remove (vertical); + vertical.MouseEnter -= View_MouseEnter; + vertical.MouseLeave -= View_MouseLeave; + } + horizontal.Width = Dim.Fill (showVerticalScrollIndicator ? 1 : 0); } } - /// - /// This event is raised when the contents have scrolled - /// - //public event Action Scrolled; - public override void Redraw(Rect region) + /// + public override void Redraw (Rect region) { - SetViewsNeedsDisplay (); Driver.SetAttribute (ColorScheme.Normal); + SetViewsNeedsDisplay (); Clear (); var savedClip = ClipToBounds (); + OnDrawContent (new Rect (ContentOffset, + new Size (Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0), + Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0)))); contentView.Redraw (contentView.Bounds); Driver.Clip = savedClip; - vertical.Redraw (vertical.Bounds); - horizontal.Redraw (horizontal.Bounds); - Driver.Clip = savedClip; + if (ShowVerticalScrollIndicator) { + vertical.Redraw (vertical.Bounds); + } + + if (ShowHorizontalScrollIndicator) { + horizontal.Redraw (horizontal.Bounds); + } + + // Fill in the bottom left corner + if (ShowVerticalScrollIndicator && ShowHorizontalScrollIndicator) { + AddRune (Bounds.Width - 1, Bounds.Height - 1, ' '); + } Driver.SetAttribute (ColorScheme.Normal); } @@ -424,7 +504,7 @@ namespace Terminal.Gui { } /// - public override void PositionCursor() + public override void PositionCursor () { if (InternalSubviews.Count == 0) Driver.Move (0, 0); @@ -490,7 +570,7 @@ namespace Terminal.Gui { } /// - public override bool ProcessKey(KeyEvent kb) + public override bool ProcessKey (KeyEvent kb) { if (base.ProcessKey (kb)) return true; diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs index 3413d418a..ebb69639c 100644 --- a/UICatalog/Scenarios/Buttons.cs +++ b/UICatalog/Scenarios/Buttons.cs @@ -118,6 +118,16 @@ namespace UICatalog { }; Win.Add (moveBtn); + // Demonstrates how changing the View.Frame property can SIZE Views (#583) + y += 2; + var sizeBtn = new Button (10, y, "Size This Button via Frame") { + ColorScheme = Colors.Error, + }; + moveBtn.Clicked = () => { + sizeBtn.Frame = new Rect (sizeBtn.Frame.X, sizeBtn.Frame.Y, sizeBtn.Frame.Width + 5, sizeBtn.Frame.Height); + }; + Win.Add (sizeBtn); + // Demo changing hotkey ustring MoveHotkey (ustring txt) { diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs new file mode 100644 index 000000000..96288504d --- /dev/null +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -0,0 +1,123 @@ +using NStack; +using System.Collections.Generic; +using System.Text; +using Terminal.Gui; + +namespace UICatalog { + /// + /// This Scenario demonstrates building a custom control (a class deriving from View) that: + /// - Provides a simple "Character Map" application (like Windows' charmap.exe). + /// - Helps test unicode character rendering in Terminal.Gui + /// - Illustrates how to use ScrollView to do infinite scrolling + /// + [ScenarioMetadata (Name: "Character Map", Description: "Illustrates a custom control and Unicode")] + [ScenarioCategory ("Text")] + [ScenarioCategory ("Controls")] + class CharacterMap : Scenario { + public override void Setup () + { + var charMap = new CharMap () { X = 0, Y = 0, Width = CharMap.RowWidth + 2, Height = Dim.Fill(), Start = 0x2500, + ColorScheme = Colors.Dialog}; + + Win.Add (charMap); + + Button CreateBlock(Window win, ustring title, int start, int end, View align) + { + var button = new Button ($"{title} (U+{start:x5}-{end:x5})") { + X = Pos.X (align), + Y = Pos.Bottom (align), + Clicked = () => { + charMap.Start = start; + }, + }; + win.Add (button); + return button; + }; + + var label = new Label ("Unicode Blocks:") { X = Pos.Right (charMap) + 2, Y = Pos.Y (charMap) }; + Win.Add (label); + var button = CreateBlock (Win, "Currency Symbols", 0x20A0, 0x20CF, label); + button = CreateBlock (Win, "Letterlike Symbols", 0x2100, 0x214F, button); + button = CreateBlock (Win, "Arrows", 0x2190, 0x21ff, button); + button = CreateBlock (Win, "Mathematical symbols", 0x2200, 0x22ff, button); + button = CreateBlock (Win, "Miscellaneous Technical", 0x2300, 0x23ff, button); + button = CreateBlock (Win, "Box Drawing & Geometric Shapes", 0x2500, 0x25ff, button); + button = CreateBlock (Win, "Miscellaneous Symbols", 0x2600, 0x26ff, button); + button = CreateBlock (Win, "Dingbats", 0x2700, 0x27ff, button); + button = CreateBlock (Win, "Braille", 0x2800, 0x28ff, button); + button = CreateBlock (Win, "Miscellaneous Symbols and Arrows", 0x2b00, 0x2bff, button); + button = CreateBlock (Win, "Alphabetic Presentation Forms", 0xFB00, 0xFb4f, button); + button = CreateBlock (Win, "Cuneiform Numbers and Punctuation[1", 0x12400, 0x1240f, button); + button = CreateBlock (Win, "Chess Symbols", 0x1FA00, 0x1FA0f, button); + button = CreateBlock (Win, "End", CharMap.MaxCodePointVal - 16, CharMap.MaxCodePointVal, button); + } + } + + class CharMap : ScrollView { + + /// + /// Specifies the starting offset for the character map. The default is 0x2500 + /// which is the Box Drawing characters. + /// + public int Start { + get => _start; + set { + _start = value; + ContentOffset = new Point (0, _start / 16); + + SetNeedsDisplay (); + } + } + int _start = 0x2500; + + public static int MaxCodePointVal => 0xE0FFF; + + // Row Header + space + (space + char + space) + public static int RowHeaderWidth => $"U+{MaxCodePointVal:x5}".Length; + public static int RowWidth => RowHeaderWidth + 1 + (" c ".Length * 16); + + public CharMap () + { + ContentSize = new Size (CharMap.RowWidth, MaxCodePointVal / 16); + ShowVerticalScrollIndicator = true; + ShowHorizontalScrollIndicator = false; + LayoutComplete += (sender, args) => { + if (Bounds.Width <= RowWidth) { + ShowHorizontalScrollIndicator = true; + } else { + ShowHorizontalScrollIndicator = false; + } + }; + + DrawContent += CharMap_DrawContent; + } + +#if true + private void CharMap_DrawContent (object sender, Rect viewport) + { + for (int header = 0; header < 16; header++) { + Move (viewport.X + RowHeaderWidth + 1 + (header * 3), 0); + Driver.AddStr ($" {header:x} "); + } + for (int row = 0; row < viewport.Height - 1; row++) { + int val = (-viewport.Y + row) * 16; + if (val < MaxCodePointVal) { + var rowLabel = $"U+{val / 16:x4}x"; + Move (0, row + 1); + Driver.AddStr (rowLabel); + for (int col = 0; col < 16; col++) { + Move (viewport.X + RowHeaderWidth + 1 + (col * 3), 0 + row + 1); + Driver.AddStr ($" {(char)((-viewport.Y + row) * 16 + col)} "); + } + } + } + } +#else + public override void OnDrawContent (Rect viewport) + { + CharMap_DrawContent(this, viewport); + base.OnDrawContent (viewport); + } +#endif + } +} diff --git a/UICatalog/Scenarios/Scrolling.cs b/UICatalog/Scenarios/Scrolling.cs index da989d02f..27425d61d 100644 --- a/UICatalog/Scenarios/Scrolling.cs +++ b/UICatalog/Scenarios/Scrolling.cs @@ -95,12 +95,13 @@ namespace UICatalog { Win.Add (label); // BUGBUG: ScrollView only supports Absolute Positioning (#72) - var scrollView = new ScrollView (new Rect (2, 2, 50, 20)); - scrollView.ColorScheme = Colors.TopLevel; - scrollView.ContentSize = new Size (200, 100); - //ContentOffset = new Point (0, 0), - scrollView.ShowVerticalScrollIndicator = true; - scrollView.ShowHorizontalScrollIndicator = true; + var scrollView = new ScrollView (new Rect (2, 2, 50, 20)) { + ColorScheme = Colors.TopLevel, + ContentSize = new Size (200, 100), + //ContentOffset = new Point (0, 0), + ShowVerticalScrollIndicator = true, + ShowHorizontalScrollIndicator = true, + }; const string rule = "|123456789"; var horizontalRuler = new Label ("") { @@ -177,13 +178,31 @@ namespace UICatalog { }; scrollView.Add (anchorButton); + var hCheckBox = new CheckBox ("Horizontal Scrollbar", scrollView.ShowHorizontalScrollIndicator) { + X = Pos.X(scrollView), + Y = Pos.Bottom(scrollView) + 1, + }; + hCheckBox.Toggled += (sender, previousChecked) => { + scrollView.ShowHorizontalScrollIndicator = ((CheckBox)sender).Checked; + }; + Win.Add (hCheckBox); + + var vCheckBox = new CheckBox ("Vertical Scrollbar", scrollView.ShowVerticalScrollIndicator) { + X = Pos.Right (hCheckBox) + 3, + Y = Pos.Bottom (scrollView) + 1, + }; + vCheckBox.Toggled += (sender, previousChecked) => { + scrollView.ShowVerticalScrollIndicator = ((CheckBox)sender).Checked; + }; + Win.Add (vCheckBox); + var scrollView2 = new ScrollView (new Rect (55, 2, 20, 8)) { ContentSize = new Size (20, 50), //ContentOffset = new Point (0, 0), ShowVerticalScrollIndicator = true, ShowHorizontalScrollIndicator = true }; - scrollView2.Add (new Filler(new Rect (0, 0, 60, 40))); + scrollView2.Add (new Filler (new Rect (0, 0, 60, 40))); // This is just to debug the visuals of the scrollview when small var scrollView3 = new ScrollView (new Rect (55, 15, 3, 3)) { @@ -205,7 +224,7 @@ namespace UICatalog { var progress = new ProgressBar (); progress.X = 5; - progress.Y = Pos.AnchorEnd (3); + progress.Y = Pos.AnchorEnd (2); progress.Width = 50; bool timer (MainLoop caller) { diff --git a/UICatalog/Scenarios/TextAlignment.cs b/UICatalog/Scenarios/TextAlignments.cs similarity index 97% rename from UICatalog/Scenarios/TextAlignment.cs rename to UICatalog/Scenarios/TextAlignments.cs index 1f4beb019..219d10e0f 100644 --- a/UICatalog/Scenarios/TextAlignment.cs +++ b/UICatalog/Scenarios/TextAlignments.cs @@ -6,7 +6,7 @@ using Terminal.Gui; namespace UICatalog { [ScenarioMetadata (Name: "Text Alignment", Description: "Demonstrates text alignment")] [ScenarioCategory ("Text")] - class TextAlignment : Scenario { + class TextAlignments : Scenario { public override void Setup () { int i = 1; diff --git a/UICatalog/Scenarios/Unicode.cs b/UICatalog/Scenarios/Unicode.cs index bf7acee93..197f5bc32 100644 --- a/UICatalog/Scenarios/Unicode.cs +++ b/UICatalog/Scenarios/Unicode.cs @@ -10,8 +10,6 @@ namespace UICatalog { class UnicodeInMenu : Scenario { public override void Setup () { - const int margin = 1; - var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_Файл", new MenuItem [] { new MenuItem ("_Создать", "Creates new file", null), @@ -27,14 +25,9 @@ namespace UICatalog { }); Top.Add (menu); - var label = new Label ("Button:") { X = margin, Y = margin }; + var label = new Label ("Button:") { X = 0, Y = 1 }; Win.Add (label); - var button = new Button (" ~  s  gui.cs   master ↑10") { X = 15, Y = Pos.Y (label) }; - Win.Add (button); - - label = new Label ("Button:") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 }; - Win.Add (label); - var button2 = new Button ("Со_хранить") { X = 15, Y = Pos.Y (label), Width = Dim.Percent (50) }; + var button2 = new Button ("Со_хранить") { X = 15, Y = Pos.Y (label), Width = Dim.Percent (50), }; Win.Add (button2); label = new Label ("CheckBox:") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 }; From ca036453b3f1601bf6986e83a7bc1f5db9001862 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 3 Jun 2020 19:21:29 +0100 Subject: [PATCH 2/2] Added some features to TextView like mouse welling and Ctrl+End/Home to navigate to the end and begin of the text. --- Terminal.Gui/Views/TextView.cs | 103 +++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 36 deletions(-) diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index a6d782fe4..7e665ecdc 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -771,32 +771,12 @@ namespace Terminal.Gui { case Key.ControlN: case Key.CursorDown: - if (currentRow + 1 < model.Count) { - if (columnTrack == -1) - columnTrack = currentColumn; - currentRow++; - if (currentRow >= topRow + Frame.Height) { - topRow++; - SetNeedsDisplay (); - } - TrackColumn (); - PositionCursor (); - } + MoveDown (); break; case Key.ControlP: case Key.CursorUp: - if (currentRow > 0) { - if (columnTrack == -1) - columnTrack = currentColumn; - currentRow--; - if (currentRow < topRow) { - topRow--; - SetNeedsDisplay (); - } - TrackColumn (); - PositionCursor (); - } + MoveUp (); break; case Key.ControlF: @@ -1024,6 +1004,18 @@ namespace Terminal.Gui { SetNeedsDisplay (new Rect (0, currentRow - topRow, 2, Frame.Height)); break; + case Key.CtrlMask | Key.End: + currentRow = model.Count; + TrackColumn (); + PositionCursor (); + break; + + case Key.CtrlMask | Key.Home: + currentRow = 0; + TrackColumn (); + PositionCursor (); + break; + default: // Ignore control characters and other special keys if (kb.Key < Key.Space || kb.Key > Key.CharMask) @@ -1043,6 +1035,36 @@ namespace Terminal.Gui { return true; } + private void MoveUp () + { + if (currentRow > 0) { + if (columnTrack == -1) + columnTrack = currentColumn; + currentRow--; + if (currentRow < topRow) { + topRow--; + SetNeedsDisplay (); + } + TrackColumn (); + PositionCursor (); + } + } + + private void MoveDown () + { + if (currentRow + 1 < model.Count) { + if (columnTrack == -1) + columnTrack = currentColumn; + currentRow++; + if (currentRow >= topRow + Frame.Height) { + topRow++; + SetNeedsDisplay (); + } + TrackColumn (); + PositionCursor (); + } + } + IEnumerable<(int col, int row, Rune rune)> ForwardIterator (int col, int row) { if (col < 0 || row < 0) @@ -1173,28 +1195,37 @@ namespace Terminal.Gui { /// public override bool MouseEvent (MouseEvent ev) { - if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked)) { + if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked) && + !ev.Flags.HasFlag (MouseFlags.WheeledDown) && !ev.Flags.HasFlag (MouseFlags.WheeledUp)) { return false; } if (!HasFocus) SuperView.SetFocus (this); - - if (model.Count > 0) { - var maxCursorPositionableLine = (model.Count - 1) - topRow; - if (ev.Y > maxCursorPositionableLine) { - currentRow = maxCursorPositionableLine; - } else { - currentRow = ev.Y + topRow; + if (ev.Flags == MouseFlags.Button1Clicked) { + if (model.Count > 0) { + var maxCursorPositionableLine = (model.Count - 1) - topRow; + if (ev.Y > maxCursorPositionableLine) { + currentRow = maxCursorPositionableLine; + } else { + currentRow = ev.Y + topRow; + } + var r = GetCurrentLine (); + if (ev.X - leftColumn >= r.Count) + currentColumn = r.Count - leftColumn; + else + currentColumn = ev.X - leftColumn; } - var r = GetCurrentLine (); - if (ev.X - leftColumn >= r.Count) - currentColumn = r.Count - leftColumn; - else - currentColumn = ev.X - leftColumn; + PositionCursor (); + } else if (ev.Flags == MouseFlags.WheeledDown) { + lastWasKill = false; + MoveDown (); + } else if (ev.Flags == MouseFlags.WheeledUp) { + lastWasKill = false; + MoveUp (); } - PositionCursor (); + return true; } }