From c2f9b93b46f686254742f0a24811b63cda09a01a Mon Sep 17 00:00:00 2001 From: Qrakhen Date: Wed, 19 Nov 2025 19:01:38 +0100 Subject: [PATCH] split even more and begin writing our own text editor even --- Qrakhen.Qamp.Editor/App.xaml | 8 ++ Qrakhen.Qamp.Editor/App.xaml.cs | 20 +++++ Qrakhen.Qamp.Editor/AssemblyInfo.cs | 10 +++ Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml | 30 +++++++ .../Controls/EditorFrame.xaml.cs | 22 +++++ .../Controls/LineRenderer.xaml | 12 +++ .../Controls/LineRenderer.xaml.cs | 60 ++++++++++++++ Qrakhen.Qamp.Editor/MainWindow.xaml | 30 +++++++ Qrakhen.Qamp.Editor/MainWindow.xaml.cs | 11 +++ .../Qrakhen.Qamp.Editor.csproj | 19 +++++ .../ViewModel/MainViewModel.cs | 45 +++++++++++ Qrakhen.Qamp.Memory/LineBuffer.cs | 39 +++++++++ .../Qrakhen.Qamp.Memory.csproj | 9 +++ Qrakhen.Qamp.Memory/Range.cs | 3 + Qrakhen.Qamp.Memory/TailBuffer.cs | 80 +++++++++++++++++++ Qrakhen.Qamp.Tests/Qrakhen.Qamp.Tests.csproj | 23 ++++++ Qrakhen.Qamp.Tests/UnitTest1.cs | 10 +++ Qrakhen.Qamp.sln | 34 +++++++- 18 files changed, 463 insertions(+), 2 deletions(-) create mode 100644 Qrakhen.Qamp.Editor/App.xaml create mode 100644 Qrakhen.Qamp.Editor/App.xaml.cs create mode 100644 Qrakhen.Qamp.Editor/AssemblyInfo.cs create mode 100644 Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml create mode 100644 Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml.cs create mode 100644 Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml create mode 100644 Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml.cs create mode 100644 Qrakhen.Qamp.Editor/MainWindow.xaml create mode 100644 Qrakhen.Qamp.Editor/MainWindow.xaml.cs create mode 100644 Qrakhen.Qamp.Editor/Qrakhen.Qamp.Editor.csproj create mode 100644 Qrakhen.Qamp.Editor/ViewModel/MainViewModel.cs create mode 100644 Qrakhen.Qamp.Memory/LineBuffer.cs create mode 100644 Qrakhen.Qamp.Memory/Qrakhen.Qamp.Memory.csproj create mode 100644 Qrakhen.Qamp.Memory/Range.cs create mode 100644 Qrakhen.Qamp.Memory/TailBuffer.cs create mode 100644 Qrakhen.Qamp.Tests/Qrakhen.Qamp.Tests.csproj create mode 100644 Qrakhen.Qamp.Tests/UnitTest1.cs diff --git a/Qrakhen.Qamp.Editor/App.xaml b/Qrakhen.Qamp.Editor/App.xaml new file mode 100644 index 0000000..b3cfd06 --- /dev/null +++ b/Qrakhen.Qamp.Editor/App.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/Qrakhen.Qamp.Editor/App.xaml.cs b/Qrakhen.Qamp.Editor/App.xaml.cs new file mode 100644 index 0000000..6046470 --- /dev/null +++ b/Qrakhen.Qamp.Editor/App.xaml.cs @@ -0,0 +1,20 @@ +using System.Configuration; +using System.Data; +using System.Windows; + +namespace Qrakhen.Qamp.Editor; +/// +/// Interaction logic for App.xaml +/// +public partial class App : Application +{ + protected override void OnStartup(StartupEventArgs e) + { + MainWindow window = new MainWindow() { + DataContext = new ViewModel.FileViewModel() + }; + + window.Show(); + } +} + diff --git a/Qrakhen.Qamp.Editor/AssemblyInfo.cs b/Qrakhen.Qamp.Editor/AssemblyInfo.cs new file mode 100644 index 0000000..b0ec827 --- /dev/null +++ b/Qrakhen.Qamp.Editor/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml b/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml new file mode 100644 index 0000000..b4cd9f4 --- /dev/null +++ b/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml.cs b/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml.cs new file mode 100644 index 0000000..6b93768 --- /dev/null +++ b/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +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 EditorFrame : UserControl +{ + public EditorFrame() + { + InitializeComponent(); + } +} diff --git a/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml b/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml new file mode 100644 index 0000000..dcdce90 --- /dev/null +++ b/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml.cs b/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml.cs new file mode 100644 index 0000000..3fa25c2 --- /dev/null +++ b/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml.cs @@ -0,0 +1,60 @@ +using Qrakhen.Qamp.Memory; +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +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 = + DependencyProperty.Register( + nameof(LineBuffer), + typeof(LineBuffer), + typeof(LineRenderer), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender)); + + 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; + + public LineRenderer() + { + Height = LineHeight; + SnapsToDevicePixels = true; + } + + protected override void OnRender(DrawingContext drawingContext) + { + base.OnRender(drawingContext); + + if (string.IsNullOrEmpty(LineBuffer.Cached)) + return; + + var formattedText = new FormattedText( + LineBuffer.Cached, + System.Globalization.CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + _typeface, + FontSize, + Brushes.White, + VisualTreeHelper.GetDpi(this).PixelsPerDip + ); + + drawingContext.DrawText(formattedText, new Point(2, 0)); + } +} diff --git a/Qrakhen.Qamp.Editor/MainWindow.xaml b/Qrakhen.Qamp.Editor/MainWindow.xaml new file mode 100644 index 0000000..87cee02 --- /dev/null +++ b/Qrakhen.Qamp.Editor/MainWindow.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/Qrakhen.Qamp.Editor/MainWindow.xaml.cs b/Qrakhen.Qamp.Editor/MainWindow.xaml.cs new file mode 100644 index 0000000..9296620 --- /dev/null +++ b/Qrakhen.Qamp.Editor/MainWindow.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows; + +namespace Qrakhen.Qamp.Editor; + +public partial class MainWindow : Window +{ + public MainWindow() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/Qrakhen.Qamp.Editor/Qrakhen.Qamp.Editor.csproj b/Qrakhen.Qamp.Editor/Qrakhen.Qamp.Editor.csproj new file mode 100644 index 0000000..e9a980a --- /dev/null +++ b/Qrakhen.Qamp.Editor/Qrakhen.Qamp.Editor.csproj @@ -0,0 +1,19 @@ + + + + WinExe + net10.0-windows + enable + enable + true + + + + + + + + + + + diff --git a/Qrakhen.Qamp.Editor/ViewModel/MainViewModel.cs b/Qrakhen.Qamp.Editor/ViewModel/MainViewModel.cs new file mode 100644 index 0000000..20219ca --- /dev/null +++ b/Qrakhen.Qamp.Editor/ViewModel/MainViewModel.cs @@ -0,0 +1,45 @@ +using Qrakhen.Qamp.Memory; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Qrakhen.Qamp.Editor.ViewModel; + +public class MainViewModel +{ + +} + +public class FileViewModel : ObservableObject +{ + public ObservableCollection Lines { get; private set; } = new() { + new LineBuffer("test"), + new LineBuffer(" is very good;"), + new LineBuffer(" jaja();"), + new LineBuffer("}") + }; +} + +public abstract class ObservableObject : INotifyPropertyChanged +{ + public event PropertyChangedEventHandler? PropertyChanged; + + protected bool SetProperty([NotNullIfNotNull("newValue")] ref T field, T newValue, [CallerMemberName] string? propertyName = null) + { + if (EqualityComparer.Default.Equals(field, newValue)) + return false; + + field = newValue; + OnPropertyChanged(propertyName); + return true; + } + + protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} \ No newline at end of file diff --git a/Qrakhen.Qamp.Memory/LineBuffer.cs b/Qrakhen.Qamp.Memory/LineBuffer.cs new file mode 100644 index 0000000..0a8192f --- /dev/null +++ b/Qrakhen.Qamp.Memory/LineBuffer.cs @@ -0,0 +1,39 @@ +using System.Text; + +namespace Qrakhen.Qamp.Memory; + +public sealed class LineBuffer : TailBuffer +{ + public Encoding Encoding { get; } + public string Cached { get; private set; } = ""; + + 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(); + } + + protected override void SetTail(int tail = -1) + { + base.SetTail(tail); + Cached = ToString(); + } + + public void Insert(int cursor, string data) + => Insert(cursor, Encoding.GetBytes(data)); + + public override string ToString() + { + if (Tail == 0 || Encoding == null) + return string.Empty; + return Encoding.GetString(Data, 0, Tail); + } + + public static implicit operator LineBuffer(string s) => new LineBuffer(s); +} diff --git a/Qrakhen.Qamp.Memory/Qrakhen.Qamp.Memory.csproj b/Qrakhen.Qamp.Memory/Qrakhen.Qamp.Memory.csproj new file mode 100644 index 0000000..b760144 --- /dev/null +++ b/Qrakhen.Qamp.Memory/Qrakhen.Qamp.Memory.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/Qrakhen.Qamp.Memory/Range.cs b/Qrakhen.Qamp.Memory/Range.cs new file mode 100644 index 0000000..b5b2672 --- /dev/null +++ b/Qrakhen.Qamp.Memory/Range.cs @@ -0,0 +1,3 @@ +namespace Qrakhen.Qamp.Memory; + +public readonly record struct Range(int From, int To); diff --git a/Qrakhen.Qamp.Memory/TailBuffer.cs b/Qrakhen.Qamp.Memory/TailBuffer.cs new file mode 100644 index 0000000..00b24e1 --- /dev/null +++ b/Qrakhen.Qamp.Memory/TailBuffer.cs @@ -0,0 +1,80 @@ +namespace Qrakhen.Qamp.Memory; + +// add to qrakhen.memory package for clout +public abstract class TailBuffer +{ + private T[] _data; + public T[] Data { + get => _data; + } + + public int Capacity => _data.Length; + public int Tail { get; private set; } + + public TailBuffer(int size = 0x80) + { + _data = new T[size]; + } + + public TailBuffer(T[] data) + { + _data = new T[data.Length * 2]; + Array.Copy(data, 0, _data, 0, data.Length); + SetTail(data.Length); + } + + public void Insert(int cursor, T[] data) + { + cursor = SeekTail(cursor); + Prepare(cursor, data.Length); + int length = data.Length; + if (cursor == Tail) { + // Just append here. + Array.Copy(data, 0, _data, cursor, length); + SetTail(cursor + length); + } else { + // Shift everything to the right of insertion + Array.Copy(_data, cursor, _data, cursor + length, Tail - cursor); + Array.Copy(data, 0, _data, cursor, length); + SetTail(Tail + length); + } + } + + public void Delete(Range range) + => Delete(range.From, range.To - range.From); + + public void Delete(int cursor, int count) + { + if (cursor + count >= Tail) + SetTail(cursor); // Directly terminate at cursor, nothing is behind tail. + else { + // Move everything from end of deletion until tail to start of deletion. + Array.Copy(_data, cursor + count, _data, cursor, Tail - (cursor + count)); + SetTail(Tail - count); + } + } + + // never write beyond tail (there might be invalid data) + private int SeekTail(int cursor) => cursor > Tail ? Tail : cursor; + + private void Prepare(int index, int count) + { + ArgumentOutOfRangeException.ThrowIfNegative(index); + if (index + count <= _data.Length) + return; + while (index + count > _data.Length) { + T[] grow = new T[_data.Length * 2]; + Array.Copy(_data, grow, _data.Length); + _data = grow; + } + } + + /// + /// Sets the tail, or, if omitted, seeks the tail by finding the first null terminator. + /// + /// + protected virtual void SetTail(int tail = -1) + { + Tail = tail; + } +} diff --git a/Qrakhen.Qamp.Tests/Qrakhen.Qamp.Tests.csproj b/Qrakhen.Qamp.Tests/Qrakhen.Qamp.Tests.csproj new file mode 100644 index 0000000..56bc533 --- /dev/null +++ b/Qrakhen.Qamp.Tests/Qrakhen.Qamp.Tests.csproj @@ -0,0 +1,23 @@ + + + + net10.0 + enable + enable + + false + true + + + + + + + + + + + + + + diff --git a/Qrakhen.Qamp.Tests/UnitTest1.cs b/Qrakhen.Qamp.Tests/UnitTest1.cs new file mode 100644 index 0000000..02fe1af --- /dev/null +++ b/Qrakhen.Qamp.Tests/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace Qrakhen.Qamp.Tests; + +public class UnitTest1 +{ + [Fact] + public void Test1() + { + + } +} \ No newline at end of file diff --git a/Qrakhen.Qamp.sln b/Qrakhen.Qamp.sln index a532ef4..784bb29 100644 --- a/Qrakhen.Qamp.sln +++ b/Qrakhen.Qamp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.14.36705.20 d17.14 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.11205.157 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.Core", "Qrakhen.Qamp.Core\Qrakhen.Qamp.Core.csproj", "{CA6F4D4E-AF35-4CA6-BC53-C83277EEE46C}" EndProject @@ -15,6 +15,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.Reader", "Qrak EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.Core.Tests", "Qrakhen.Qamp.Core.Tests\Qrakhen.Qamp.Core.Tests.csproj", "{C3D5A175-2572-44CB-8161-FF51BD254824}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.Tests", "Qrakhen.Qamp.Tests\Qrakhen.Qamp.Tests.csproj", "{5440D742-6149-417A-B9C8-4DEF951221E9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.Editor", "Qrakhen.Qamp.Editor\Qrakhen.Qamp.Editor.csproj", "{BE50AA8C-7D8F-4DDA-8102-92CFCE74DA96}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.Memory", "Qrakhen.Qamp.Memory\Qrakhen.Qamp.Memory.csproj", "{A8876A55-8007-4D20-9637-2099F1B82DAE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -71,6 +77,30 @@ Global {C3D5A175-2572-44CB-8161-FF51BD254824}.Release|Any CPU.Build.0 = Release|Any CPU {C3D5A175-2572-44CB-8161-FF51BD254824}.Release|x64.ActiveCfg = Release|x64 {C3D5A175-2572-44CB-8161-FF51BD254824}.Release|x64.Build.0 = Release|x64 + {5440D742-6149-417A-B9C8-4DEF951221E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5440D742-6149-417A-B9C8-4DEF951221E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5440D742-6149-417A-B9C8-4DEF951221E9}.Debug|x64.ActiveCfg = Debug|Any CPU + {5440D742-6149-417A-B9C8-4DEF951221E9}.Debug|x64.Build.0 = Debug|Any CPU + {5440D742-6149-417A-B9C8-4DEF951221E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5440D742-6149-417A-B9C8-4DEF951221E9}.Release|Any CPU.Build.0 = Release|Any CPU + {5440D742-6149-417A-B9C8-4DEF951221E9}.Release|x64.ActiveCfg = Release|Any CPU + {5440D742-6149-417A-B9C8-4DEF951221E9}.Release|x64.Build.0 = Release|Any CPU + {BE50AA8C-7D8F-4DDA-8102-92CFCE74DA96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE50AA8C-7D8F-4DDA-8102-92CFCE74DA96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE50AA8C-7D8F-4DDA-8102-92CFCE74DA96}.Debug|x64.ActiveCfg = Debug|Any CPU + {BE50AA8C-7D8F-4DDA-8102-92CFCE74DA96}.Debug|x64.Build.0 = Debug|Any CPU + {BE50AA8C-7D8F-4DDA-8102-92CFCE74DA96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE50AA8C-7D8F-4DDA-8102-92CFCE74DA96}.Release|Any CPU.Build.0 = Release|Any CPU + {BE50AA8C-7D8F-4DDA-8102-92CFCE74DA96}.Release|x64.ActiveCfg = Release|Any CPU + {BE50AA8C-7D8F-4DDA-8102-92CFCE74DA96}.Release|x64.Build.0 = Release|Any CPU + {A8876A55-8007-4D20-9637-2099F1B82DAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8876A55-8007-4D20-9637-2099F1B82DAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8876A55-8007-4D20-9637-2099F1B82DAE}.Debug|x64.ActiveCfg = Debug|Any CPU + {A8876A55-8007-4D20-9637-2099F1B82DAE}.Debug|x64.Build.0 = Debug|Any CPU + {A8876A55-8007-4D20-9637-2099F1B82DAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8876A55-8007-4D20-9637-2099F1B82DAE}.Release|Any CPU.Build.0 = Release|Any CPU + {A8876A55-8007-4D20-9637-2099F1B82DAE}.Release|x64.ActiveCfg = Release|Any CPU + {A8876A55-8007-4D20-9637-2099F1B82DAE}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE