diff --git a/TextControlBox-TestApp/MainWindow.xaml.cs b/TextControlBox-TestApp/MainWindow.xaml.cs
index f58a602..714de55 100644
--- a/TextControlBox-TestApp/MainWindow.xaml.cs
+++ b/TextControlBox-TestApp/MainWindow.xaml.cs
@@ -22,6 +22,8 @@ public MainWindow()
textbox.NumberOfSpacesForTab = 4;
textbox.ShowWhitespaceCharacters = true;
+ textbox.ContentVerticalScrollOffset = new TextControlBoxNS.Models.Structs.VerticalScrollOffset(100, 100);
+
SetWindowTheme(this, ElementTheme.Dark);
textbox.LinkClicked += Textbox_LinkClicked;
diff --git a/TextControlBox/Core/CoreTextControlBox.xaml b/TextControlBox/Core/CoreTextControlBox.xaml
index de7d0f9..e42f66b 100644
--- a/TextControlBox/Core/CoreTextControlBox.xaml
+++ b/TextControlBox/Core/CoreTextControlBox.xaml
@@ -23,6 +23,10 @@
+
+
+
+
-
+
\ No newline at end of file
diff --git a/TextControlBox/Core/CoreTextControlBox.xaml.cs b/TextControlBox/Core/CoreTextControlBox.xaml.cs
index 2a39099..183b487 100644
--- a/TextControlBox/Core/CoreTextControlBox.xaml.cs
+++ b/TextControlBox/Core/CoreTextControlBox.xaml.cs
@@ -14,6 +14,7 @@
using TextControlBoxNS.Languages;
using TextControlBoxNS.Models;
using TextControlBoxNS.Models.Enums;
+using TextControlBoxNS.Models.Structs;
using Windows.ApplicationModel.DataTransfer;
using Windows.Foundation;
using Windows.System;
@@ -985,6 +986,19 @@ public void EndActionGroup()
}
public bool IsGroupingActions => undoRedo.IsGroupingActions;
+ private VerticalScrollOffset _ContentVerticalScrollOffset = new(0);
+
+ public VerticalScrollOffset ContentVerticalScrollOffset
+ {
+ get => _ContentVerticalScrollOffset;
+ set
+ {
+ _ContentVerticalScrollOffset = value;
+ textRenderer.UpdateScrollOffset(value);
+ canvasUpdateManager.UpdateAll();
+ }
+ }
+
public bool EnableSyntaxHighlighting { get; set; } = true;
public SyntaxHighlightLanguage SyntaxHighlighting
diff --git a/TextControlBox/Core/PointerActionsManager.cs b/TextControlBox/Core/PointerActionsManager.cs
index 148484c..bf8c924 100644
--- a/TextControlBox/Core/PointerActionsManager.cs
+++ b/TextControlBox/Core/PointerActionsManager.cs
@@ -393,7 +393,7 @@ public void PointerWheelAction(ZoomManager zoomManager, PointerRoutedEventArgs e
{
scrollManager.verticalScrollBar.Value -= (delta * scrollManager._VerticalScrollSensitivity) / scrollManager.DefaultVerticalScrollSensitivity;
//Only update when a line was scrolled
- if ((int)(scrollManager.verticalScrollBar.Value / textRenderer.SingleLineHeight * scrollManager.DefaultVerticalScrollSensitivity) != textRenderer.NumberOfStartLine)
+ if ((int)(scrollManager.verticalScrollBar.Value / textRenderer.SingleLineHeight * scrollManager.DefaultVerticalScrollSensitivity + textRenderer.VerticalDrawOffset) != textRenderer.NumberOfStartLine)
{
needsUpdate = true;
}
diff --git a/TextControlBox/Core/Renderer/CursorRenderer.cs b/TextControlBox/Core/Renderer/CursorRenderer.cs
index 8934928..b874798 100644
--- a/TextControlBox/Core/Renderer/CursorRenderer.cs
+++ b/TextControlBox/Core/Renderer/CursorRenderer.cs
@@ -80,10 +80,10 @@ public void Draw(CanvasControl canvasText, CanvasControl canvasCursor, CanvasDra
float singleLineHeight = textRenderer.SingleLineHeight;
//Calculate the distance to the top for the cursorposition and render the cursor
- float renderPosY = (float)((cursorManager.LineNumber - startLine) * singleLineHeight) + singleLineHeight / scrollManager.DefaultVerticalScrollSensitivity;
+ float renderPosY = (float)((cursorManager.LineNumber - startLine) * singleLineHeight) + singleLineHeight / scrollManager.DefaultVerticalScrollSensitivity + textRenderer.VerticalDrawOffset;
//Out of display-region:
- if (renderPosY > linesToRender * singleLineHeight || renderPosY < 0)
+ if (renderPosY > linesToRender * singleLineHeight + textRenderer.VerticalDrawOffset || renderPosY < 0)
return;
textRenderer.UpdateCurrentLineTextLayout(canvasText);
diff --git a/TextControlBox/Core/Renderer/LineNumberRenderer.cs b/TextControlBox/Core/Renderer/LineNumberRenderer.cs
index 90d9e46..06d3b74 100644
--- a/TextControlBox/Core/Renderer/LineNumberRenderer.cs
+++ b/TextControlBox/Core/Renderer/LineNumberRenderer.cs
@@ -76,7 +76,7 @@ public void Draw(CanvasControl canvas, CanvasDrawEventArgs args, float spaceBetw
OldLineNumberTextToRender = LineNumberTextToRender;
LineNumberTextLayout = textLayoutManager.CreateTextLayout(canvas, LineNumberTextFormat, LineNumberTextToRender, posX, (float)canvas.Size.Height);
- args.DrawingSession.DrawTextLayout(LineNumberTextLayout, 10, textRenderer.SingleLineHeight, designHelper.LineNumberColorBrush);
+ args.DrawingSession.DrawTextLayout(LineNumberTextLayout, 10, textRenderer.VerticalDrawOffset + textRenderer.SingleLineHeight, designHelper.LineNumberColorBrush);
}
public void CreateLineNumberTextFormat()
diff --git a/TextControlBox/Core/Renderer/SelectionRenderer.cs b/TextControlBox/Core/Renderer/SelectionRenderer.cs
index 24c5618..e2e6418 100644
--- a/TextControlBox/Core/Renderer/SelectionRenderer.cs
+++ b/TextControlBox/Core/Renderer/SelectionRenderer.cs
@@ -191,7 +191,7 @@ public void Draw(CanvasControl canvasSelection, CanvasDrawEventArgs args)
textRenderer.DrawnTextLayout,
args,
(float)-scrollManager.HorizontalScroll,
- textRenderer.SingleLineHeight / scrollManager.DefaultVerticalScrollSensitivity,
+ textRenderer.SingleLineHeight / scrollManager.DefaultVerticalScrollSensitivity + textRenderer.VerticalDrawOffset,
textRenderer.NumberOfStartLine,
textRenderer.NumberOfRenderedLines,
zoomManager.ZoomedFontSize,
diff --git a/TextControlBox/Core/Renderer/TextRenderer.cs b/TextControlBox/Core/Renderer/TextRenderer.cs
index 01246b0..1ec3813 100644
--- a/TextControlBox/Core/Renderer/TextRenderer.cs
+++ b/TextControlBox/Core/Renderer/TextRenderer.cs
@@ -8,6 +8,7 @@
using System.Runtime.InteropServices;
using TextControlBoxNS.Core.Text;
using TextControlBoxNS.Helper;
+using TextControlBoxNS.Models.Structs;
using Windows.Foundation;
namespace TextControlBoxNS.Core.Renderer;
@@ -27,6 +28,10 @@ internal class TextRenderer
public int NumberOfRenderedLines = 0;
public string RenderedText = "";
public string OldRenderedText = null;
+ public float VerticalDrawOffset { get; private set; } = 0;
+
+ public double TopScrollOffset { get; private set; } = 0;
+ public double BottomScrollOffset { get; private set; } = 0;
private CursorManager cursorManager;
private TextManager textManager;
@@ -75,6 +80,14 @@ public void Init(
this.invisibleCharactersRenderer = invisibleCharactersRenderer;
this.linkRenderer = linkRenderer;
this.linkHighlightManager = linkHighlightManager;
+
+ UpdateScrollOffset(coreTextbox.ContentVerticalScrollOffset);
+ }
+
+ public void UpdateScrollOffset(VerticalScrollOffset verticalScrollOffset)
+ {
+ this.TopScrollOffset = verticalScrollOffset.Top;
+ this.BottomScrollOffset = verticalScrollOffset.Bottom;
}
//Check whether the current line is outside the bounds of the visible area
@@ -99,24 +112,49 @@ public void UpdateCurrentLineTextLayout(CanvasControl canvasText)
var singleLineHeight = SingleLineHeight;
//Measure text position and apply the value to the scrollbar
- scrollManager.verticalScrollBar.Maximum = ((textManager.LinesCount + 1) * singleLineHeight - scrollGrid.ActualHeight) / scrollManager.DefaultVerticalScrollSensitivity;
+ scrollManager.verticalScrollBar.Maximum = ((textManager.LinesCount + 1) * singleLineHeight - scrollGrid.ActualHeight + BottomScrollOffset + TopScrollOffset) / scrollManager.DefaultVerticalScrollSensitivity;
scrollManager.verticalScrollBar.ViewportSize = coreTextbox.canvasText.ActualHeight;
//Calculate number of lines that need to be rendered
int linesToRenderCount = (int)(coreTextbox.canvasText.ActualHeight / singleLineHeight);
- linesToRenderCount = Math.Min(linesToRenderCount, textManager.LinesCount);
+ linesToRenderCount = Math.Min(Math.Max(linesToRenderCount, 1), textManager.LinesCount);
- int startLine = (int)((scrollManager.VerticalScroll * scrollManager.DefaultVerticalScrollSensitivity) / singleLineHeight);
+ int startLine = (int)(((scrollManager.VerticalScroll - VerticalDrawOffset) * scrollManager.DefaultVerticalScrollSensitivity - TopScrollOffset) / singleLineHeight);
startLine = Math.Min(startLine, textManager.LinesCount);
+ if (startLine < 0) startLine = 0;
+
int linesToRender = Math.Min(linesToRenderCount, textManager.LinesCount - startLine);
return (startLine, linesToRender);
}
+ public float CalculateDrawOffset()
+ {
+ double verticalScroll = scrollManager.VerticalScroll;
+
+ double scrollCoeff = scrollManager.verticalScrollBar.Maximum / scrollManager.VerticalScroll;
+
+ double realScrollPosition = verticalScroll * scrollManager.DefaultVerticalScrollSensitivity - TopScrollOffset;
+ double preCalcOffset = realScrollPosition < 0 ? -realScrollPosition : SingleLineHeight;
+
+ float drawOffset = (float)(preCalcOffset < SingleLineHeight ? SingleLineHeight : Math.Floor(preCalcOffset / SingleLineHeight) * SingleLineHeight);
+
+ if (drawOffset > SingleLineHeight)
+ {
+ if (scrollCoeff == 1)
+ {
+ drawOffset = SingleLineHeight;
+ }
+ }
+ return drawOffset - SingleLineHeight;
+ }
+
public void Draw(CanvasControl canvasText, CanvasDrawEventArgs args)
{
+ VerticalDrawOffset = CalculateDrawOffset();
+
//Create resources and layouts:
if (NeedsTextFormatUpdate || TextFormat == null || lineNumberRenderer.LineNumberTextFormat == null)
{
@@ -171,13 +209,13 @@ public void Draw(CanvasControl canvasText, CanvasDrawEventArgs args)
searchManager.MatchingSearchLines,
searchManager.searchParameter.SearchExpression,
(float)-scrollManager.HorizontalScroll,
- SingleLineHeight / scrollManager.DefaultVerticalScrollSensitivity,
+ SingleLineHeight / scrollManager.DefaultVerticalScrollSensitivity + VerticalDrawOffset,
designHelper._Design.SearchHighlightColor
);
- ccls.DrawTextLayout(DrawnTextLayout, (float)-scrollManager.HorizontalScroll, SingleLineHeight, designHelper.TextColorBrush);
+ ccls.DrawTextLayout(DrawnTextLayout, (float)-scrollManager.HorizontalScroll, VerticalDrawOffset + SingleLineHeight, designHelper.TextColorBrush);
- invisibleCharactersRenderer.DrawTabsAndSpaces(args, ccls, RenderedText, DrawnTextLayout, SingleLineHeight);
+ invisibleCharactersRenderer.DrawTabsAndSpaces(args, ccls, RenderedText, DrawnTextLayout, VerticalDrawOffset + SingleLineHeight);
}
args.DrawingSession.DrawImage(canvasCommandList);
@@ -189,5 +227,7 @@ public void Draw(CanvasControl canvasText, CanvasDrawEventArgs args)
{
canvasUpdateManager.UpdateLineNumbers();
}
+ canvasUpdateManager.UpdateSelection(); // Possible bad for performanse
+ canvasUpdateManager.UpdateCursor(); // Possible bad for performanse
}
}
diff --git a/TextControlBox/Core/ScrollManager.cs b/TextControlBox/Core/ScrollManager.cs
index dad7bdf..aaa8a99 100644
--- a/TextControlBox/Core/ScrollManager.cs
+++ b/TextControlBox/Core/ScrollManager.cs
@@ -57,7 +57,7 @@ internal void HorizontalScrollBar_Scroll(object sender, ScrollEventArgs e)
internal void VerticalScrollBar_Scroll(object sender, ScrollEventArgs e)
{
//only update when a line was scrolled
- if ((int)(verticalScrollBar.Value / textRenderer.SingleLineHeight * DefaultVerticalScrollSensitivity) != textRenderer.NumberOfStartLine)
+ if ((int)(verticalScrollBar.Value / textRenderer.SingleLineHeight * DefaultVerticalScrollSensitivity + textRenderer.VerticalDrawOffset ) != textRenderer.NumberOfStartLine)
{
canvasHelper.UpdateAll();
}
@@ -66,7 +66,7 @@ internal void VerticalScrollBar_Scroll(object sender, ScrollEventArgs e)
public void UpdateWhenScrolled()
{
//only update when a line was scrolled
- if ((int)(verticalScrollBar.Value / textRenderer.SingleLineHeight) != textRenderer.NumberOfStartLine)
+ if ((int)(verticalScrollBar.Value / textRenderer.SingleLineHeight + textRenderer.VerticalDrawOffset) != textRenderer.NumberOfStartLine)
{
canvasHelper.UpdateAll();
}
@@ -133,10 +133,30 @@ public void ScrollPageDown()
}
public void UpdateScrollToShowCursor(bool update = true)
{
- if (textRenderer.NumberOfStartLine + textRenderer.NumberOfRenderedLines <= cursorManager.LineNumber)
- verticalScrollBar.Value = (cursorManager.LineNumber - textRenderer.NumberOfRenderedLines + 1) * textRenderer.SingleLineHeight / DefaultVerticalScrollSensitivity;
+ double globalOffset = textRenderer.VerticalDrawOffset == 0 ? textRenderer.TopScrollOffset : 0;
+ double localOffset = textRenderer.VerticalDrawOffset;
+
+ if (cursorManager.LineNumber == 0)
+ {
+ verticalScrollBar.Value = 0;
+ }
+ else if (textRenderer.NumberOfStartLine + textRenderer.NumberOfRenderedLines <= cursorManager.LineNumber)
+ {
+ verticalScrollBar.Value = ((cursorManager.LineNumber - textRenderer.NumberOfRenderedLines + 1) * textRenderer.SingleLineHeight + globalOffset) / DefaultVerticalScrollSensitivity + localOffset;
+ }
else if (textRenderer.NumberOfStartLine > cursorManager.LineNumber)
- verticalScrollBar.Value = (cursorManager.LineNumber - 1) * textRenderer.SingleLineHeight / DefaultVerticalScrollSensitivity;
+ {
+ verticalScrollBar.Value = ((cursorManager.LineNumber - 1) * textRenderer.SingleLineHeight + globalOffset) / DefaultVerticalScrollSensitivity + localOffset;
+ }
+ else if (textRenderer.VerticalDrawOffset != 0)
+ {
+ int realLineDisplayedCount = (int)((verticalScrollBar.ViewportSize - textRenderer.VerticalDrawOffset) / textRenderer.SingleLineHeight);
+ if (realLineDisplayedCount <= cursorManager.LineNumber)
+ {
+ double offsetToMove = ((cursorManager.LineNumber - realLineDisplayedCount + 1) * textRenderer.SingleLineHeight);
+ verticalScrollBar.Value = ((cursorManager.LineNumber - 1) * textRenderer.SingleLineHeight) / DefaultVerticalScrollSensitivity + offsetToMove;
+ }
+ }
if (update)
canvasHelper.UpdateAll();
diff --git a/TextControlBox/Helper/CursorHelper.cs b/TextControlBox/Helper/CursorHelper.cs
index fa66a91..35f8c64 100644
--- a/TextControlBox/Helper/CursorHelper.cs
+++ b/TextControlBox/Helper/CursorHelper.cs
@@ -14,7 +14,7 @@ internal class CursorHelper
public static int GetCursorLineFromPoint(TextRenderer textRenderer, Point point)
{
//Calculate the relative linenumber, where the pointer was pressed at
- int linenumber = (int)(point.Y / textRenderer.SingleLineHeight);
+ int linenumber = (int)((point.Y - textRenderer.VerticalDrawOffset) / textRenderer.SingleLineHeight);
linenumber += textRenderer.NumberOfStartLine;
return Math.Clamp(linenumber, 0, textRenderer.NumberOfStartLine + textRenderer.NumberOfRenderedLines - 1);
}
diff --git a/TextControlBox/Models/Structs/VerticalScrollOffset.cs b/TextControlBox/Models/Structs/VerticalScrollOffset.cs
new file mode 100644
index 0000000..e6359a5
--- /dev/null
+++ b/TextControlBox/Models/Structs/VerticalScrollOffset.cs
@@ -0,0 +1,99 @@
+namespace TextControlBoxNS.Models.Structs;
+
+///
+/// Describes the offset between content and vertical scroll area borders.
+/// Two Double values describe the Top and Bottom offsets, respectively.
+///
+public struct VerticalScrollOffset
+{
+ ///
+ /// The top offset of content
+ ///
+ public double Top { get; set; }
+
+ ///
+ /// The bottom offset of content
+ ///
+
+ public double Bottom { get; set; }
+
+ ///
+ /// Creates new VerticalScrollOffset object
+ ///
+ /// The top and bottom content offset
+ public VerticalScrollOffset(double uniformLength)
+ {
+ Top = (Bottom = uniformLength);
+ }
+
+ ///
+ /// Creates new VerticalScrollOffset object
+ ///
+ /// The top offset of content
+ /// The bottom offset of content
+ public VerticalScrollOffset(double top, double bottom)
+ {
+ this.Top = top;
+ this.Bottom = bottom;
+ }
+
+
+ ///
+ public override string ToString()
+ {
+ return $"{Top}, {Bottom}";
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is VerticalScrollOffset verticalScrollOffset)
+ {
+ return this == verticalScrollOffset;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Indicates whether this instance and a specified object are equal.
+ ///
+ /// true if verticalScrollOffset and this instance are represent the same value; otherwise, false.
+ public readonly bool Equals(VerticalScrollOffset verticalScrollOffset)
+ {
+ return this == verticalScrollOffset;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return Top.GetHashCode() ^ Bottom.GetHashCode();
+ }
+
+ ///
+ /// Compares two VerticalScrollOffset objects
+ ///
+ ///
+ ///
+ /// true if offsets are same; otherwise, false.
+ public static bool operator ==(VerticalScrollOffset t1, VerticalScrollOffset t2)
+ {
+ if (t1.Top == t2.Top)
+ {
+ return t1.Bottom == t2.Bottom;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Compares two VerticalScrollOffset objects
+ ///
+ ///
+ ///
+ /// true if offsets are different; otherwise, false.
+ public static bool operator !=(VerticalScrollOffset t1, VerticalScrollOffset t2)
+ {
+ return !(t1 == t2);
+ }
+}
diff --git a/TextControlBox/TextControlBox.cs b/TextControlBox/TextControlBox.cs
index 6f75e64..37e2ab0 100644
--- a/TextControlBox/TextControlBox.cs
+++ b/TextControlBox/TextControlBox.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using TextControlBoxNS.Core;
using TextControlBoxNS.Models;
+using TextControlBoxNS.Models.Structs;
using Windows.Foundation;
namespace TextControlBoxNS;
@@ -664,6 +665,15 @@ public bool AddLines(int start, string[] text)
return coreTextBox.AddLines(start, text);
}
+ ///
+ /// Gets or sets content scroll offset
+ ///
+ public VerticalScrollOffset ContentVerticalScrollOffset
+ {
+ get => coreTextBox.ContentVerticalScrollOffset;
+ set => coreTextBox.ContentVerticalScrollOffset = value;
+ }
+
///
/// returns the current tabs and spaces detected from the loaded document.
/// with useSpacesInsteadTabs indicates whether spaces are used instead of tabs and with spaces the number of spaces used for a tab