cool tekst editor
This commit is contained in:
parent
87ea8f4771
commit
30157f1f5d
|
|
@ -0,0 +1,7 @@
|
||||||
|
using Qrakhen.Qamp.Editor.Primitives;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Editor.Commands;
|
||||||
|
|
||||||
|
public readonly record struct MoveCaretOptions(BufferPosition Position,
|
||||||
|
bool Relative = true,
|
||||||
|
bool Jump = false);
|
||||||
|
|
@ -27,4 +27,4 @@ public class RelayCommand : ICommand
|
||||||
public bool CanExecute(object? parameter) => _canExecute(parameter);
|
public bool CanExecute(object? parameter) => _canExecute(parameter);
|
||||||
|
|
||||||
public void Execute(object? parameter) => _execute(parameter);
|
public void Execute(object? parameter) => _execute(parameter);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,10 @@ public class Caret : Adorner
|
||||||
private readonly Brush _brush;
|
private readonly Brush _brush;
|
||||||
private bool _blink;
|
private bool _blink;
|
||||||
private Point _cursorPosition;
|
private Point _cursorPosition;
|
||||||
private DispatcherTimer _timer;
|
private Point _targetPosition;
|
||||||
|
private (Point from, Point to)[] _lines = [];
|
||||||
|
private DispatcherTimer _blinkTimer;
|
||||||
|
private DispatcherTimer _moveTimer;
|
||||||
|
|
||||||
public Caret(UIElement adornedElement, Brush brush, double height = 18, double thickness = 1.72) : base(adornedElement)
|
public Caret(UIElement adornedElement, Brush brush, double height = 18, double thickness = 1.72) : base(adornedElement)
|
||||||
{
|
{
|
||||||
|
|
@ -20,13 +23,28 @@ public class Caret : Adorner
|
||||||
|
|
||||||
_height = height;
|
_height = height;
|
||||||
_brush = brush;
|
_brush = brush;
|
||||||
_pen = new Pen(brush, 1.5);
|
_pen = new Pen(brush, 1.92);
|
||||||
_timer = new DispatcherTimer();
|
|
||||||
_timer.Interval = TimeSpan.FromMilliseconds(428);
|
_blinkTimer = new DispatcherTimer();
|
||||||
_timer.Tick += new EventHandler((s, e) => {
|
_blinkTimer.Interval = TimeSpan.FromMilliseconds(428);
|
||||||
|
_blinkTimer.Tick += new EventHandler((s, e) => {
|
||||||
Toggle(_blink = !_blink);
|
Toggle(_blink = !_blink);
|
||||||
});
|
});
|
||||||
_timer.Start();
|
_blinkTimer.Start();
|
||||||
|
|
||||||
|
_moveTimer = new DispatcherTimer();
|
||||||
|
_moveTimer.Interval = TimeSpan.FromMilliseconds(12);
|
||||||
|
_moveTimer.Tick += new EventHandler((s, e) => {
|
||||||
|
var delta = _targetPosition - _cursorPosition;
|
||||||
|
if (delta.Length > 2.4)
|
||||||
|
_cursorPosition += delta * .84;
|
||||||
|
else {
|
||||||
|
_cursorPosition = _targetPosition;
|
||||||
|
_moveTimer.Stop();
|
||||||
|
_blinkTimer.Start();
|
||||||
|
}
|
||||||
|
InvalidateVisual();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Toggle(bool hide)
|
public void Toggle(bool hide)
|
||||||
|
|
@ -34,13 +52,19 @@ public class Caret : Adorner
|
||||||
_pen.Brush = hide ? Brushes.Transparent : _brush;
|
_pen.Brush = hide ? Brushes.Transparent : _brush;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateSelection((Point from, Point to)[] lines)
|
||||||
|
{
|
||||||
|
_lines = lines;
|
||||||
|
InvalidateVisual();
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdatePosition(Point newPosition)
|
public void UpdatePosition(Point newPosition)
|
||||||
{
|
{
|
||||||
_timer.Stop();
|
_cursorPosition = _targetPosition = newPosition;
|
||||||
Toggle(_blink = false);
|
Toggle(_blink = false);
|
||||||
_cursorPosition = newPosition;
|
//_blinkTimer.Stop();
|
||||||
|
//_moveTimer.Start();
|
||||||
InvalidateVisual();
|
InvalidateVisual();
|
||||||
_timer.Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnRender(DrawingContext drawingContext)
|
protected override void OnRender(DrawingContext drawingContext)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
<UserControl.Style>
|
<UserControl.Style>
|
||||||
<Style TargetType="{x:Type local:EditorFrame}">
|
<Style TargetType="{x:Type local:EditorFrame}">
|
||||||
<Setter Property="CursorPosition" Value="{Binding CursorPosition}" />
|
<Setter Property="CursorPosition" Value="{Binding CursorPosition}" />
|
||||||
|
<Setter Property="CurrentSelection" Value="{Binding CurrentSelection}" />
|
||||||
<Setter Property="CurrentLineBuffer" Value="{Binding CurrentLineBuffer}" />
|
<Setter Property="CurrentLineBuffer" Value="{Binding CurrentLineBuffer}" />
|
||||||
</Style>
|
</Style>
|
||||||
</UserControl.Style>
|
</UserControl.Style>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
using Qrakhen.Qamp.Editor.Primitives;
|
using Qrakhen.Qamp.Editor.Commands;
|
||||||
|
using Qrakhen.Qamp.Editor.Input;
|
||||||
|
using Qrakhen.Qamp.Editor.Primitives;
|
||||||
using Qrakhen.Qamp.Editor.ViewModel;
|
using Qrakhen.Qamp.Editor.ViewModel;
|
||||||
using Qrakhen.Qamp.Memory;
|
using Qrakhen.Qamp.Memory;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
@ -6,6 +8,7 @@ using System.Windows.Controls;
|
||||||
using System.Windows.Documents;
|
using System.Windows.Documents;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
namespace Qrakhen.Qamp.Editor.Controls;
|
namespace Qrakhen.Qamp.Editor.Controls;
|
||||||
|
|
||||||
|
|
@ -18,8 +21,22 @@ public partial class EditorFrame : UserControl
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
Loaded += OnLoaded;
|
Loaded += OnLoaded;
|
||||||
PreviewKeyDown += OnKeyDown;
|
PreviewKeyDown += OnKeyDown;
|
||||||
|
PreviewMouseDown += OnMouseDown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void OnMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.LeftButton == MouseButtonState.Pressed) {
|
||||||
|
if (DataContext is EditorFrameViewModel vm) {
|
||||||
|
vm.MoveCaretCommand.Execute(
|
||||||
|
new MoveCaretOptions(
|
||||||
|
CalculateCursorPosition(e.GetPosition(this), 0, 0),
|
||||||
|
false,
|
||||||
|
false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnKeyDown(object sender, KeyEventArgs e)
|
private void OnKeyDown(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is EditorFrameViewModel vm) {
|
if (DataContext is EditorFrameViewModel vm) {
|
||||||
|
|
@ -30,8 +47,12 @@ public partial class EditorFrame : UserControl
|
||||||
Key.Down => new(1, 0),
|
Key.Down => new(1, 0),
|
||||||
_ => new(0, 0)
|
_ => new(0, 0)
|
||||||
};
|
};
|
||||||
if (position != BufferPosition.Void)
|
if (position != BufferPosition.Initial)
|
||||||
vm.MoveCaretCommand.Execute(position);
|
vm.MoveCaretCommand.Execute(
|
||||||
|
new MoveCaretOptions(
|
||||||
|
position,
|
||||||
|
true,
|
||||||
|
InputService.CtrlHeld));
|
||||||
else if (e.Key == Key.Back)
|
else if (e.Key == Key.Back)
|
||||||
vm.DeleteCommand.Execute(-1);
|
vm.DeleteCommand.Execute(-1);
|
||||||
else if (e.Key == Key.Delete)
|
else if (e.Key == Key.Delete)
|
||||||
|
|
@ -41,9 +62,11 @@ public partial class EditorFrame : UserControl
|
||||||
else if (e.Key == Key.Delete)
|
else if (e.Key == Key.Delete)
|
||||||
vm.DeleteCommand.Execute(1);
|
vm.DeleteCommand.Execute(1);
|
||||||
else {
|
else {
|
||||||
string c = e.Key.ToString();
|
char c = KeyHelper.Get(e.Key,
|
||||||
if (c.Length == 1)
|
InputService.ShiftHeld ? 1 :
|
||||||
vm.InsertCommand.Execute(c);
|
(InputService.CtrlHeld && InputService.AltHeld) ? 2 : 0);
|
||||||
|
if (c != 0)
|
||||||
|
vm.InsertCommand.Execute(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
|
|
@ -63,7 +86,13 @@ public partial class EditorFrame : UserControl
|
||||||
Loaded -= OnLoaded;
|
Loaded -= OnLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Point CalculateCaretPosition(int line, int index, double scrollY, double scrollX)
|
private BufferPosition CalculateCursorPosition(Point point, double scrollY, double scrollX)
|
||||||
|
{
|
||||||
|
var ft = TextHelper.GetText(new string('_', 1), 14, null, VisualTreeHelper.GetDpi(this).PixelsPerDip);
|
||||||
|
return new BufferPosition((int)(point.Y / TextHelper.LineHeight), (int)(point.X / ft.Width));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point CalculateCaretPosition(int line, int index, double scrollY, double scrollX)
|
||||||
{
|
{
|
||||||
var ft = TextHelper.GetText(new string('_', index), 14, null, VisualTreeHelper.GetDpi(this).PixelsPerDip);
|
var ft = TextHelper.GetText(new string('_', index), 14, null, VisualTreeHelper.GetDpi(this).PixelsPerDip);
|
||||||
return new Point(ft.Width + scrollX + 2, line * TextHelper.LineHeight + scrollY);
|
return new Point(ft.Width + scrollX + 2, line * TextHelper.LineHeight + scrollY);
|
||||||
|
|
@ -74,7 +103,12 @@ public partial class EditorFrame : UserControl
|
||||||
Caret?.UpdatePosition(point);
|
Caret?.UpdatePosition(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateCaret()
|
private void SelectRegion((Point from, Point to)[] lines)
|
||||||
|
{
|
||||||
|
Caret?.UpdateSelection(lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCaret()
|
||||||
{
|
{
|
||||||
int line = CursorPosition.Line;
|
int line = CursorPosition.Line;
|
||||||
int charIndex = CursorPosition.Column;
|
int charIndex = CursorPosition.Column;
|
||||||
|
|
@ -88,6 +122,10 @@ public partial class EditorFrame : UserControl
|
||||||
charIndex,
|
charIndex,
|
||||||
scrollY,
|
scrollY,
|
||||||
scrollX));
|
scrollX));
|
||||||
|
|
||||||
|
if (!CurrentSelection.IsVoid) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LineBuffer CurrentLineBuffer {
|
public LineBuffer CurrentLineBuffer {
|
||||||
|
|
@ -112,11 +150,29 @@ public partial class EditorFrame : UserControl
|
||||||
nameof(CursorPosition),
|
nameof(CursorPosition),
|
||||||
typeof(BufferPosition),
|
typeof(BufferPosition),
|
||||||
typeof(EditorFrame),
|
typeof(EditorFrame),
|
||||||
new PropertyMetadata(BufferPosition.Void, OnCursorPositionChanged));
|
new PropertyMetadata(BufferPosition.Initial, OnCursorPositionChanged));
|
||||||
|
|
||||||
private static void OnCursorPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
private static void OnCursorPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
var frame = (EditorFrame)d;
|
var frame = (EditorFrame)d;
|
||||||
frame.UpdateCaret();
|
frame.UpdateCaret();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BufferRegion CurrentSelection {
|
||||||
|
get => (BufferRegion)GetValue(CurrentSelectionProperty);
|
||||||
|
set => SetValue(CurrentSelectionProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DependencyProperty CurrentSelectionProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(CurrentSelection),
|
||||||
|
typeof(BufferRegion),
|
||||||
|
typeof(EditorFrame),
|
||||||
|
new PropertyMetadata(BufferRegion.Void, OnCurrentSelectionChanged));
|
||||||
|
|
||||||
|
private static void OnCurrentSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var frame = (EditorFrame)d;
|
||||||
|
frame.UpdateCaret();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,5 @@
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="18" d:DesignWidth="420">
|
d:DesignHeight="18" d:DesignWidth="420">
|
||||||
<Grid>
|
<Grid>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
|
||||||
|
|
@ -9,71 +9,114 @@ using System.Windows.Data;
|
||||||
using System.Windows.Documents;
|
using System.Windows.Documents;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
|
||||||
using System.Windows.Navigation;
|
|
||||||
using System.Windows.Shapes;
|
|
||||||
|
|
||||||
namespace Qrakhen.Qamp.Editor.Controls;
|
namespace Qrakhen.Qamp.Editor.Controls;
|
||||||
|
|
||||||
public partial class LineRenderer : UserControl
|
public partial class LineRenderer : UserControl
|
||||||
{
|
{
|
||||||
public static readonly DependencyProperty LineBufferProperty =
|
private readonly Typeface _typeface = new(new FontFamily("Consolas"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
|
||||||
|
private const double FontSize = 14;
|
||||||
|
private const double LineHeight = 18;
|
||||||
|
|
||||||
|
public Pen Pen { get; set; }
|
||||||
|
public Brush Highlighter { get; set; }
|
||||||
|
|
||||||
|
public LineRenderer()
|
||||||
|
{
|
||||||
|
Height = LineHeight;
|
||||||
|
SnapsToDevicePixels = true;
|
||||||
|
Highlighter = new SolidColorBrush(Color.FromArgb(0xff, 0x24, 0x92, 0x72));
|
||||||
|
Pen = new Pen(Highlighter, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnRender(DrawingContext drawingContext)
|
||||||
|
{
|
||||||
|
base.OnRender(drawingContext);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(LineBuffer.Cached))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var formattedText = TextHelper.GetText(
|
||||||
|
LineBuffer.Cached,
|
||||||
|
FontSize,
|
||||||
|
Brushes.White,
|
||||||
|
VisualTreeHelper.GetDpi(this).PixelsPerDip
|
||||||
|
);
|
||||||
|
|
||||||
|
var x = Random.Shared.Next(0, LineBuffer.Tail);
|
||||||
|
var s = new BufferSpan(x, Random.Shared.Next(x, LineBuffer.Tail));
|
||||||
|
Selection = s;
|
||||||
|
|
||||||
|
var highlighter = TextHelper.GetText(
|
||||||
|
new string(' ', Selection.Start) + new string('█', Selection.End - Selection.Start),
|
||||||
|
FontSize,
|
||||||
|
Highlighter,
|
||||||
|
VisualTreeHelper.GetDpi(this).PixelsPerDip
|
||||||
|
);
|
||||||
|
|
||||||
|
drawingContext.DrawText(highlighter, new Point(2, 0));
|
||||||
|
drawingContext.DrawText(formattedText, new Point(2, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DependencyProperty LineBufferProperty =
|
||||||
DependencyProperty.Register(
|
DependencyProperty.Register(
|
||||||
nameof(LineBuffer),
|
nameof(LineBuffer),
|
||||||
typeof(LineBuffer),
|
typeof(LineBuffer),
|
||||||
typeof(LineRenderer),
|
typeof(LineRenderer),
|
||||||
new FrameworkPropertyMetadata(null, OnLineBufferChanged));
|
new FrameworkPropertyMetadata(null, OnLineBufferChanged));
|
||||||
|
|
||||||
public LineBuffer LineBuffer {
|
public LineBuffer LineBuffer {
|
||||||
get => (LineBuffer)GetValue(LineBufferProperty);
|
get => (LineBuffer)GetValue(LineBufferProperty);
|
||||||
set => SetValue(LineBufferProperty, value);
|
set => SetValue(LineBufferProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Typeface _typeface = new(new FontFamily("Consolas"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
|
private static void OnLineBufferChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
private const double FontSize = 14;
|
{
|
||||||
private const double LineHeight = 18;
|
if (d is LineRenderer renderer) {
|
||||||
|
if (e.OldValue is LineBuffer oldBuffer && oldBuffer != null) {
|
||||||
|
oldBuffer.PropertyChanged -= renderer.OnLineBufferPropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
public LineRenderer()
|
if (e.NewValue is LineBuffer newBuffer && newBuffer != null) {
|
||||||
{
|
newBuffer.PropertyChanged += renderer.OnLineBufferPropertyChanged;
|
||||||
Height = LineHeight;
|
}
|
||||||
SnapsToDevicePixels = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnRender(DrawingContext drawingContext)
|
renderer.InvalidateVisual();
|
||||||
{
|
}
|
||||||
base.OnRender(drawingContext);
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(LineBuffer.Cached))
|
private void OnLineBufferPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
return;
|
{
|
||||||
|
if (e.PropertyName == nameof(LineBuffer.Cached))
|
||||||
|
InvalidateVisual();
|
||||||
|
}
|
||||||
|
|
||||||
var formattedText = TextHelper.GetText(
|
|
||||||
LineBuffer.Cached,
|
|
||||||
FontSize,
|
|
||||||
Brushes.White,
|
|
||||||
VisualTreeHelper.GetDpi(this).PixelsPerDip
|
|
||||||
);
|
|
||||||
|
|
||||||
drawingContext.DrawText(formattedText, new Point(2, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnLineBufferChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
public static readonly DependencyProperty SelectionProperty =
|
||||||
{
|
DependencyProperty.Register(
|
||||||
if (d is LineRenderer renderer) {
|
nameof(Selection),
|
||||||
if (e.OldValue is LineBuffer oldBuffer && oldBuffer != null) {
|
typeof(BufferSpan),
|
||||||
oldBuffer.PropertyChanged -= renderer.OnLineBufferPropertyChanged;
|
typeof(LineRenderer),
|
||||||
}
|
new FrameworkPropertyMetadata(BufferSpan.None, OnSelectionChanged));
|
||||||
|
|
||||||
if (e.NewValue is LineBuffer newBuffer && newBuffer != null) {
|
public BufferSpan Selection {
|
||||||
newBuffer.PropertyChanged += renderer.OnLineBufferPropertyChanged;
|
get => (BufferSpan)GetValue(SelectionProperty);
|
||||||
}
|
set => SetValue(SelectionProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
renderer.InvalidateVisual();
|
private static void OnSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
}
|
{
|
||||||
}
|
if (d is LineRenderer renderer) {
|
||||||
|
if (e.OldValue is LineBuffer oldBuffer && oldBuffer != null) {
|
||||||
|
oldBuffer.PropertyChanged -= renderer.OnLineBufferPropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.NewValue is LineBuffer newBuffer && newBuffer != null) {
|
||||||
|
newBuffer.PropertyChanged += renderer.OnLineBufferPropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.InvalidateVisual();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnLineBufferPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.PropertyName == nameof(LineBuffer.Cached))
|
|
||||||
InvalidateVisual();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,19 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.CodeDom.Compiler;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing.Drawing2D;
|
||||||
|
using System.Runtime.ConstrainedExecution;
|
||||||
|
using System.Security.Policy;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Controls.Primitives;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using static System.Net.Mime.MediaTypeNames;
|
||||||
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||||
|
|
||||||
namespace Qrakhen.Qamp.Editor.InputService;
|
namespace Qrakhen.Qamp.Editor.Input;
|
||||||
|
|
||||||
public interface IInputHandler
|
public interface IInputHandler
|
||||||
{
|
{
|
||||||
|
|
@ -34,7 +44,7 @@ public class InputService : IInputHandler
|
||||||
public static bool LeftShiftHeld => Keyboard.IsKeyDown(Key.LeftShift);
|
public static bool LeftShiftHeld => Keyboard.IsKeyDown(Key.LeftShift);
|
||||||
public static bool LeftAltHeld => Keyboard.IsKeyDown(Key.LeftAlt);
|
public static bool LeftAltHeld => Keyboard.IsKeyDown(Key.LeftAlt);
|
||||||
public static bool RightCtrlHeld => Keyboard.IsKeyDown(Key.RightCtrl);
|
public static bool RightCtrlHeld => Keyboard.IsKeyDown(Key.RightCtrl);
|
||||||
public static bool RightShiftHeld => Keyboard.IsKeyDown(Key.LeftCtrl);
|
public static bool RightShiftHeld => Keyboard.IsKeyDown(Key.RightShift);
|
||||||
public static bool RightAltHeld => Keyboard.IsKeyDown(Key.RightAlt);
|
public static bool RightAltHeld => Keyboard.IsKeyDown(Key.RightAlt);
|
||||||
public static bool CtrlHeld => LeftCtrlHeld || RightCtrlHeld;
|
public static bool CtrlHeld => LeftCtrlHeld || RightCtrlHeld;
|
||||||
public static bool ShiftHeld => LeftShiftHeld || RightShiftHeld;
|
public static bool ShiftHeld => LeftShiftHeld || RightShiftHeld;
|
||||||
|
|
@ -44,4 +54,212 @@ public class InputService : IInputHandler
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class KeyHelper
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<Key, char[]> _chars = [];
|
||||||
|
|
||||||
|
public static char Get(Key key, int modifier = 0)
|
||||||
|
{
|
||||||
|
return _chars[key][modifier];
|
||||||
|
}
|
||||||
|
|
||||||
|
static KeyHelper()
|
||||||
|
{
|
||||||
|
for (Key i = Key.A; i <= Key.Z; i++) {
|
||||||
|
char c = i.ToString()[^1];
|
||||||
|
Add(i, char.ToLower(c), char.ToUpper(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
_chars[Key.Q] = ['q', 'Q', '@'];
|
||||||
|
|
||||||
|
Add(Key.None);
|
||||||
|
Add(Key.Cancel);
|
||||||
|
Add(Key.Back);
|
||||||
|
Add(Key.Tab, '\t');
|
||||||
|
Add(Key.LineFeed);
|
||||||
|
Add(Key.Clear);
|
||||||
|
Add(Key.Enter, '\n');
|
||||||
|
Add(Key.Return);
|
||||||
|
Add(Key.Pause);
|
||||||
|
Add(Key.Capital);
|
||||||
|
Add(Key.CapsLock);
|
||||||
|
Add(Key.HangulMode);
|
||||||
|
Add(Key.KanaMode);
|
||||||
|
Add(Key.JunjaMode);
|
||||||
|
Add(Key.FinalMode);
|
||||||
|
Add(Key.HanjaMode);
|
||||||
|
Add(Key.KanjiMode);
|
||||||
|
Add(Key.Escape);
|
||||||
|
Add(Key.ImeConvert);
|
||||||
|
Add(Key.ImeNonConvert);
|
||||||
|
Add(Key.ImeAccept);
|
||||||
|
Add(Key.ImeModeChange);
|
||||||
|
Add(Key.Space, ' ');
|
||||||
|
Add(Key.PageUp);
|
||||||
|
Add(Key.Prior);
|
||||||
|
Add(Key.Next);
|
||||||
|
Add(Key.PageDown);
|
||||||
|
Add(Key.End);
|
||||||
|
Add(Key.Home);
|
||||||
|
Add(Key.Left);
|
||||||
|
Add(Key.Up);
|
||||||
|
Add(Key.Right);
|
||||||
|
Add(Key.Down);
|
||||||
|
Add(Key.Select);
|
||||||
|
Add(Key.Print);
|
||||||
|
Add(Key.Execute);
|
||||||
|
Add(Key.PrintScreen);
|
||||||
|
Add(Key.Snapshot);
|
||||||
|
Add(Key.Insert);
|
||||||
|
Add(Key.Delete);
|
||||||
|
Add(Key.Help);
|
||||||
|
Add(Key.LWin);
|
||||||
|
Add(Key.RWin);
|
||||||
|
Add(Key.Apps);
|
||||||
|
Add(Key.Sleep);
|
||||||
|
Add(Key.D0, '0', '=', '}');
|
||||||
|
Add(Key.D1, '1', '!');
|
||||||
|
Add(Key.D2, '2', '"', '²');
|
||||||
|
Add(Key.D3, '3', '§', '³');
|
||||||
|
Add(Key.D4, '4', '$');
|
||||||
|
Add(Key.D5, '5', '%');
|
||||||
|
Add(Key.D6, '6', '&');
|
||||||
|
Add(Key.D7, '7', '/', '{');
|
||||||
|
Add(Key.D8, '8', '(', '[');
|
||||||
|
Add(Key.D9, '9', ')', ']');
|
||||||
|
Add(Key.NumPad0, '0');
|
||||||
|
Add(Key.NumPad1, '1');
|
||||||
|
Add(Key.NumPad2, '2');
|
||||||
|
Add(Key.NumPad3, '3');
|
||||||
|
Add(Key.NumPad4, '4');
|
||||||
|
Add(Key.NumPad5, '5');
|
||||||
|
Add(Key.NumPad6, '6');
|
||||||
|
Add(Key.NumPad7, '7');
|
||||||
|
Add(Key.NumPad8, '8');
|
||||||
|
Add(Key.NumPad9, '9');
|
||||||
|
Add(Key.Multiply, '*');
|
||||||
|
Add(Key.Add, '+');
|
||||||
|
Add(Key.Separator, '.');
|
||||||
|
Add(Key.Subtract, '-');
|
||||||
|
Add(Key.Decimal, ',');
|
||||||
|
Add(Key.Divide, '/');
|
||||||
|
Add(Key.F1);
|
||||||
|
Add(Key.F2);
|
||||||
|
Add(Key.F3);
|
||||||
|
Add(Key.F4);
|
||||||
|
Add(Key.F5);
|
||||||
|
Add(Key.F6);
|
||||||
|
Add(Key.F7);
|
||||||
|
Add(Key.F8);
|
||||||
|
Add(Key.F9);
|
||||||
|
Add(Key.F10);
|
||||||
|
Add(Key.F11);
|
||||||
|
Add(Key.F12);
|
||||||
|
Add(Key.F13);
|
||||||
|
Add(Key.F14);
|
||||||
|
Add(Key.F15);
|
||||||
|
Add(Key.F16);
|
||||||
|
Add(Key.F17);
|
||||||
|
Add(Key.F18);
|
||||||
|
Add(Key.F19);
|
||||||
|
Add(Key.F20);
|
||||||
|
Add(Key.F21);
|
||||||
|
Add(Key.F22);
|
||||||
|
Add(Key.F23);
|
||||||
|
Add(Key.F24);
|
||||||
|
Add(Key.NumLock);
|
||||||
|
Add(Key.Scroll);
|
||||||
|
Add(Key.LeftShift);
|
||||||
|
Add(Key.RightShift);
|
||||||
|
Add(Key.LeftCtrl);
|
||||||
|
Add(Key.RightCtrl);
|
||||||
|
Add(Key.LeftAlt);
|
||||||
|
Add(Key.RightAlt);
|
||||||
|
Add(Key.BrowserBack);
|
||||||
|
Add(Key.BrowserForward);
|
||||||
|
Add(Key.BrowserRefresh);
|
||||||
|
Add(Key.BrowserStop);
|
||||||
|
Add(Key.BrowserSearch);
|
||||||
|
Add(Key.BrowserFavorites);
|
||||||
|
Add(Key.BrowserHome);
|
||||||
|
Add(Key.VolumeMute);
|
||||||
|
Add(Key.VolumeDown);
|
||||||
|
Add(Key.VolumeUp);
|
||||||
|
Add(Key.MediaNextTrack);
|
||||||
|
Add(Key.MediaPreviousTrack);
|
||||||
|
Add(Key.MediaStop);
|
||||||
|
Add(Key.MediaPlayPause);
|
||||||
|
Add(Key.LaunchMail);
|
||||||
|
Add(Key.SelectMedia);
|
||||||
|
Add(Key.LaunchApplication1);
|
||||||
|
Add(Key.LaunchApplication2);
|
||||||
|
Add(Key.Oem1, 'ü', 'Ü');
|
||||||
|
Add(Key.OemSemicolon, ',', ';');
|
||||||
|
Add(Key.OemPlus, '+', '*', '~');
|
||||||
|
Add(Key.OemComma, ',', ';');
|
||||||
|
Add(Key.OemMinus, '-', '_');
|
||||||
|
Add(Key.OemPeriod, '.', ':');
|
||||||
|
Add(Key.Oem2);
|
||||||
|
Add(Key.OemQuestion, '#', '\'');
|
||||||
|
Add(Key.Oem3, 'ö', 'Ö');
|
||||||
|
Add(Key.OemTilde);
|
||||||
|
Add(Key.AbntC1);
|
||||||
|
Add(Key.AbntC2);
|
||||||
|
Add(Key.Oem4);
|
||||||
|
Add(Key.OemOpenBrackets);
|
||||||
|
Add(Key.Oem5, '^', '°');
|
||||||
|
Add(Key.OemPipe);
|
||||||
|
Add(Key.Oem6, '´', '`');
|
||||||
|
Add(Key.OemCloseBrackets);
|
||||||
|
Add(Key.Oem7, 'ß', '?');
|
||||||
|
Add(Key.OemQuotes, 'ä', 'Ä');
|
||||||
|
Add(Key.Oem8);
|
||||||
|
Add(Key.Oem102);
|
||||||
|
Add(Key.OemBackslash, '<', '>', '|');
|
||||||
|
Add(Key.ImeProcessed);
|
||||||
|
Add(Key.System);
|
||||||
|
Add(Key.DbeAlphanumeric);
|
||||||
|
Add(Key.OemAttn);
|
||||||
|
Add(Key.DbeKatakana);
|
||||||
|
Add(Key.OemFinish);
|
||||||
|
Add(Key.DbeHiragana);
|
||||||
|
Add(Key.OemCopy);
|
||||||
|
Add(Key.DbeSbcsChar);
|
||||||
|
Add(Key.OemAuto);
|
||||||
|
Add(Key.DbeDbcsChar);
|
||||||
|
Add(Key.OemEnlw);
|
||||||
|
Add(Key.DbeRoman);
|
||||||
|
Add(Key.OemBackTab);
|
||||||
|
Add(Key.Attn);
|
||||||
|
Add(Key.DbeNoRoman);
|
||||||
|
Add(Key.CrSel);
|
||||||
|
Add(Key.DbeEnterWordRegisterMode);
|
||||||
|
Add(Key.DbeEnterImeConfigureMode);
|
||||||
|
Add(Key.ExSel);
|
||||||
|
Add(Key.DbeFlushString);
|
||||||
|
Add(Key.EraseEof);
|
||||||
|
Add(Key.DbeCodeInput);
|
||||||
|
Add(Key.Play);
|
||||||
|
Add(Key.DbeNoCodeInput);
|
||||||
|
Add(Key.Zoom);
|
||||||
|
Add(Key.DbeDetermineString);
|
||||||
|
Add(Key.NoName);
|
||||||
|
Add(Key.DbeEnterDialogConversionMode);
|
||||||
|
Add(Key.Pa1);
|
||||||
|
Add(Key.OemClear);
|
||||||
|
Add(Key.DeadCharProcessed);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Add(Key key, params char[] chars)
|
||||||
|
{
|
||||||
|
char[] padded = new char[3];
|
||||||
|
for (int i = 0; i < chars.Length; i++)
|
||||||
|
padded[i] = chars[i];
|
||||||
|
if (_chars.ContainsKey(key) && _chars[key][0] > 0)
|
||||||
|
return;
|
||||||
|
_chars[key] = padded;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
public readonly record struct BufferPosition(int Line = 0, int Column = 0)
|
public readonly record struct BufferPosition(int Line = 0, int Column = 0)
|
||||||
{
|
{
|
||||||
public static readonly BufferPosition Void = new(0, 0);
|
public bool IsInitial => Line == 0 && Column == 0;
|
||||||
|
|
||||||
|
public static readonly BufferPosition Initial = new(0, 0);
|
||||||
public static readonly BufferPosition Left = new(0, -1);
|
public static readonly BufferPosition Left = new(0, -1);
|
||||||
public static readonly BufferPosition Right = new(0, 1);
|
public static readonly BufferPosition Right = new(0, 1);
|
||||||
public static readonly BufferPosition Up = new(-1, 0);
|
public static readonly BufferPosition Up = new(-1, 0);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
public struct BufferRegion(BufferPosition from, BufferPosition to)
|
public struct BufferRegion(BufferPosition from, BufferPosition to)
|
||||||
{
|
{
|
||||||
|
public static readonly BufferRegion Void = new BufferRegion(default, default);
|
||||||
|
|
||||||
|
public bool IsVoid => (From - To).IsInitial;
|
||||||
|
|
||||||
public BufferPosition From = from;
|
public BufferPosition From = from;
|
||||||
public BufferPosition To = to;
|
public BufferPosition To = to;
|
||||||
}
|
}
|
||||||
|
|
@ -1,123 +1,181 @@
|
||||||
using Qrakhen.Qamp.Editor.Commands;
|
using Qrakhen.Qamp.Editor.Commands;
|
||||||
using Qrakhen.Qamp.Editor.Primitives;
|
using Qrakhen.Qamp.Editor.Primitives;
|
||||||
using Qrakhen.Qamp.Memory;
|
using Qrakhen.Qamp.Memory;
|
||||||
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Windows.Documents;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
|
||||||
namespace Qrakhen.Qamp.Editor.ViewModel;
|
namespace Qrakhen.Qamp.Editor.ViewModel;
|
||||||
|
|
||||||
public class EditorFrameViewModel : ObservableObject
|
public class EditorFrameViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
private BufferPosition _cursorPosition;
|
private BufferPosition _cursorPosition;
|
||||||
public BufferPosition CursorPosition {
|
public BufferPosition CursorPosition {
|
||||||
get => _cursorPosition;
|
get => _cursorPosition;
|
||||||
set => SetProperty(ref _cursorPosition, value);
|
set => SetProperty(ref _cursorPosition, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int CursorLine => _cursorPosition.Line;
|
private BufferRegion _currentSelection;
|
||||||
|
public BufferRegion CurrentSelection {
|
||||||
|
get => _currentSelection;
|
||||||
|
set => SetProperty(ref _currentSelection, value);
|
||||||
|
}
|
||||||
|
|
||||||
public int CursorColumn => _cursorPosition.Column;
|
public int CursorLine => _cursorPosition.Line;
|
||||||
|
|
||||||
public LineBuffer CurrentLineBuffer => Lines[CursorLine];
|
public int CursorColumn => _cursorPosition.Column;
|
||||||
|
|
||||||
public ICommand MoveCaretCommand { get; }
|
public LineBuffer CurrentLineBuffer => Lines[CursorLine];
|
||||||
public ICommand MoveBlockCommand { get; }
|
|
||||||
public ICommand InsertCommand { get; }
|
|
||||||
public ICommand DeleteCommand { get; }
|
|
||||||
public ICommand ClipBoardCommand { get; }
|
|
||||||
|
|
||||||
public ObservableCollection<LineBuffer> Lines { get; private set; } = new() {
|
public ICommand MoveCaretCommand { get; }
|
||||||
new LineBuffer("test"),
|
public ICommand MoveBlockCommand { get; }
|
||||||
new LineBuffer(" is very good;"),
|
public ICommand InsertCommand { get; }
|
||||||
new LineBuffer(" jaja();"),
|
public ICommand DeleteCommand { get; }
|
||||||
new LineBuffer("}")
|
public ICommand ClipBoardCommand { get; }
|
||||||
};
|
|
||||||
|
|
||||||
public EditorFrameViewModel()
|
public ObservableCollection<LineBuffer> Lines { get; private set; } = new() {
|
||||||
{
|
new LineBuffer("test"),
|
||||||
MoveCaretCommand = new RelayCommand(ExecuteMoveCaret);
|
new LineBuffer(" is very good;"),
|
||||||
MoveBlockCommand = new RelayCommand(ExecuteMoveBlock);
|
new LineBuffer(" jaja();"),
|
||||||
InsertCommand = new RelayCommand(ExecuteInsert);
|
new LineBuffer("}")
|
||||||
DeleteCommand = new RelayCommand(ExecuteDelete);
|
};
|
||||||
ClipBoardCommand = new RelayCommand(() => { });
|
|
||||||
}
|
|
||||||
|
|
||||||
private BufferPosition AlignBufferPosition(BufferPosition position)
|
public EditorFrameViewModel()
|
||||||
{
|
{
|
||||||
int line = Math.Max(0, Math.Min(position.Line, Lines.Count - 1));
|
MoveCaretCommand = new RelayCommand(ExecuteMoveCaret);
|
||||||
LineBuffer buffer = Lines[line];
|
MoveBlockCommand = new RelayCommand(ExecuteMoveBlock);
|
||||||
int column = Math.Max(0, Math.Min(position.Column, buffer.Tail));
|
InsertCommand = new RelayCommand(ExecuteInsert);
|
||||||
return new BufferPosition(line, column);
|
DeleteCommand = new RelayCommand(ExecuteDelete);
|
||||||
}
|
ClipBoardCommand = new RelayCommand(() => { });
|
||||||
|
}
|
||||||
|
|
||||||
public void SetCursor(BufferPosition position)
|
|
||||||
{
|
|
||||||
CursorPosition = AlignBufferPosition(position);
|
|
||||||
OnPropertyChanged(nameof(CurrentLineBuffer));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void MoveCursor(BufferPosition relative)
|
public void SetCursor(int line, int column) => SetCursor(new BufferPosition(line, column));
|
||||||
{
|
public void SetCursor(BufferPosition position)
|
||||||
SetCursor(CursorPosition + relative);
|
{
|
||||||
OnPropertyChanged(nameof(CurrentLineBuffer));
|
CursorPosition = AlignBufferPosition(position);
|
||||||
}
|
OnPropertyChanged(nameof(CurrentLineBuffer));
|
||||||
|
}
|
||||||
|
|
||||||
public void Delete(BufferRegion region, bool follow = true)
|
public void MoveCursor(int lines, int columns) => MoveCursor(new BufferPosition(lines, columns));
|
||||||
{
|
public void MoveCursor(BufferPosition relative, bool jump = false)
|
||||||
if (follow)
|
{
|
||||||
SetCursor(new BufferPosition(region.From.Line, region.From.Column));
|
SetCursor(CursorPosition + relative);
|
||||||
for (int line = region.From.Line; line < region.To.Line; line++)
|
OnPropertyChanged(nameof(CurrentLineBuffer));
|
||||||
{
|
}
|
||||||
|
|
||||||
}
|
public void Delete(BufferRegion region, bool follow = true)
|
||||||
}
|
{
|
||||||
|
|
||||||
public void Delete(int count = 1, bool follow = true)
|
|
||||||
{
|
|
||||||
if (follow)
|
|
||||||
MoveCursor(new BufferPosition(0, -count));
|
|
||||||
CurrentLineBuffer.Delete(CursorColumn, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Insert(string data, bool follow = true)
|
|
||||||
{
|
|
||||||
CurrentLineBuffer.Insert(CursorColumn, data);
|
|
||||||
|
|
||||||
int newLine = data.IndexOf(Environment.NewLine);
|
|
||||||
if (newLine > -1) {
|
|
||||||
int position = CursorColumn - (data.Length - newLine + 1);
|
|
||||||
LineBuffer split = new LineBuffer(CurrentLineBuffer.Slice(position), CurrentLineBuffer.Encoding);
|
|
||||||
Lines.Insert(CursorLine, split);
|
|
||||||
SetCursor(new BufferPosition(CursorLine + 1, split.Tail));
|
|
||||||
} else {
|
|
||||||
if (follow)
|
if (follow)
|
||||||
MoveCursor(new BufferPosition(0, data.Length));
|
SetCursor(new BufferPosition(region.From.Line, region.From.Column));
|
||||||
}
|
for (int line = region.From.Line; line < region.To.Line; line++) {
|
||||||
}
|
|
||||||
|
|
||||||
private void ExecuteMoveCaret(object? parameter)
|
}
|
||||||
{
|
}
|
||||||
if (parameter is BufferPosition relative)
|
|
||||||
MoveCursor(relative);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExecuteMoveBlock(object? parameter)
|
public void Delete(int count = 1, bool follow = true)
|
||||||
{
|
{
|
||||||
if (parameter is BufferPosition relative)
|
if (count < 1)
|
||||||
MoveCursor(relative);
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
private void ExecuteInsert(object? parameter)
|
if (follow) {
|
||||||
{
|
// remove newline
|
||||||
if (parameter is string str)
|
if (CursorColumn - count < 0 && CursorLine > 0) {
|
||||||
Insert(str, true);
|
int position = Lines[CursorLine - 1].Tail;
|
||||||
}
|
Lines[CursorLine - 1].Insert(
|
||||||
|
position,
|
||||||
|
CurrentLineBuffer.Slice(0));
|
||||||
|
Lines.RemoveAt(CursorLine);
|
||||||
|
SetCursor(CursorLine - 1, position);
|
||||||
|
return;
|
||||||
|
} else
|
||||||
|
MoveCursor(0, -count);
|
||||||
|
} else {
|
||||||
|
if (CursorColumn + count >= CurrentLineBuffer.Tail &&
|
||||||
|
CursorLine < Lines.Count - 1) {
|
||||||
|
CurrentLineBuffer.Insert(
|
||||||
|
CurrentLineBuffer.Tail,
|
||||||
|
Lines[CursorLine + 1].Slice(0));
|
||||||
|
Lines.RemoveAt(CursorLine + 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ExecuteDelete(object? parameter)
|
CurrentLineBuffer.Delete(CursorColumn, count);
|
||||||
{
|
}
|
||||||
if (parameter is int count)
|
|
||||||
Delete(Math.Abs(count), count < 0);
|
/*
|
||||||
if (parameter is BufferRegion region)
|
|
||||||
Delete(region);
|
asdasdy
|
||||||
}
|
klöklöklökl
|
||||||
|
köäläälööx
|
||||||
|
cyxc
|
||||||
|
|
||||||
|
asdasd
|
||||||
|
|
||||||
|
* */
|
||||||
|
|
||||||
|
public void Insert(string data, bool follow = true)
|
||||||
|
{
|
||||||
|
data = data.Replace("\t", " ");
|
||||||
|
var lines = data.Split(Environment.NewLine, StringSplitOptions.None);
|
||||||
|
for (int i = 0; i < lines.Length; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
LineBuffer split = new LineBuffer(
|
||||||
|
CurrentLineBuffer.Slice(CursorColumn),
|
||||||
|
CurrentLineBuffer.Encoding);
|
||||||
|
Lines.Insert(CursorLine + 1, split);
|
||||||
|
SetCursor(new BufferPosition(CursorLine + 1, 0));
|
||||||
|
}
|
||||||
|
CurrentLineBuffer.Insert(CursorColumn, lines[i]);
|
||||||
|
if (follow)
|
||||||
|
MoveCursor(new BufferPosition(0, lines[i].Length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferPosition AlignBufferPosition(BufferPosition position)
|
||||||
|
{
|
||||||
|
int line = Math.Max(0, Math.Min(position.Line, Lines.Count - 1));
|
||||||
|
LineBuffer buffer = Lines[line];
|
||||||
|
int column = Math.Max(0, Math.Min(position.Column, buffer.Tail));
|
||||||
|
return new BufferPosition(line, column);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Commands
|
||||||
|
|
||||||
|
private void ExecuteMoveCaret(object? parameter)
|
||||||
|
{
|
||||||
|
if (parameter is MoveCaretOptions options) {
|
||||||
|
if (options.Relative)
|
||||||
|
MoveCursor(options.Position, options.Jump);
|
||||||
|
else
|
||||||
|
SetCursor(options.Position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteMoveBlock(object? parameter)
|
||||||
|
{
|
||||||
|
if (parameter is BufferPosition relative)
|
||||||
|
MoveCursor(relative);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteInsert(object? parameter)
|
||||||
|
{
|
||||||
|
if (parameter is string str)
|
||||||
|
Insert(str, true);
|
||||||
|
if (parameter is char c)
|
||||||
|
Insert(c + "", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteDelete(object? parameter)
|
||||||
|
{
|
||||||
|
if (parameter is int count)
|
||||||
|
Delete(Math.Abs(count), count < 0);
|
||||||
|
if (parameter is BufferRegion region)
|
||||||
|
Delete(region);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,33 @@ using System.Text;
|
||||||
|
|
||||||
namespace Qrakhen.Qamp.Memory;
|
namespace Qrakhen.Qamp.Memory;
|
||||||
|
|
||||||
public sealed class LineBuffer : TailBuffer<byte>, INotifyPropertyChanged
|
public struct BufferSpan
|
||||||
|
{
|
||||||
|
public static readonly BufferSpan None = new(0, 0);
|
||||||
|
|
||||||
|
public int Start { get; set; } = 0;
|
||||||
|
public int End { get; set; } = 0;
|
||||||
|
|
||||||
|
public bool HasSelection => Start - End != 0;
|
||||||
|
|
||||||
|
public BufferSpan(int start, int end)
|
||||||
|
{
|
||||||
|
Start = start;
|
||||||
|
End = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferSpan Synchronized<T>(TailBuffer<T> source)
|
||||||
|
{
|
||||||
|
return new BufferSpan(Start, Math.Min(End, source.Tail));
|
||||||
|
}
|
||||||
|
|
||||||
|
public T[] GetSelectedRegion<T>(TailBuffer<T> source)
|
||||||
|
{
|
||||||
|
return source.Data[Start..End];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LineBuffer : TailBuffer<byte>, INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
public Encoding Encoding { get; }
|
public Encoding Encoding { get; }
|
||||||
public string Cached { get; private set; } = "";
|
public string Cached { get; private set; } = "";
|
||||||
|
|
@ -32,6 +58,14 @@ public sealed class LineBuffer : TailBuffer<byte>, INotifyPropertyChanged
|
||||||
public void Insert(int cursor, string data)
|
public void Insert(int cursor, string data)
|
||||||
=> Insert(cursor, Encoding.GetBytes(data));
|
=> Insert(cursor, Encoding.GetBytes(data));
|
||||||
|
|
||||||
|
public string ToString(BufferSpan span)
|
||||||
|
{
|
||||||
|
var synced = span.Synchronized(this);
|
||||||
|
if (!synced.HasSelection)
|
||||||
|
return string.Empty;
|
||||||
|
return Encoding.GetString(Data[synced.Start..synced.End]);
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (Tail == 0 || Encoding == null)
|
if (Tail == 0 || Encoding == null)
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,12 @@ public abstract class TailBuffer<T> : ICloneable
|
||||||
|
|
||||||
public TailBuffer(int size = 0x80)
|
public TailBuffer(int size = 0x80)
|
||||||
{
|
{
|
||||||
_data = new T[size];
|
_data = new T[size];
|
||||||
}
|
}
|
||||||
|
|
||||||
public TailBuffer(T[] data)
|
public TailBuffer(T[] data)
|
||||||
{
|
{
|
||||||
_data = new T[data.Length * 2];
|
_data = new T[Math.Max(0x10, data.Length * 2)];
|
||||||
Array.Copy(data, 0, _data, 0, data.Length);
|
Array.Copy(data, 0, _data, 0, data.Length);
|
||||||
SetTail(data.Length);
|
SetTail(data.Length);
|
||||||
}
|
}
|
||||||
|
|
@ -54,20 +54,21 @@ public abstract class TailBuffer<T> : ICloneable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public T[] Slice(int position)
|
public T[] Slice(int from = 0, int until = -1)
|
||||||
{
|
{
|
||||||
position = SeekTail(position);
|
from = SeekTail(from);
|
||||||
if (position == Tail)
|
if (from == Tail)
|
||||||
return [];
|
return [];
|
||||||
T[] data = _data[position..Tail];
|
until = until < 0 ? Tail : SeekTail(until);
|
||||||
Delete(position, Tail - position);
|
T[] data = _data[from..until];
|
||||||
return data;
|
Delete(from, until - from);
|
||||||
}
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
public abstract object Clone();
|
public abstract object Clone();
|
||||||
|
|
||||||
// never write beyond tail (there might be invalid data)
|
// never write beyond tail (there might be invalid data)
|
||||||
private int SeekTail(int cursor) => cursor > Tail ? Tail : cursor;
|
private int SeekTail(int cursor) => Math.Max(0, Math.Min(cursor, Tail));
|
||||||
|
|
||||||
private void Prepare(int index, int count)
|
private void Prepare(int index, int count)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue