diff --git a/Qrakhen.Qamp.Editor/Commands/MoveCaretOptions.cs b/Qrakhen.Qamp.Editor/Commands/MoveCaretOptions.cs new file mode 100644 index 0000000..1e41038 --- /dev/null +++ b/Qrakhen.Qamp.Editor/Commands/MoveCaretOptions.cs @@ -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); diff --git a/Qrakhen.Qamp.Editor/Commands/RelayCommand.cs b/Qrakhen.Qamp.Editor/Commands/RelayCommand.cs index f218422..9f75f97 100644 --- a/Qrakhen.Qamp.Editor/Commands/RelayCommand.cs +++ b/Qrakhen.Qamp.Editor/Commands/RelayCommand.cs @@ -27,4 +27,4 @@ public class RelayCommand : ICommand public bool CanExecute(object? parameter) => _canExecute(parameter); public void Execute(object? parameter) => _execute(parameter); -} \ No newline at end of file +} diff --git a/Qrakhen.Qamp.Editor/Controls/Caret.cs b/Qrakhen.Qamp.Editor/Controls/Caret.cs index 99e424a..5ae77d9 100644 --- a/Qrakhen.Qamp.Editor/Controls/Caret.cs +++ b/Qrakhen.Qamp.Editor/Controls/Caret.cs @@ -12,7 +12,10 @@ public class Caret : Adorner private readonly Brush _brush; private bool _blink; 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) { @@ -20,13 +23,28 @@ public class Caret : Adorner _height = height; _brush = brush; - _pen = new Pen(brush, 1.5); - _timer = new DispatcherTimer(); - _timer.Interval = TimeSpan.FromMilliseconds(428); - _timer.Tick += new EventHandler((s, e) => { + _pen = new Pen(brush, 1.92); + + _blinkTimer = new DispatcherTimer(); + _blinkTimer.Interval = TimeSpan.FromMilliseconds(428); + _blinkTimer.Tick += new EventHandler((s, e) => { 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) @@ -34,13 +52,19 @@ public class Caret : Adorner _pen.Brush = hide ? Brushes.Transparent : _brush; } + public void UpdateSelection((Point from, Point to)[] lines) + { + _lines = lines; + InvalidateVisual(); + } + public void UpdatePosition(Point newPosition) { - _timer.Stop(); + _cursorPosition = _targetPosition = newPosition; Toggle(_blink = false); - _cursorPosition = newPosition; + //_blinkTimer.Stop(); + //_moveTimer.Start(); InvalidateVisual(); - _timer.Start(); } protected override void OnRender(DrawingContext drawingContext) diff --git a/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml b/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml index d2641cf..ff6ac05 100644 --- a/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml +++ b/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml @@ -12,6 +12,7 @@ diff --git a/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml.cs b/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml.cs index 8e8680e..a294dbc 100644 --- a/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml.cs +++ b/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml.cs @@ -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.Memory; using System.Windows; @@ -6,6 +8,7 @@ using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; +using System.Windows.Shapes; namespace Qrakhen.Qamp.Editor.Controls; @@ -18,8 +21,22 @@ public partial class EditorFrame : UserControl InitializeComponent(); Loaded += OnLoaded; 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) { if (DataContext is EditorFrameViewModel vm) { @@ -30,8 +47,12 @@ public partial class EditorFrame : UserControl Key.Down => new(1, 0), _ => new(0, 0) }; - if (position != BufferPosition.Void) - vm.MoveCaretCommand.Execute(position); + if (position != BufferPosition.Initial) + vm.MoveCaretCommand.Execute( + new MoveCaretOptions( + position, + true, + InputService.CtrlHeld)); else if (e.Key == Key.Back) vm.DeleteCommand.Execute(-1); else if (e.Key == Key.Delete) @@ -41,9 +62,11 @@ public partial class EditorFrame : UserControl else if (e.Key == Key.Delete) vm.DeleteCommand.Execute(1); else { - string c = e.Key.ToString(); - if (c.Length == 1) - vm.InsertCommand.Execute(c); + char c = KeyHelper.Get(e.Key, + InputService.ShiftHeld ? 1 : + (InputService.CtrlHeld && InputService.AltHeld) ? 2 : 0); + if (c != 0) + vm.InsertCommand.Execute(c); } e.Handled = true; @@ -63,7 +86,13 @@ public partial class EditorFrame : UserControl 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); return new Point(ft.Width + scrollX + 2, line * TextHelper.LineHeight + scrollY); @@ -74,7 +103,12 @@ public partial class EditorFrame : UserControl 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 charIndex = CursorPosition.Column; @@ -88,6 +122,10 @@ public partial class EditorFrame : UserControl charIndex, scrollY, scrollX)); + + if (!CurrentSelection.IsVoid) { + + } } public LineBuffer CurrentLineBuffer { @@ -112,11 +150,29 @@ public partial class EditorFrame : UserControl nameof(CursorPosition), typeof(BufferPosition), typeof(EditorFrame), - new PropertyMetadata(BufferPosition.Void, OnCursorPositionChanged)); + new PropertyMetadata(BufferPosition.Initial, OnCursorPositionChanged)); private static void OnCursorPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var frame = (EditorFrame)d; 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(); + } } diff --git a/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml b/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml index b3c0f66..c1a3fdc 100644 --- a/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml +++ b/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml @@ -7,6 +7,5 @@ mc:Ignorable="d" d:DesignHeight="18" d:DesignWidth="420"> - diff --git a/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml.cs b/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml.cs index 2e09e37..3a3d474 100644 --- a/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml.cs +++ b/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml.cs @@ -9,71 +9,114 @@ using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; - namespace Qrakhen.Qamp.Editor.Controls; 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( - nameof(LineBuffer), - typeof(LineBuffer), - typeof(LineRenderer), + nameof(LineBuffer), + typeof(LineBuffer), + typeof(LineRenderer), new FrameworkPropertyMetadata(null, OnLineBufferChanged)); - public LineBuffer LineBuffer { - get => (LineBuffer)GetValue(LineBufferProperty); - set => SetValue(LineBufferProperty, value); - } + public LineBuffer LineBuffer { + get => (LineBuffer)GetValue(LineBufferProperty); + set => SetValue(LineBufferProperty, value); + } - private readonly Typeface _typeface = new(new FontFamily("Consolas"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal); - private const double FontSize = 14; - private const double LineHeight = 18; + private static void OnLineBufferChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is LineRenderer renderer) { + if (e.OldValue is LineBuffer oldBuffer && oldBuffer != null) { + oldBuffer.PropertyChanged -= renderer.OnLineBufferPropertyChanged; + } - public LineRenderer() - { - Height = LineHeight; - SnapsToDevicePixels = true; - } + if (e.NewValue is LineBuffer newBuffer && newBuffer != null) { + newBuffer.PropertyChanged += renderer.OnLineBufferPropertyChanged; + } - protected override void OnRender(DrawingContext drawingContext) - { - base.OnRender(drawingContext); + renderer.InvalidateVisual(); + } + } - if (string.IsNullOrEmpty(LineBuffer.Cached)) - return; + private void OnLineBufferPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + 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) - { - if (d is LineRenderer renderer) { - if (e.OldValue is LineBuffer oldBuffer && oldBuffer != null) { - oldBuffer.PropertyChanged -= renderer.OnLineBufferPropertyChanged; - } + public static readonly DependencyProperty SelectionProperty = + DependencyProperty.Register( + nameof(Selection), + typeof(BufferSpan), + typeof(LineRenderer), + new FrameworkPropertyMetadata(BufferSpan.None, OnSelectionChanged)); - if (e.NewValue is LineBuffer newBuffer && newBuffer != null) { - newBuffer.PropertyChanged += renderer.OnLineBufferPropertyChanged; - } + public BufferSpan Selection { + 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(); - } } diff --git a/Qrakhen.Qamp.Editor/InputService.cs b/Qrakhen.Qamp.Editor/InputService.cs index 4d56a2d..baed6ad 100644 --- a/Qrakhen.Qamp.Editor/InputService.cs +++ b/Qrakhen.Qamp.Editor/InputService.cs @@ -1,9 +1,19 @@ using System; +using System.CodeDom.Compiler; using System.Collections.Generic; +using System.Drawing.Drawing2D; +using System.Runtime.ConstrainedExecution; +using System.Security.Policy; using System.Text; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; 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 { @@ -34,7 +44,7 @@ public class InputService : IInputHandler public static bool LeftShiftHeld => Keyboard.IsKeyDown(Key.LeftShift); public static bool LeftAltHeld => Keyboard.IsKeyDown(Key.LeftAlt); 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 CtrlHeld => LeftCtrlHeld || RightCtrlHeld; public static bool ShiftHeld => LeftShiftHeld || RightShiftHeld; @@ -44,4 +54,212 @@ public class InputService : IInputHandler { } +} + +public static class KeyHelper +{ + private static readonly Dictionary _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; + } } \ No newline at end of file diff --git a/Qrakhen.Qamp.Editor/Primitives/BufferPosition.cs b/Qrakhen.Qamp.Editor/Primitives/BufferPosition.cs index b8547af..d7f4686 100644 --- a/Qrakhen.Qamp.Editor/Primitives/BufferPosition.cs +++ b/Qrakhen.Qamp.Editor/Primitives/BufferPosition.cs @@ -2,7 +2,9 @@ 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 Right = new(0, 1); public static readonly BufferPosition Up = new(-1, 0); diff --git a/Qrakhen.Qamp.Editor/Primitives/BufferRegion.cs b/Qrakhen.Qamp.Editor/Primitives/BufferRegion.cs index 255c26b..c3aeb86 100644 --- a/Qrakhen.Qamp.Editor/Primitives/BufferRegion.cs +++ b/Qrakhen.Qamp.Editor/Primitives/BufferRegion.cs @@ -2,6 +2,10 @@ 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 To = to; } \ No newline at end of file diff --git a/Qrakhen.Qamp.Editor/ViewModel/EditorFrameViewModel.cs b/Qrakhen.Qamp.Editor/ViewModel/EditorFrameViewModel.cs index 75985ad..040e210 100644 --- a/Qrakhen.Qamp.Editor/ViewModel/EditorFrameViewModel.cs +++ b/Qrakhen.Qamp.Editor/ViewModel/EditorFrameViewModel.cs @@ -1,123 +1,181 @@ using Qrakhen.Qamp.Editor.Commands; using Qrakhen.Qamp.Editor.Primitives; using Qrakhen.Qamp.Memory; +using System; using System.Collections.ObjectModel; +using System.Windows.Documents; using System.Windows.Input; namespace Qrakhen.Qamp.Editor.ViewModel; public class EditorFrameViewModel : ObservableObject { - private BufferPosition _cursorPosition; - public BufferPosition CursorPosition { + private BufferPosition _cursorPosition; + public BufferPosition CursorPosition { get => _cursorPosition; 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 ICommand MoveBlockCommand { get; } - public ICommand InsertCommand { get; } - public ICommand DeleteCommand { get; } - public ICommand ClipBoardCommand { get; } + public LineBuffer CurrentLineBuffer => Lines[CursorLine]; - public ObservableCollection Lines { get; private set; } = new() { - new LineBuffer("test"), - new LineBuffer(" is very good;"), - new LineBuffer(" jaja();"), - new LineBuffer("}") - }; + public ICommand MoveCaretCommand { get; } + public ICommand MoveBlockCommand { get; } + public ICommand InsertCommand { get; } + public ICommand DeleteCommand { get; } + public ICommand ClipBoardCommand { get; } - public EditorFrameViewModel() - { - MoveCaretCommand = new RelayCommand(ExecuteMoveCaret); - MoveBlockCommand = new RelayCommand(ExecuteMoveBlock); - InsertCommand = new RelayCommand(ExecuteInsert); - DeleteCommand = new RelayCommand(ExecuteDelete); - ClipBoardCommand = new RelayCommand(() => { }); - } + public ObservableCollection Lines { get; private set; } = new() { + new LineBuffer("test"), + new LineBuffer(" is very good;"), + new LineBuffer(" jaja();"), + new LineBuffer("}") + }; - 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); - } + public EditorFrameViewModel() + { + MoveCaretCommand = new RelayCommand(ExecuteMoveCaret); + MoveBlockCommand = new RelayCommand(ExecuteMoveBlock); + InsertCommand = new RelayCommand(ExecuteInsert); + DeleteCommand = new RelayCommand(ExecuteDelete); + ClipBoardCommand = new RelayCommand(() => { }); + } - public void SetCursor(BufferPosition position) - { - CursorPosition = AlignBufferPosition(position); - OnPropertyChanged(nameof(CurrentLineBuffer)); - } + - public void MoveCursor(BufferPosition relative) - { - SetCursor(CursorPosition + relative); - OnPropertyChanged(nameof(CurrentLineBuffer)); - } + public void SetCursor(int line, int column) => SetCursor(new BufferPosition(line, column)); + public void SetCursor(BufferPosition position) + { + CursorPosition = AlignBufferPosition(position); + OnPropertyChanged(nameof(CurrentLineBuffer)); + } - public void Delete(BufferRegion region, bool follow = true) - { - if (follow) - SetCursor(new BufferPosition(region.From.Line, region.From.Column)); - for (int line = region.From.Line; line < region.To.Line; line++) - { + public void MoveCursor(int lines, int columns) => MoveCursor(new BufferPosition(lines, columns)); + public void MoveCursor(BufferPosition relative, bool jump = false) + { + SetCursor(CursorPosition + relative); + OnPropertyChanged(nameof(CurrentLineBuffer)); + } - } - } - - 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 { + public void Delete(BufferRegion region, bool follow = true) + { 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) - { - if (parameter is BufferPosition relative) - MoveCursor(relative); - } + public void Delete(int count = 1, bool follow = true) + { + if (count < 1) + return; - private void ExecuteInsert(object? parameter) - { - if (parameter is string str) - Insert(str, true); - } + if (follow) { + // remove newline + if (CursorColumn - count < 0 && CursorLine > 0) { + 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) - { - if (parameter is int count) - Delete(Math.Abs(count), count < 0); - if (parameter is BufferRegion region) - Delete(region); - } + CurrentLineBuffer.Delete(CursorColumn, count); + } + + /* + + 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 } diff --git a/Qrakhen.Qamp.Memory/LineBuffer.cs b/Qrakhen.Qamp.Memory/LineBuffer.cs index c5adb88..8f8af5c 100644 --- a/Qrakhen.Qamp.Memory/LineBuffer.cs +++ b/Qrakhen.Qamp.Memory/LineBuffer.cs @@ -3,7 +3,33 @@ using System.Text; namespace Qrakhen.Qamp.Memory; -public sealed class LineBuffer : TailBuffer, 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(TailBuffer source) + { + return new BufferSpan(Start, Math.Min(End, source.Tail)); + } + + public T[] GetSelectedRegion(TailBuffer source) + { + return source.Data[Start..End]; + } +} + +public class LineBuffer : TailBuffer, INotifyPropertyChanged { public Encoding Encoding { get; } public string Cached { get; private set; } = ""; @@ -32,6 +58,14 @@ public sealed class LineBuffer : TailBuffer, INotifyPropertyChanged public void Insert(int cursor, string 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() { if (Tail == 0 || Encoding == null) diff --git a/Qrakhen.Qamp.Memory/TailBuffer.cs b/Qrakhen.Qamp.Memory/TailBuffer.cs index e9a8532..1529cf0 100644 --- a/Qrakhen.Qamp.Memory/TailBuffer.cs +++ b/Qrakhen.Qamp.Memory/TailBuffer.cs @@ -13,12 +13,12 @@ public abstract class TailBuffer : ICloneable public TailBuffer(int size = 0x80) { - _data = new T[size]; + _data = new T[size]; } 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); SetTail(data.Length); } @@ -54,20 +54,21 @@ public abstract class TailBuffer : ICloneable } } - public T[] Slice(int position) - { - position = SeekTail(position); - if (position == Tail) - return []; - T[] data = _data[position..Tail]; - Delete(position, Tail - position); - return data; - } + public T[] Slice(int from = 0, int until = -1) + { + from = SeekTail(from); + if (from == Tail) + return []; + until = until < 0 ? Tail : SeekTail(until); + T[] data = _data[from..until]; + Delete(from, until - from); + return data; + } public abstract object Clone(); // 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) {