diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs
index 0e7bd5bb6..b4ccd5f0d 100644
--- a/Terminal.Gui/Core/View.cs
+++ b/Terminal.Gui/Core/View.cs
@@ -741,7 +741,7 @@ namespace Terminal.Gui {
public void SetChildNeedsDisplay ()
{
ChildNeedsDisplay = true;
- if (container != null)
+ if (container != null && !container.ChildNeedsDisplay)
container.SetChildNeedsDisplay ();
}
@@ -1342,6 +1342,7 @@ namespace Terminal.Gui {
// 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.OnDrawContent (view.Bounds);
view.Redraw (view.Bounds);
}
}
diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs
index 84404d71e..41b1ae856 100644
--- a/Terminal.Gui/Views/ListView.cs
+++ b/Terminal.Gui/Views/ListView.cs
@@ -298,11 +298,6 @@ namespace Terminal.Gui {
Driver.SetAttribute (current);
Move (0, 0);
var f = Frame;
- if (selected < top) {
- top = selected;
- } else if (selected >= top + f.Height) {
- top = selected;
- }
var item = top;
bool focused = HasFocus;
int col = allowsMarking ? 2 : 0;
@@ -477,8 +472,11 @@ namespace Terminal.Gui {
} else if (selected + 1 < source.Count) { //can move by down by one.
selected++;
- if (selected >= top + Frame.Height)
+ if (selected >= top + Frame.Height) {
top++;
+ } else if (selected < top) {
+ top = selected;
+ }
OnSelectedChanged ();
SetNeedsDisplay ();
} else if (selected == 0) {
@@ -511,8 +509,11 @@ namespace Terminal.Gui {
if (selected > Source.Count) {
selected = Source.Count - 1;
}
- if (selected < top)
+ if (selected < top) {
top = selected;
+ } else if (selected > top + Frame.Height) {
+ top = Math.Max (selected - Frame.Height + 1, 0);
+ }
OnSelectedChanged ();
SetNeedsDisplay ();
}
@@ -551,6 +552,26 @@ namespace Terminal.Gui {
return true;
}
+ ///
+ /// Scrolls the view down.
+ ///
+ /// Number of lines to scroll down.
+ public virtual void ScrollDown (int lines)
+ {
+ top = Math.Min (top + lines, source.Count - 1);
+ SetNeedsDisplay ();
+ }
+
+ ///
+ /// Scrolls the view up.
+ ///
+ /// Number of lines to scroll up.
+ public virtual void ScrollUp (int lines)
+ {
+ top = Math.Max (top - lines, 0);
+ SetNeedsDisplay ();
+ }
+
int lastSelectedItem = -1;
private bool allowsMultipleSelection = true;
@@ -630,10 +651,10 @@ namespace Terminal.Gui {
}
if (me.Flags == MouseFlags.WheeledDown) {
- MoveDown ();
+ ScrollDown (1);
return true;
} else if (me.Flags == MouseFlags.WheeledUp) {
- MoveUp ();
+ ScrollUp (1);
return true;
}
@@ -655,6 +676,8 @@ namespace Terminal.Gui {
return true;
}
+
+
}
///
diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs
new file mode 100644
index 000000000..985d31ef4
--- /dev/null
+++ b/Terminal.Gui/Views/ScrollBarView.cs
@@ -0,0 +1,538 @@
+//
+// ScrollBarView.cs: ScrollBarView view.
+//
+// Authors:
+// Miguel de Icaza (miguel@gnome.org)
+//
+
+using System;
+
+namespace Terminal.Gui {
+ ///
+ /// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical
+ ///
+ ///
+ ///
+ /// The scrollbar is drawn to be a representation of the Size, assuming that the
+ /// scroll position is set at Position.
+ ///
+ ///
+ /// If the region to display the scrollbar is larger than three characters,
+ /// arrow indicators are drawn.
+ ///
+ ///
+ public class ScrollBarView : View {
+ bool vertical;
+ int size, position, contentOffset;
+ bool showScrollIndicator;
+ bool keepContentAlwaysInViewport = true;
+ bool autoHideScrollBars = true;
+ Dim originalHostWidth, originalHostHeight;
+ bool hosted;
+ bool showBothScrollIndicator => OtherScrollBarView != null && OtherScrollBarView.showScrollIndicator;
+
+ ///
+ /// 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 (int size, int position, bool isVertical) : base ()
+ {
+ Init (size, position, isVertical);
+ }
+
+ ///
+ /// Initializes a new instance of the class using layout.
+ ///
+ public ScrollBarView (View host, bool isVertical) : this (0, 0, isVertical)
+ {
+ hosted = true;
+ originalHostWidth = host.Width;
+ originalHostHeight = host.Height;
+ X = isVertical ? Pos.Right(host) : Pos.Left (host);
+ Y = isVertical ? Pos.Top (host) : Pos.Bottom (host);
+ Host = host;
+ Host.SuperView.Add (this);
+ ShowScrollIndicator = true;
+ AutoHideScrollBars = true;
+ }
+
+ void Init (int size, int position, bool isVertical)
+ {
+ vertical = isVertical;
+ this.position = position;
+ this.size = size;
+ WantContinuousButtonPressed = true;
+ }
+
+ ///
+ /// 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 {
+ size = value;
+ ShowHideScrollBars ();
+ SetNeedsDisplay ();
+ }
+ }
+
+ ///
+ /// Represents the top left/top corner coordinate that is displayed by the scrollbar.
+ ///
+ /// The content offset.
+ public int ContentOffset {
+ get {
+ return contentOffset;
+ }
+ set {
+ var co = -Math.Abs (value);
+ if (contentOffset != co) {
+ if (CanScroll (contentOffset - co, out int max, vertical)) {
+ if (max == contentOffset - co) {
+ contentOffset = co;
+ } else {
+ contentOffset = co + max;
+ }
+ }
+ Position = Math.Max (0, -contentOffset);
+ OnChangedPosition ();
+ SetNeedsDisplay ();
+ }
+ }
+ }
+
+ ///
+ /// This event is raised when the position on the scrollbar has changed.
+ ///
+ public event Action ChangedPosition;
+
+ ///
+ /// The position, relative to , to set the scrollbar at.
+ ///
+ /// The position.
+ public int Position {
+ get => position;
+ set {
+ position = value;
+ SetNeedsDisplay ();
+ }
+ }
+
+ ///
+ /// Get or sets the view that host this
+ ///
+ public View Host { get; internal set; }
+
+ ///
+ /// Represent a vertical or horizontal ScrollBarView other than this.
+ ///
+ public ScrollBarView OtherScrollBarView { get; set; }
+
+ ///
+ /// Gets or sets the visibility for the vertical or horizontal scroll indicator.
+ ///
+ /// true if show vertical or horizontal scroll indicator; otherwise, false.
+ public bool ShowScrollIndicator {
+ get => showScrollIndicator;
+ set {
+ if (value == showScrollIndicator) {
+ return;
+ }
+
+ showScrollIndicator = value;
+ SetNeedsLayout ();
+ if (value) {
+ Visible = true;
+ } else {
+ Visible = false;
+ }
+ Width = vertical ? 1 : Dim.Width (Host);
+ Height = vertical ? Dim.Height (Host) : 1;
+ if (vertical) {
+ Host.Width = showScrollIndicator ? originalHostWidth - 1 : originalHostWidth;
+ } else {
+ Host.Height = showScrollIndicator ? originalHostHeight - 1 : originalHostHeight;
+ }
+ }
+ }
+
+ ///
+ /// Get or sets if the view-port is kept always visible in the area of this
+ ///
+ public bool KeepContentAlwaysInViewport {
+ get { return keepContentAlwaysInViewport; }
+ set {
+ if (keepContentAlwaysInViewport != value) {
+ keepContentAlwaysInViewport = value;
+ int co = 0;
+ if (value && !vertical && -contentOffset + Host.Bounds.Width > size) {
+ co = size - Host.Bounds.Width + (showBothScrollIndicator ? 1 : 0);
+ }
+ if (value && vertical && -contentOffset + Host.Bounds.Height > size) {
+ co = size - Host.Bounds.Height + (showBothScrollIndicator ? 1 : 0);
+ }
+ if (co != 0) {
+ ContentOffset = co;
+ }
+ if (OtherScrollBarView != null && OtherScrollBarView.keepContentAlwaysInViewport != value) {
+ OtherScrollBarView.KeepContentAlwaysInViewport = value;
+ }
+ }
+ }
+ }
+
+ ///
+ /// If true the vertical/horizontal scroll bars won't be showed if it's not needed.
+ ///
+ public bool AutoHideScrollBars {
+ get => autoHideScrollBars;
+ set {
+ if (autoHideScrollBars != value) {
+ autoHideScrollBars = value;
+ SetNeedsDisplay ();
+ }
+ }
+ }
+
+ void SetPosition (int newPos)
+ {
+ Position = newPos;
+ ContentOffset = Position;
+ OnChangedPosition ();
+ }
+
+ ///
+ /// Virtual method to invoke the action event.
+ ///
+ public virtual void OnChangedPosition ()
+ {
+ ChangedPosition?.Invoke ();
+ }
+
+ internal bool pending;
+
+ void ShowHideScrollBars ()
+ {
+ if (!hosted || !autoHideScrollBars) {
+ return;
+ }
+
+ if (vertical) {
+ if (Host.Bounds.Height == 0 || Host.Bounds.Height > size) {
+ if (showScrollIndicator) {
+ ShowScrollIndicator = false;
+ }
+ } else if (Host.Bounds.Height > 0 && Host.Bounds.Height == size) {
+ pending = true;
+ } else if (!showScrollIndicator) {
+ ShowScrollIndicator = true;
+ }
+ } else {
+ if (Host.Bounds.Width == 0 || Host.Bounds.Width > size) {
+ if (showScrollIndicator) {
+ ShowScrollIndicator = false;
+ }
+ } else if (Host.Bounds.Width > 0 && Host.Bounds.Width == size && OtherScrollBarView.pending) {
+ if (showScrollIndicator) {
+ ShowScrollIndicator = false;
+ }
+ if (showBothScrollIndicator) {
+ OtherScrollBarView.showScrollIndicator = false;
+ }
+ } else {
+ if (OtherScrollBarView.pending) {
+ if (!showBothScrollIndicator) {
+ OtherScrollBarView.showScrollIndicator = true;
+ }
+ }
+ if (!showScrollIndicator) {
+ ShowScrollIndicator = true;
+ }
+ }
+ }
+ }
+
+ int posTopTee;
+ int posLeftTee;
+ int posBottomTee;
+ int posRightTee;
+
+ ///
+ public override void Redraw (Rect region)
+ {
+ if (ColorScheme == null || Size == 0) {
+ return;
+ }
+
+ Driver.SetAttribute (ColorScheme.Normal);
+
+ if ((vertical && Bounds.Height == 0) || (!vertical && Bounds.Width == 0)) {
+ return;
+ }
+
+ if (vertical) {
+ if (region.Right < Bounds.Width - 1) {
+ return;
+ }
+
+ var col = Bounds.Width - 1;
+ var bh = Bounds.Height;
+ Rune special;
+
+ if (bh < 4) {
+ var by1 = position * bh / Size;
+ var by2 = (position + bh) * bh / Size;
+
+ Move (col, 0);
+ if (Bounds.Height == 1) {
+ Driver.AddRune (Driver.Diamond);
+ } else {
+ Driver.AddRune (Driver.UpArrow);
+ }
+ if (Bounds.Height == 3) {
+ Move (col, 1);
+ Driver.AddRune (Driver.Diamond);
+ }
+ if (Bounds.Height > 1) {
+ Move (col, Bounds.Height - 1);
+ Driver.AddRune (Driver.DownArrow);
+ }
+ } else {
+ bh -= 2;
+ var by1 = position * bh / Size;
+ var by2 = KeepContentAlwaysInViewport ? Math.Min (((position + bh) * bh / Size) + 1, bh - 1) : (position + bh) * bh / Size;
+ if (KeepContentAlwaysInViewport && by1 == by2) {
+ by1 = Math.Max (by1 - 1, 0);
+ }
+
+ Move (col, 0);
+ Driver.AddRune (Driver.UpArrow);
+ Move (col, Bounds.Height - 1);
+ Driver.AddRune (Driver.DownArrow);
+
+ bool hasTopTee = false;
+ bool hasDiamond = false;
+ bool hasBottomTee = false;
+ for (int y = 0; y < bh; y++) {
+ Move (col, y + 1);
+ if ((y < by1 || y > by2) && ((position > 0 && !hasTopTee) || (hasTopTee && hasBottomTee))) {
+ special = Driver.Stipple;
+ } else {
+ if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond) {
+ hasDiamond = true;
+ special = Driver.Diamond;
+ } else {
+ if (y == by1 && !hasTopTee) {
+ hasTopTee = true;
+ posTopTee = y;
+ special = Driver.TopTee;
+ } else if ((position == 0 && y == bh - 1 || y >= by2 || by2 == 0) && !hasBottomTee) {
+ hasBottomTee = true;
+ posBottomTee = y;
+ special = Driver.BottomTee;
+ } else {
+ special = Driver.VLine;
+ }
+ }
+ }
+ Driver.AddRune (special);
+ }
+ if (!hasTopTee) {
+ Move (col, Bounds.Height - 2);
+ Driver.AddRune (Driver.TopTee);
+ }
+ }
+ } else {
+ if (region.Bottom < Bounds.Height - 1) {
+ return;
+ }
+
+ var row = Bounds.Height - 1;
+ var bw = Bounds.Width;
+ Rune special;
+
+ if (bw < 4) {
+ var bx1 = position * bw / Size;
+ var bx2 = (position + bw) * bw / Size;
+
+ Move (0, row);
+ Driver.AddRune (Driver.LeftArrow);
+ Driver.AddRune (Driver.RightArrow);
+ } else {
+ bw -= 2;
+ var bx1 = position * bw / Size;
+ var bx2 = KeepContentAlwaysInViewport ? Math.Min (((position + bw) * bw / Size) + 1, bw - 1) : (position + bw) * bw / Size;
+ if (KeepContentAlwaysInViewport && bx1 == bx2) {
+ bx1 = Math.Max (bx1 - 1, 0);
+ }
+
+ Move (0, row);
+ Driver.AddRune (Driver.LeftArrow);
+
+ bool hasLeftTee = false;
+ bool hasDiamond = false;
+ bool hasRightTee = false;
+ for (int x = 0; x < bw; x++) {
+ if ((x < bx1 || x >= bx2 + 1) && ((position > 0 && !hasLeftTee) || (hasLeftTee && hasRightTee))) {
+ special = Driver.Stipple;
+ } else {
+ if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond) {
+ hasDiamond = true;
+ special = Driver.Diamond;
+ } else {
+ if (x == bx1 && !hasLeftTee) {
+ hasLeftTee = true;
+ posLeftTee = x;
+ special = Driver.LeftTee;
+ } else if ((position == 0 && x == bw - 1 || x >= bx2 || bx2 == 0) && !hasRightTee) {
+ hasRightTee = true;
+ posRightTee = x;
+ special = Driver.RightTee;
+ } else {
+ special = Driver.HLine;
+ }
+ }
+ }
+ Driver.AddRune (special);
+ }
+ if (!hasLeftTee) {
+ Move (Bounds.Width -2, row);
+ Driver.AddRune (Driver.LeftTee);
+ }
+
+ Driver.AddRune (Driver.RightArrow);
+ }
+ }
+ }
+
+ int lastLocation = -1;
+
+ ///
+ public override bool MouseEvent (MouseEvent me)
+ {
+ if (me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked &&
+ !me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
+ return false;
+ }
+
+ if (!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
+ lastLocation = -1;
+ }
+
+ int location = vertical ? me.Y : me.X;
+ int barsize = vertical ? Bounds.Height : Bounds.Width;
+ int posTopLeftTee = vertical ? posTopTee : posLeftTee;
+ int posBottomRightTee = vertical ? posBottomTee : posRightTee;
+
+ barsize -= 2;
+ var pos = Position;
+ if (location == 0) {
+ if (pos > 0) {
+ SetPosition (pos - 1);
+ }
+ } else if (location == barsize + 1) {
+ if (CanScroll (1, out _, vertical)) {
+ SetPosition (pos + 1);
+ }
+ } else if (location > 0 && location < barsize + 1) {
+ var b1 = pos * barsize / Size;
+ var b2 = KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size;
+ if (KeepContentAlwaysInViewport && b1 == b2) {
+ b1 = Math.Max (b1 - 1, 0);
+ }
+
+ if (location > b1 && location <= b2 + 1) {
+ if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1Clicked) {
+ if (location == 1) {
+ SetPosition (0);
+ } else if (location == barsize) {
+ CanScroll (Size - pos, out int nv, vertical);
+ if (nv > 0) {
+ SetPosition (Math.Min (pos + nv, Size));
+ }
+ }
+ } else if (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
+ var mb = (b2 - b1) / 2;
+ var ml = mb + b1 + (mb == 0 ? 1 : 0);
+ if ((location >= b1 && location <= ml) || (location < lastLocation && lastLocation > -1)) {
+ lastLocation = location;
+ var np = b1 * Size / barsize;
+ SetPosition (np);
+ } else if (location > lastLocation) {
+ var np = location * Size / barsize;
+ CanScroll (np - pos, out int nv, vertical);
+ if (nv > 0) {
+ SetPosition (pos + nv);
+ }
+ }
+ }
+ } else {
+ if (location >= b2 + 1 && location > posTopLeftTee && location > b1 && location > posBottomRightTee && posBottomRightTee > 0) {
+ CanScroll (location, out int nv, vertical);
+ if (nv > 0) {
+ SetPosition (Math.Min (pos + nv, Size));
+ }
+ } else if (location <= b1) {
+ SetPosition (Math.Max (pos - barsize - location, 0));
+ }
+ }
+ }
+
+ return true;
+ }
+
+ internal bool CanScroll (int n, out int max, bool isVertical = false)
+ {
+ var size = isVertical ?
+ (KeepContentAlwaysInViewport ? Host.Bounds.Height + (showBothScrollIndicator ? -2 : -1) : 0) :
+ (KeepContentAlwaysInViewport ? Host.Bounds.Width + (showBothScrollIndicator ? -2 : -1) : 0);
+ var cSize = -Size;
+ var cOffSet = contentOffset;
+ var newSize = Math.Max (cSize, cOffSet - n);
+ max = cSize < newSize - size ? n : -cSize + (cOffSet - size) - 1;
+ if (cSize < newSize - size) {
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs
index 6e441b16c..0a5b139bc 100644
--- a/Terminal.Gui/Views/ScrollView.cs
+++ b/Terminal.Gui/Views/ScrollView.cs
@@ -1,5 +1,5 @@
//
-// ScrollView.cs: ScrollView and ScrollBarView views.
+// ScrollView.cs: ScrollView view.
//
// Authors:
// Miguel de Icaza (miguel@gnome.org)
@@ -15,345 +15,6 @@ using System;
using System.Reflection;
namespace Terminal.Gui {
- ///
- /// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical
- ///
- ///
- ///
- /// The scrollbar is drawn to be a representation of the Size, assuming that the
- /// scroll position is set at Position.
- ///
- ///
- /// If the region to display the scrollbar is larger than three characters,
- /// arrow indicators are drawn.
- ///
- ///
- public class ScrollBarView : View {
- bool vertical = false;
- int size = 0, position = 0;
-
- ///
- /// 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 {
- size = value;
- SetNeedsDisplay ();
- }
- }
-
- ///
- /// This event is raised when the position on the scrollbar has changed.
- ///
- public event Action ChangedPosition;
-
- ///
- /// The position, relative to , to set the scrollbar at.
- ///
- /// The position.
- public int Position {
- get => position;
- set {
- position = value;
- SetNeedsDisplay ();
- }
- }
-
- ///
- /// Get or sets the view that host this
- ///
- public ScrollView Host { get; internal set; }
-
- void SetPosition (int newPos)
- {
- Position = newPos;
- ChangedPosition?.Invoke ();
- }
-
- ///
- /// 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 (int size, int position, bool isVertical) : base ()
- {
- Init (size, position, isVertical);
- }
-
- void Init (int size, int position, bool isVertical)
- {
- vertical = isVertical;
- this.position = position;
- this.size = size;
- WantContinuousButtonPressed = true;
- }
-
- int posTopTee;
- int posLeftTee;
- int posBottomTee;
- int posRightTee;
-
- ///
- public override void Redraw (Rect region)
- {
- if (ColorScheme == null || Size == 0)
- return;
-
- Driver.SetAttribute (ColorScheme.Normal);
-
- if (Bounds.Height == 0) {
- return;
- }
-
- if (vertical) {
- if (region.Right < Bounds.Width - 1)
- return;
-
- var col = Bounds.Width - 1;
- var bh = Bounds.Height;
- Rune special;
-
- if (bh < 4) {
- var by1 = position * bh / Size;
- var by2 = (position + bh) * bh / Size;
-
- Move (col, 0);
- if (Bounds.Height == 1) {
- Driver.AddRune (Driver.Diamond);
- } else {
- Driver.AddRune (Driver.UpArrow);
- }
- if (Bounds.Height == 3) {
- Move (col, 1);
- Driver.AddRune (Driver.Diamond);
- }
- if (Bounds.Height > 1) {
- Move (col, Bounds.Height - 1);
- Driver.AddRune (Driver.DownArrow);
- }
- } else {
- bh -= 2;
- var by1 = position * bh / Size;
- var by2 = Host.KeepContentAlwaysInViewport ? Math.Min (((position + bh) * bh / Size) + 1, bh - 1) : (position + bh) * bh / Size;
- if (Host.KeepContentAlwaysInViewport && by1 == by2) {
- by1 = Math.Max (by1 - 1, 0);
- }
-
- Move (col, 0);
- Driver.AddRune (Driver.UpArrow);
- Move (col, Bounds.Height - 1);
- Driver.AddRune (Driver.DownArrow);
-
- bool hasTopTee = false;
- bool hasDiamond = false;
- bool hasBottomTee = false;
- for (int y = 0; y < bh; y++) {
- Move (col, y + 1);
- if ((y < by1 || y > by2) && ((position > 0 && !hasTopTee) || (hasTopTee && hasBottomTee))) {
- special = Driver.Stipple;
- } else {
- if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond) {
- hasDiamond = true;
- special = Driver.Diamond;
- } else {
- if (y == by1 && !hasTopTee) {
- hasTopTee = true;
- posTopTee = y;
- special = Driver.TopTee;
- } else if ((position == 0 && y == bh - 1 || y >= by2 || by2 == 0) && !hasBottomTee) {
- hasBottomTee = true;
- posBottomTee = y;
- special = Driver.BottomTee;
- } else {
- special = Driver.VLine;
- }
- }
- }
- Driver.AddRune (special);
- }
- if (!hasTopTee) {
- Move (col, Bounds.Height - 2);
- Driver.AddRune (Driver.TopTee);
- }
- }
- } else {
- if (region.Bottom < Bounds.Height - 1)
- return;
-
- var row = Bounds.Height - 1;
- var bw = Bounds.Width;
- Rune special;
-
- if (bw < 4) {
- var bx1 = position * bw / Size;
- var bx2 = (position + bw) * bw / Size;
-
- Move (0, row);
- Driver.AddRune (Driver.LeftArrow);
- Driver.AddRune (Driver.RightArrow);
- } else {
- bw -= 2;
- var bx1 = position * bw / Size;
- var bx2 = Host.KeepContentAlwaysInViewport ? Math.Min (((position + bw) * bw / Size) + 1, bw - 1) : (position + bw) * bw / Size;
- if (Host.KeepContentAlwaysInViewport && bx1 == bx2) {
- bx1 = Math.Max (bx1 - 1, 0);
- }
-
- Move (0, row);
- Driver.AddRune (Driver.LeftArrow);
-
- bool hasLeftTee = false;
- bool hasDiamond = false;
- bool hasRightTee = false;
- for (int x = 0; x < bw; x++) {
- if ((x < bx1 || x >= bx2 + 1) && ((position > 0 && !hasLeftTee) || (hasLeftTee && hasRightTee))) {
- special = Driver.Stipple;
- } else {
- if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond) {
- hasDiamond = true;
- special = Driver.Diamond;
- } else {
- if (x == bx1 && !hasLeftTee) {
- hasLeftTee = true;
- posLeftTee = x;
- special = Driver.LeftTee;
- } else if ((position == 0 && x == bw - 1 || x >= bx2 || bx2 == 0) && !hasRightTee) {
- hasRightTee = true;
- posRightTee = x;
- special = Driver.RightTee;
- } else {
- special = Driver.HLine;
- }
- }
- }
- Driver.AddRune (special);
- }
- if (!hasLeftTee) {
- Move (Bounds.Width -2, row);
- Driver.AddRune (Driver.LeftTee);
- }
-
- Driver.AddRune (Driver.RightArrow);
- }
- }
- }
-
- int lastLocation = -1;
-
- ///
- public override bool MouseEvent (MouseEvent me)
- {
- if (me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked &&
- !me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
- return false;
- }
-
- if (!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
- lastLocation = -1;
- }
-
- int location = vertical ? me.Y : me.X;
- int barsize = vertical ? Bounds.Height : Bounds.Width;
- int posTopLeftTee = vertical ? posTopTee : posLeftTee;
- int posBottomRightTee = vertical ? posBottomTee : posRightTee;
-
- barsize -= 2;
- var pos = Position;
- if (location == 0) {
- if (pos > 0) {
- SetPosition (pos - 1);
- }
- } else if (location == barsize + 1) {
- if (Host.CanScroll (1, out _, vertical)) {
- SetPosition (pos + 1);
- }
- } else if (location > 0 && location < barsize + 1) {
- var b1 = pos * barsize / Size;
- var b2 = Host.KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size;
- if (Host.KeepContentAlwaysInViewport && b1 == b2) {
- b1 = Math.Max (b1 - 1, 0);
- }
-
- if (location > b1 && location <= b2 + 1) {
- if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1Clicked) {
- if (location == 1) {
- SetPosition (0);
- } else if (location == barsize) {
- Host.CanScroll (Size - pos, out int nv, vertical);
- if (nv > 0) {
- SetPosition (Math.Min (pos + nv, Size));
- }
- }
- } else if (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
- var mb = (b2 - b1) / 2;
- var ml = mb + b1 + (mb == 0 ? 1 : 0);
- if ((location >= b1 && location <= ml) || (location < lastLocation && lastLocation > -1)) {
- lastLocation = location;
- var np = b1 * Size / barsize;
- SetPosition (np);
- } else if (location > lastLocation) {
- var np = location * Size / barsize;
- Host.CanScroll (np - pos, out int nv, vertical);
- if (nv > 0) {
- SetPosition (pos + nv);
- }
- }
- }
- } else {
- if (location >= b2 + 1 && location > posTopLeftTee && location > b1 && location > posBottomRightTee && posBottomRightTee > 0) {
- Host.CanScroll (location, out int nv, vertical);
- if (nv > 0) {
- SetPosition (Math.Min (pos + nv, Size));
- }
- } else if (location <= b1) {
- SetPosition (Math.Max (pos - barsize - location, 0));
- }
- }
- }
-
- return true;
- }
- }
-
///
/// Scrollviews are views that present a window into a virtual space where subviews are added. Similar to the iOS UIScrollView.
///
@@ -483,6 +144,8 @@ namespace Terminal.Gui {
set {
if (keepContentAlwaysInViewport != value) {
keepContentAlwaysInViewport = value;
+ vertical.OtherScrollBarView.KeepContentAlwaysInViewport = value;
+ horizontal.OtherScrollBarView.KeepContentAlwaysInViewport = value;
Point p = default;
if (value && -contentOffset.X + Bounds.Width > contentSize.Width) {
p = new Point (contentSize.Width - Bounds.Width + (showVerticalScrollIndicator ? 1 : 0), -contentOffset.Y);
@@ -540,17 +203,21 @@ namespace Terminal.Gui {
public bool ShowHorizontalScrollIndicator {
get => showHorizontalScrollIndicator;
set {
- if (value == showHorizontalScrollIndicator)
+ if (value == showHorizontalScrollIndicator) {
return;
+ }
showHorizontalScrollIndicator = value;
SetNeedsLayout ();
if (value) {
base.Add (horizontal);
+ horizontal.OtherScrollBarView = vertical;
+ horizontal.OtherScrollBarView.ShowScrollIndicator = value;
horizontal.MouseEnter += View_MouseEnter;
horizontal.MouseLeave += View_MouseLeave;
} else {
- Remove (horizontal);
+ base.Remove (horizontal);
+ horizontal.OtherScrollBarView = null;
horizontal.MouseEnter -= View_MouseEnter;
horizontal.MouseLeave -= View_MouseLeave;
}
@@ -575,17 +242,21 @@ namespace Terminal.Gui {
public bool ShowVerticalScrollIndicator {
get => showVerticalScrollIndicator;
set {
- if (value == showVerticalScrollIndicator)
+ if (value == showVerticalScrollIndicator) {
return;
+ }
showVerticalScrollIndicator = value;
SetNeedsLayout ();
if (value) {
base.Add (vertical);
+ vertical.OtherScrollBarView = horizontal;
+ vertical.OtherScrollBarView.ShowScrollIndicator = value;
vertical.MouseEnter += View_MouseEnter;
vertical.MouseLeave += View_MouseLeave;
} else {
Remove (vertical);
+ vertical.OtherScrollBarView = null;
vertical.MouseEnter -= View_MouseEnter;
vertical.MouseLeave -= View_MouseLeave;
}
@@ -739,7 +410,7 @@ namespace Terminal.Gui {
/// Number of lines to scroll.
public bool ScrollDown (int lines)
{
- if (CanScroll (lines, out _, true)) {
+ if (vertical.CanScroll (lines, out _, true)) {
ContentOffset = new Point (contentOffset.X, contentOffset.Y - lines);
return true;
}
@@ -753,28 +424,13 @@ namespace Terminal.Gui {
/// Number of columns to scroll by.
public bool ScrollRight (int cols)
{
- if (CanScroll (cols, out _)) {
+ if (horizontal.CanScroll (cols, out _)) {
ContentOffset = new Point (contentOffset.X - cols, contentOffset.Y);
return true;
}
return false;
}
- internal bool CanScroll (int n, out int max, bool isVertical = false)
- {
- var size = isVertical ?
- (KeepContentAlwaysInViewport ? Bounds.Height + (showHorizontalScrollIndicator ? -2 : -1) : 0) :
- (KeepContentAlwaysInViewport ? Bounds.Width + (showVerticalScrollIndicator ? -2 : -1) : 0);
- var cSize = isVertical ? -contentSize.Height : -contentSize.Width;
- var cOffSet = isVertical ? contentOffset.Y : contentOffset.X;
- var newSize = Math.Max (cSize, cOffSet - n);
- max = cSize < newSize - size ? n : -cSize + (cOffSet - size) - 1;
- if (cSize < newSize - size) {
- return true;
- }
- return false;
- }
-
///
public override bool ProcessKey (KeyEvent kb)
{
diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs
index 3284843f4..50e9dc36f 100644
--- a/Terminal.Gui/Views/TextView.cs
+++ b/Terminal.Gui/Views/TextView.cs
@@ -460,6 +460,26 @@ namespace Terminal.Gui {
}
}
+ ///
+ /// Gets or sets the top row.
+ ///
+ public int TopRow { get => topRow; set => topRow = Math.Max (Math.Min (value, Lines - 1), 0); }
+
+ ///
+ /// Gets or sets the left column.
+ ///
+ public int LeftColumn { get => leftColumn; set => leftColumn = Math.Max (Math.Min (value, Maxlength - 1), 0); }
+
+ ///
+ /// Gets the maximum visible length line.
+ ///
+ public int Maxlength => model.GetMaxVisibleLine (topRow, topRow + Frame.Height);
+
+ ///
+ /// Gets the number of lines.
+ ///
+ public int Lines => model.Count;
+
///
/// Loads the contents of the file into the .
///
@@ -875,10 +895,10 @@ namespace Terminal.Gui {
idx = 0;
}
if (isRow) {
- topRow = idx > model.Count - 1 ? model.Count - 1 : idx;
+ topRow = Math.Max (idx > model.Count - 1 ? model.Count - 1 : idx, 0);
} else {
var maxlength = model.GetMaxVisibleLine (topRow, topRow + Frame.Height);
- leftColumn = idx > maxlength - 1 ? maxlength - 1 : idx;
+ leftColumn = Math.Max (idx > maxlength - 1 ? maxlength - 1 : idx, 0);
}
SetNeedsDisplay ();
}
@@ -1374,7 +1394,7 @@ namespace Terminal.Gui {
if (ev.Flags == MouseFlags.Button1Clicked) {
if (model.Count > 0) {
- var maxCursorPositionableLine = (model.Count - 1) - topRow;
+ var maxCursorPositionableLine = Math.Max ((model.Count - 1) - topRow, 0);
if (ev.Y > maxCursorPositionableLine) {
currentRow = maxCursorPositionableLine;
} else {
diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs
index 8c273f1b0..c76695d4d 100644
--- a/UICatalog/Scenarios/Editor.cs
+++ b/UICatalog/Scenarios/Editor.cs
@@ -13,6 +13,7 @@ namespace UICatalog {
private string _fileName = "demo.txt";
private TextView _textView;
private bool _saved = true;
+ private ScrollBarView _vertical;
public override void Init (Toplevel top, ColorScheme colorScheme)
{
@@ -35,6 +36,7 @@ namespace UICatalog {
new MenuItem ("C_ut", "", () => Cut()),
new MenuItem ("_Paste", "", () => Paste())
}),
+ new MenuBarItem ("_ScrollBarView", CreateKeepChecked ())
});
Top.Add (menu);
@@ -67,6 +69,41 @@ namespace UICatalog {
LoadFile ();
Win.Add (_textView);
+
+ _vertical = new ScrollBarView (_textView, true);
+ var horizontal = new ScrollBarView (_textView, false);
+ _vertical.OtherScrollBarView = horizontal;
+ horizontal.OtherScrollBarView = _vertical;
+
+ _vertical.ChangedPosition += () => {
+ _textView.TopRow = _vertical.Position;
+ if (_textView.TopRow != _vertical.Position) {
+ _vertical.Position = _textView.TopRow;
+ }
+ _textView.SetNeedsDisplay ();
+ };
+
+ horizontal.ChangedPosition += () => {
+ _textView.LeftColumn = horizontal.Position;
+ if (_textView.LeftColumn != horizontal.Position) {
+ horizontal.Position = _textView.LeftColumn;
+ }
+ _textView.SetNeedsDisplay ();
+ };
+
+ _textView.DrawContent += (e) => {
+ _vertical.Size = _textView.Lines - 1;
+ _vertical.ContentOffset = _textView.TopRow;
+ horizontal.Size = _textView.Maxlength - 1;
+ horizontal.ContentOffset = _textView.LeftColumn;
+ _vertical.ColorScheme = horizontal.ColorScheme = _textView.ColorScheme;
+ if (_vertical.ShowScrollIndicator) {
+ _vertical.Redraw (e);
+ }
+ if (horizontal.ShowScrollIndicator) {
+ horizontal.Redraw (e);
+ }
+ };
}
public override void Setup ()
@@ -145,11 +182,25 @@ namespace UICatalog {
sb.Append ("Hello world.\n");
sb.Append ("This is a test of the Emergency Broadcast System.\n");
+ for (int i = 0; i < 40; i++) {
+ sb.Append ("This is a test with a very long line and many lines to test the ScrollViewBar against the TextView.\n");
+ }
var sw = System.IO.File.CreateText (fileName);
sw.Write (sb.ToString ());
sw.Close ();
}
+ private MenuItem [] CreateKeepChecked ()
+ {
+ var item = new MenuItem ();
+ item.Title = "Keep Content Always In Viewport";
+ item.CheckType |= MenuItemCheckStyle.Checked;
+ item.Checked = true;
+ item.Action += () => _vertical.KeepContentAlwaysInViewport = item.Checked = !item.Checked;
+
+ return new MenuItem [] { item };
+ }
+
public override void Run ()
{
base.Run ();
diff --git a/UICatalog/Scenarios/ListViewWithSelection.cs b/UICatalog/Scenarios/ListViewWithSelection.cs
index 5a0e03daa..7ad3f9579 100644
--- a/UICatalog/Scenarios/ListViewWithSelection.cs
+++ b/UICatalog/Scenarios/ListViewWithSelection.cs
@@ -6,7 +6,7 @@ using System.Linq;
using Terminal.Gui;
namespace UICatalog {
- [ScenarioMetadata (Name: "List View With Selection", Description: "ListView with colunns and selection")]
+ [ScenarioMetadata (Name: "List View With Selection", Description: "ListView with columns and selection")]
[ScenarioCategory ("Controls")]
class ListViewWithSelection : Scenario {
@@ -55,9 +55,34 @@ namespace UICatalog {
};
Win.Add (_listView);
-
+ var vertical = new ScrollBarView (_listView, true);
+
+ vertical.ChangedPosition += () => {
+ _listView.TopItem = vertical.Position;
+ if (_listView.TopItem != vertical.Position) {
+ vertical.Position = _listView.TopItem;
+ }
+ _listView.SetNeedsDisplay ();
+ };
+
+ _listView.DrawContent += (e) => {
+ vertical.Size = _listView.Source.Count;
+ vertical.ContentOffset = _listView.TopItem;
+ vertical.ColorScheme = _listView.ColorScheme;
+ if (vertical.ShowScrollIndicator) {
+ vertical.Redraw (e);
+ }
+ };
+
_listView.SetSource (_scenarios);
+ var k = "Keep Content Always In Viewport";
+ var keepCheckBox = new CheckBox (k, vertical.AutoHideScrollBars) {
+ X = Pos.AnchorEnd (k.Length + 3),
+ Y = 0,
+ };
+ keepCheckBox.Toggled += (_) => vertical.KeepContentAlwaysInViewport = keepCheckBox.Checked;
+ Win.Add (keepCheckBox);
}
private void _customRenderCB_Toggled (bool prev)
@@ -84,7 +109,7 @@ namespace UICatalog {
Win.SetNeedsDisplay ();
}
- // This is basicaly the same implementation used by the UICatalog main window
+ // This is basically the same implementation used by the UICatalog main window
internal class ScenarioListDataSource : IListDataSource {
int _nameColumnWidth = 30;
private List scenarios;