diff --git a/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml.cs b/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml.cs index a4701c3..8e8680e 100644 --- a/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml.cs +++ b/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml.cs @@ -6,7 +6,6 @@ using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; -using System.Windows.Threading; namespace Qrakhen.Qamp.Editor.Controls; @@ -21,34 +20,35 @@ public partial class EditorFrame : UserControl PreviewKeyDown += OnKeyDown; } - private void OnKeyDown(object sender, KeyEventArgs e) - { - if (DataContext is EditorFrameViewModel vm) { - CursorPosition position = e.Key switch { - Key.Right => new(0, 1), - Key.Left => new(0, -1), - Key.Up => new(-1, 0), - Key.Down => new(1, 0), - _ => new(0, 0) - }; - if (position != CursorPosition.Void && - vm.MoveCaretCommand.CanExecute(position)) - { - vm.MoveCaretCommand.Execute(position); - } else { - if (e.Key == Key.Back) { - vm.Delete(1); - } else { - string c = e.Key.ToString(); - if (c.Length == 1) { - vm.Insert(c); - } - } - } - - e.Handled = true; + private void OnKeyDown(object sender, KeyEventArgs e) + { + if (DataContext is EditorFrameViewModel vm) { + BufferPosition position = e.Key switch { + Key.Right => new(0, 1), + Key.Left => new(0, -1), + Key.Up => new(-1, 0), + Key.Down => new(1, 0), + _ => new(0, 0) + }; + if (position != BufferPosition.Void) + vm.MoveCaretCommand.Execute(position); + else if (e.Key == Key.Back) + vm.DeleteCommand.Execute(-1); + else if (e.Key == Key.Delete) + vm.DeleteCommand.Execute(1); + else if (e.Key == Key.Enter) + vm.InsertCommand.Execute(Environment.NewLine); + else if (e.Key == Key.Delete) + vm.DeleteCommand.Execute(1); + else { + string c = e.Key.ToString(); + if (c.Length == 1) + vm.InsertCommand.Execute(c); } + + e.Handled = true; } + } private void OnLoaded(object sender, RoutedEventArgs e) { @@ -102,17 +102,17 @@ public partial class EditorFrame : UserControl typeof(EditorFrame), new PropertyMetadata(null)); - public CursorPosition CursorPosition { - get => (CursorPosition)GetValue(CursorPositionProperty); + public BufferPosition CursorPosition { + get => (BufferPosition)GetValue(CursorPositionProperty); set => SetValue(CursorPositionProperty, value); } public static readonly DependencyProperty CursorPositionProperty = DependencyProperty.Register( nameof(CursorPosition), - typeof(CursorPosition), + typeof(BufferPosition), typeof(EditorFrame), - new PropertyMetadata(CursorPosition.Void, OnCursorPositionChanged)); + new PropertyMetadata(BufferPosition.Void, OnCursorPositionChanged)); private static void OnCursorPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { diff --git a/Qrakhen.Qamp.Editor/InputService.cs b/Qrakhen.Qamp.Editor/InputService.cs new file mode 100644 index 0000000..4d56a2d --- /dev/null +++ b/Qrakhen.Qamp.Editor/InputService.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Input; + +namespace Qrakhen.Qamp.Editor.InputService; + +public interface IInputHandler +{ + +} + +public enum ActionId +{ + None, + Undo, + Redo, + Copy, + Paste, + Delete, + MoveUp, + MoveDown, + MoveRight, + MoveLeft, + Submit, + Close +} + +public delegate void KeyEventListener(); + +public class InputService : IInputHandler +{ + public static bool LeftCtrlHeld => Keyboard.IsKeyDown(Key.LeftCtrl); + 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 RightAltHeld => Keyboard.IsKeyDown(Key.RightAlt); + public static bool CtrlHeld => LeftCtrlHeld || RightCtrlHeld; + public static bool ShiftHeld => LeftShiftHeld || RightShiftHeld; + public static bool AltHeld => LeftAltHeld || RightAltHeld; + + public static void AddKeyListener(Key key, params Key[] modifiers) + { + + } +} \ No newline at end of file diff --git a/Qrakhen.Qamp.Editor/Primitives/BufferPosition.cs b/Qrakhen.Qamp.Editor/Primitives/BufferPosition.cs new file mode 100644 index 0000000..b8547af --- /dev/null +++ b/Qrakhen.Qamp.Editor/Primitives/BufferPosition.cs @@ -0,0 +1,19 @@ +namespace Qrakhen.Qamp.Editor.Primitives; + +public readonly record struct BufferPosition(int Line = 0, int Column = 0) +{ + public static readonly BufferPosition Void = 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); + public static readonly BufferPosition Down = new(1, 0); + + public static BufferPosition operator +(BufferPosition left, BufferPosition right) + => new(left.Line + right.Line, left.Column + right.Column); + + public static BufferPosition operator -(BufferPosition left, BufferPosition right) + => new(left.Line - right.Line, left.Column - right.Column); + + public static BufferPosition operator *(BufferPosition left, int factor) + => new(left.Line * factor, left.Column * factor); +} diff --git a/Qrakhen.Qamp.Editor/Primitives/BufferRegion.cs b/Qrakhen.Qamp.Editor/Primitives/BufferRegion.cs new file mode 100644 index 0000000..255c26b --- /dev/null +++ b/Qrakhen.Qamp.Editor/Primitives/BufferRegion.cs @@ -0,0 +1,7 @@ +namespace Qrakhen.Qamp.Editor.Primitives; + +public struct BufferRegion(BufferPosition from, BufferPosition to) +{ + public BufferPosition From = from; + public BufferPosition To = to; +} \ No newline at end of file diff --git a/Qrakhen.Qamp.Editor/Primitives/CursorPosition.cs b/Qrakhen.Qamp.Editor/Primitives/CursorPosition.cs deleted file mode 100644 index 4dce632..0000000 --- a/Qrakhen.Qamp.Editor/Primitives/CursorPosition.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Qrakhen.Qamp.Editor.Primitives; - -public record struct CursorPosition(int line = 0, int column = 0) -{ - public static readonly CursorPosition Void = new(0, 0); - public static readonly CursorPosition Left = new(0, -1); - public static readonly CursorPosition Right = new(0, 1); - public static readonly CursorPosition Up = new(-1, 0); - public static readonly CursorPosition Down = new(1, 0); - - public int Line = line; - public int Column = column; - - public static CursorPosition operator +(CursorPosition left, CursorPosition right) - => new(left.Line + right.Line, left.Column + right.Column); - - public static CursorPosition operator -(CursorPosition left, CursorPosition right) - => new(left.Line - right.Line, left.Column - right.Column); - - public static CursorPosition operator *(CursorPosition left, int factor) - => new(left.Line * factor, left.Column * factor); -} diff --git a/Qrakhen.Qamp.Editor/Primitives/CursorRegion.cs b/Qrakhen.Qamp.Editor/Primitives/CursorRegion.cs deleted file mode 100644 index ea70623..0000000 --- a/Qrakhen.Qamp.Editor/Primitives/CursorRegion.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Qrakhen.Qamp.Editor.Primitives; - -public struct CursorRegion(CursorPosition from, CursorPosition to) -{ - public CursorPosition From = from; - public CursorPosition 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 cf4d346..75985ad 100644 --- a/Qrakhen.Qamp.Editor/ViewModel/EditorFrameViewModel.cs +++ b/Qrakhen.Qamp.Editor/ViewModel/EditorFrameViewModel.cs @@ -8,76 +8,116 @@ namespace Qrakhen.Qamp.Editor.ViewModel; public class EditorFrameViewModel : ObservableObject { - private CursorPosition _cursorPosition; - public CursorPosition CursorPosition { - get => _cursorPosition; - set => SetProperty(ref _cursorPosition, value); - } + private BufferPosition _cursorPosition; + public BufferPosition CursorPosition { + get => _cursorPosition; + set => SetProperty(ref _cursorPosition, value); + } - public int CursorLine { - get => _cursorPosition.Line; - set => SetProperty(ref _cursorPosition.Line, value); - } + public int CursorLine => _cursorPosition.Line; - public int CursorColumn { - get => _cursorPosition.Column; - set => SetProperty(ref _cursorPosition.Column, value); - } + public int CursorColumn => _cursorPosition.Column; - public LineBuffer CurrentLineBuffer => Lines[CursorLine]; + public LineBuffer CurrentLineBuffer => Lines[CursorLine]; - public ICommand MoveCaretCommand { get; } + public ICommand MoveCaretCommand { get; } + public ICommand MoveBlockCommand { get; } + public ICommand InsertCommand { get; } + public ICommand DeleteCommand { get; } + public ICommand ClipBoardCommand { get; } - public ObservableCollection Lines { get; private set; } = new() { + public ObservableCollection Lines { get; private set; } = new() { new LineBuffer("test"), new LineBuffer(" is very good;"), new LineBuffer(" jaja();"), new LineBuffer("}") }; - public EditorFrameViewModel() - { - MoveCaretCommand = new RelayCommand(ExecuteMoveCaret, CanMoveCaret); - } + public EditorFrameViewModel() + { + MoveCaretCommand = new RelayCommand(ExecuteMoveCaret); + MoveBlockCommand = new RelayCommand(ExecuteMoveBlock); + InsertCommand = new RelayCommand(ExecuteInsert); + DeleteCommand = new RelayCommand(ExecuteDelete); + ClipBoardCommand = new RelayCommand(() => { }); + } - public void SetCursor(int line, int column) - { - line = line < 0 ? 0 : line >= Lines.Count ? Lines.Count - 1 : line; - LineBuffer buffer = Lines[line]; - column = column < 0 ? 0 : column > buffer.Tail ? buffer.Tail : column; - CursorPosition = new(line, column); - } + 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 void MoveCursor(int lines, int columns) - { - SetCursor(CursorLine + lines, CursorColumn + columns); - OnPropertyChanged(nameof(CurrentLineBuffer)); - } + public void SetCursor(BufferPosition position) + { + CursorPosition = AlignBufferPosition(position); + OnPropertyChanged(nameof(CurrentLineBuffer)); + } - public bool CanMoveCaret(object? relativeUpdate) - { - return true; - } + public void MoveCursor(BufferPosition relative) + { + SetCursor(CursorPosition + relative); + OnPropertyChanged(nameof(CurrentLineBuffer)); + } - public void ExecuteMoveCaret(object? relativeUpdate) + 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++) { - if (relativeUpdate is CursorPosition pos) - MoveCursor(pos.Line, pos.Column); - } - public void Delete(int count = 1, bool follow = true) - { - if (follow) - MoveCursor(0, -count); - CurrentLineBuffer.Delete(CursorColumn, count); - OnPropertyChanged(nameof(Lines)); } + } - public void Insert(string data, bool follow = true) - { - CurrentLineBuffer.Insert(CursorColumn, data); - if (follow) - MoveCursor(0, data.Length); - OnPropertyChanged(nameof(Lines)); + 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) + MoveCursor(new BufferPosition(0, data.Length)); } + } + + private void ExecuteMoveCaret(object? parameter) + { + if (parameter is BufferPosition relative) + MoveCursor(relative); + } + + 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); + } + + private void ExecuteDelete(object? parameter) + { + if (parameter is int count) + Delete(Math.Abs(count), count < 0); + if (parameter is BufferRegion region) + Delete(region); + } } diff --git a/Qrakhen.Qamp.Memory/LineBuffer.cs b/Qrakhen.Qamp.Memory/LineBuffer.cs index 6bb4e21..c5adb88 100644 --- a/Qrakhen.Qamp.Memory/LineBuffer.cs +++ b/Qrakhen.Qamp.Memory/LineBuffer.cs @@ -5,21 +5,22 @@ namespace Qrakhen.Qamp.Memory; public sealed class LineBuffer : TailBuffer, INotifyPropertyChanged { - public Encoding Encoding { get; } - public string Cached { get; private set; } = ""; - public event PropertyChangedEventHandler? PropertyChanged; + public Encoding Encoding { get; } + public string Cached { get; private set; } = ""; + public event PropertyChangedEventHandler? PropertyChanged; - public LineBuffer(Encoding encoding, int size = 0x80) : base(size) - { - Encoding = encoding; - } + public LineBuffer(Encoding encoding, int size = 0x80) : base(size) + { + Encoding = encoding; + } - public LineBuffer(string data) : this(data, Encoding.ASCII) { } - public LineBuffer(string data, Encoding encoding) : base(encoding.GetBytes(data)) - { - Encoding = encoding; - Cached = ToString(); - } + public LineBuffer(string data) : this(data, Encoding.ASCII) { } + public LineBuffer(string data, Encoding encoding) : this(encoding.GetBytes(data), encoding) { } + public LineBuffer(byte[] data, Encoding encoding) : base(data) + { + Encoding = encoding; + Cached = ToString(); + } protected override void SetTail(int tail = -1) { @@ -38,5 +39,10 @@ public sealed class LineBuffer : TailBuffer, INotifyPropertyChanged return Encoding.GetString(Data, 0, Tail); } + public override object Clone() + { + return new LineBuffer(_data.ToArray(), Encoding); + } + public static implicit operator LineBuffer(string s) => new LineBuffer(s); } diff --git a/Qrakhen.Qamp.Memory/TailBuffer.cs b/Qrakhen.Qamp.Memory/TailBuffer.cs index 0acb6f9..e9a8532 100644 --- a/Qrakhen.Qamp.Memory/TailBuffer.cs +++ b/Qrakhen.Qamp.Memory/TailBuffer.cs @@ -1,9 +1,9 @@ namespace Qrakhen.Qamp.Memory; // add to qrakhen.memory package for clout -public abstract class TailBuffer +public abstract class TailBuffer : ICloneable { - private T[] _data; + protected T[] _data; public T[] Data { get => _data; } @@ -54,6 +54,18 @@ public abstract class TailBuffer } } + public T[] Slice(int position) + { + position = SeekTail(position); + if (position == Tail) + return []; + T[] data = _data[position..Tail]; + Delete(position, Tail - position); + return data; + } + + public abstract object Clone(); + // never write beyond tail (there might be invalid data) private int SeekTail(int cursor) => cursor > Tail ? Tail : cursor;