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