This commit is contained in:
Qrakhen 2025-11-22 17:34:27 +01:00
parent e2022ef80e
commit b3385c0860
6 changed files with 269 additions and 185 deletions

View File

@ -7,26 +7,28 @@ namespace Qrakhen.Qamp.Editor.Controls;
public class Caret : Adorner
{
private readonly Pen _pen;
private readonly double _height;
private readonly Brush _brush;
private bool _blink;
private Point _cursorPosition;
private Point _targetPosition;
private readonly Pen _pen;
private readonly double _height;
private readonly Brush _brush;
private bool _blink;
private bool _enabled;
private Point _cursorPosition;
private Point _targetPosition;
private (Point from, Point to)[] _lines = [];
private DispatcherTimer _blinkTimer;
private DispatcherTimer _moveTimer;
private DispatcherTimer _blinkTimer;
private DispatcherTimer _moveTimer;
public Caret(UIElement adornedElement, Brush brush, double height = 18, double thickness = 1.72) : base(adornedElement)
{
IsHitTestVisible = false;
_enabled = true;
_height = height;
_brush = brush;
_pen = new Pen(brush, 1.92);
_blinkTimer = new DispatcherTimer();
_blinkTimer.Interval = TimeSpan.FromMilliseconds(428);
_blinkTimer.Interval = TimeSpan.FromMilliseconds(324);
_blinkTimer.Tick += new EventHandler((s, e) => {
Toggle(_blink = !_blink);
});
@ -50,22 +52,30 @@ public class Caret : Adorner
public void Toggle(bool hide)
{
_pen.Brush = hide ? Brushes.Transparent : _brush;
InvalidateVisual();
}
public void Disable()
{
_blinkTimer?.Stop();
_enabled = false;
Toggle(true);
InvalidateVisual();
}
public void UpdateSelection((Point from, Point to)[] lines)
{
_lines = lines;
InvalidateVisual();
}
public void UpdatePosition(Point newPosition)
{
_cursorPosition = _targetPosition = newPosition;
Toggle(_blink = false);
//_blinkTimer.Stop();
//_moveTimer.Start();
InvalidateVisual();
}
public void UpdatePosition(Point newPosition)
{
_cursorPosition = _targetPosition = newPosition;
_enabled = true;
Toggle(_blink = false);
_blinkTimer.Start();
}
protected override void OnRender(DrawingContext drawingContext)
{

View File

@ -16,25 +16,39 @@
<Setter Property="CurrentLineBuffer" Value="{Binding CurrentLineBuffer}" />
</Style>
</UserControl.Style>
<ScrollViewer x:Name="ScrollView"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox x:Name="LinesList"
Grid.Column="0"
ItemsSource="{Binding Lines.Count}"
IsHitTestVisible="False"
Background="#121314"
Foreground="#969696"
BorderThickness="0"
Padding="0,0,5,0"
VerticalContentAlignment="Top"
HorizontalContentAlignment="Right"/>
<ItemsControl ItemsSource="{Binding Lines}"
Grid.Column="1">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:LineRenderer LineBuffer="{Binding Buffer}"
Selection="{Binding Selection}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ScrollViewer x:Name="ScrollView"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:LineRenderer LineBuffer="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ItemsControl>
</Grid>
</ScrollViewer>
</UserControl>

View File

@ -3,26 +3,32 @@ using Qrakhen.Qamp.Editor.Input;
using Qrakhen.Qamp.Editor.Primitives;
using Qrakhen.Qamp.Editor.ViewModel;
using Qrakhen.Qamp.Memory;
using System.Diagnostics.Eventing.Reader;
using System.Windows;
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;
public partial class EditorFrame : UserControl
{
public Caret Caret { get; private set; }
public Caret Caret { get; private set; }
public EditorFrame()
{
InitializeComponent();
Loaded += OnLoaded;
PreviewKeyDown += OnKeyDown;
PreviewMouseDown += OnMouseDown;
}
public EditorFrame()
{
InitializeComponent();
Loaded += OnLoaded;
PreviewKeyDown += OnKeyDown;
PreviewMouseDown += OnMouseDown;
ScrollView.ScrollChanged += OnScroll;
}
private void OnScroll(object sender, ScrollChangedEventArgs e)
{
UpdateCaret(false);
}
protected void OnMouseDown(object sender, MouseButtonEventArgs e)
{
@ -37,126 +43,144 @@ public partial class EditorFrame : UserControl
}
}
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.Initial)
vm.MoveCaretCommand.Execute(
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.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)
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 {
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 {
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;
}
}
e.Handled = true;
}
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Focus();
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this);
if (adornerLayer != null) {
Caret = new Caret(this, Brushes.LimeGreen);
adornerLayer.Add(Caret);
} else {
System.Diagnostics.Debug.WriteLine("AdornerLayer still null. Check parent hierarchy.");
}
Loaded -= OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Focus();
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this);
if (adornerLayer != null) {
Caret = new Caret(this, Brushes.LimeGreen);
adornerLayer.Add(Caret);
} else {
System.Diagnostics.Debug.WriteLine("AdornerLayer still null. Check parent hierarchy.");
}
Loaded -= OnLoaded;
}
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));
return new BufferPosition((int)(point.Y / TextHelper.LineHeight), (int)((point.X - 34) / 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);
}
private void MoveCaret(Point point)
{
Caret?.UpdatePosition(point);
}
private void SelectRegion((Point from, Point to)[] lines)
{
Caret?.UpdateSelection(lines);
var ft = TextHelper.GetText(new string('_', index), 14, null, VisualTreeHelper.GetDpi(this).PixelsPerDip);
return new Point(ft.Width - scrollX + 34, line * TextHelper.LineHeight - scrollY);
}
private void UpdateCaret()
{
int line = CursorPosition.Line;
int charIndex = CursorPosition.Column;
private void MoveCaret(Point point)
{
Caret?.UpdatePosition(point);
}
double scrollY = ScrollView.VerticalOffset;
double scrollX = ScrollView.HorizontalOffset;
private void HideCaret()
{
Caret?.Disable();
}
MoveCaret(
CalculateCaretPosition(
line,
charIndex,
scrollY,
scrollX));
private void AutoScroll(Point point)
{
MoveCaret(point);
}
private void UpdateCaret(bool autoScroll = true)
{
int line = CursorPosition.Line;
int charIndex = CursorPosition.Column;
double scrollY = ScrollView.VerticalOffset;
double scrollX = ScrollView.HorizontalOffset;
Point position = CalculateCaretPosition(
line,
charIndex,
scrollY,
scrollX);
Point offset = ScrollView.TranslatePoint(position, ScrollView);
if (offset.X < 0 ||
offset.Y < 0 ||
offset.X >= ScrollView.ActualWidth ||
offset.Y >= ScrollView.ActualHeight) {
if (autoScroll)
AutoScroll(offset);
else
HideCaret();
}
else
MoveCaret(position);
if (!CurrentSelection.IsVoid) {
}
}
}
public LineBuffer CurrentLineBuffer {
get => (LineBuffer)GetValue(CurrentLineBufferProperty);
set => SetValue(CurrentLineBufferProperty, value);
}
public LineBuffer CurrentLineBuffer {
get => (LineBuffer)GetValue(CurrentLineBufferProperty);
set => SetValue(CurrentLineBufferProperty, value);
}
public static readonly DependencyProperty CurrentLineBufferProperty =
public static readonly DependencyProperty CurrentLineBufferProperty =
DependencyProperty.Register(
nameof(CurrentLineBuffer),
typeof(LineBuffer),
typeof(EditorFrame),
new PropertyMetadata(null));
public BufferPosition CursorPosition {
get => (BufferPosition)GetValue(CursorPositionProperty);
set => SetValue(CursorPositionProperty, value);
}
public BufferPosition CursorPosition {
get => (BufferPosition)GetValue(CursorPositionProperty);
set => SetValue(CursorPositionProperty, value);
}
public static readonly DependencyProperty CursorPositionProperty =
public static readonly DependencyProperty CursorPositionProperty =
DependencyProperty.Register(
nameof(CursorPosition),
typeof(BufferPosition),
typeof(EditorFrame),
new PropertyMetadata(BufferPosition.Initial, OnCursorPositionChanged));
private static void OnCursorPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var frame = (EditorFrame)d;
frame.UpdateCaret();
}
private static void OnCursorPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var frame = (EditorFrame)d;
frame.UpdateCaret();
}
public BufferRegion CurrentSelection {
get => (BufferRegion)GetValue(CurrentSelectionProperty);

View File

@ -1,13 +1,8 @@
using Qrakhen.Qamp.Memory;
using System;
using System.Collections.Generic;
using System.ComponentModel;
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;
namespace Qrakhen.Qamp.Editor.Controls;

View File

@ -7,39 +7,74 @@
xmlns:vm="clr-namespace:Qrakhen.Qamp.Editor.ViewModel"
xmlns:controls="clr-namespace:Qrakhen.Qamp.Editor.Controls"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=vm:FileViewModel}"
Title="MainWindow" Height="450" Width="800">
Background="#161718"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
Title="MainWindow" Height="640" Width="920">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="5*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="32" />
<RowDefinition Height="*" />
<RowDefinition Height="18" />
</Grid.RowDefinitions>
<Grid Grid.Row="0"
Background="#202122">
<Menu Background="#202122"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
Height="32">
<MenuItem Header="File"
Height="32"
Foreground="#fefefe">
<MenuItem></MenuItem>
<MenuItem></MenuItem>
<MenuItem></MenuItem>
<MenuItem></MenuItem>
<MenuItem></MenuItem>
</MenuItem>
<MenuItem Header="Fun" Foreground="#fefefe">
<ListBox Grid.Column="0"
ItemsSource="{Binding Lines.Count}"
IsHitTestVisible="False"
Background="#242628"
Foreground="#969696"
BorderThickness="0"
Padding="0,0,5,0"
VerticalContentAlignment="Top"
HorizontalContentAlignment="Right"/>
</MenuItem>
<MenuItem Header="Run" Foreground="#fefefe">
<controls:EditorFrame Grid.Column="1" />
</Grid>
<Grid Grid.Row="1" Background="#161718">
</MenuItem>
</Menu>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Background="#242526">
</Grid>
<GridSplitter Grid.Column="0"
ResizeDirection="Columns"
Width="1"
Background="#40fefefe" />
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="5*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<controls:EditorFrame />
</Grid>
<GridSplitter Grid.Row="0"
ResizeDirection="Rows"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Height="1"
Background="#40fefefe" />
<Grid Grid.Row="1" Background="#1e1f20">
</Grid>
</Grid>
</Grid>
<Grid Grid.Row="2"
Background="#141516">
<Label Foreground="#80efefef"
FontSize="10"
Padding="2">Fun &amp; Status</Label>
</Grid>
</Grid>
</Window>

View File

@ -8,6 +8,12 @@ using System.Windows.Input;
namespace Qrakhen.Qamp.Editor.ViewModel;
public class SelectableLineBuffer(LineBuffer buffer)
{
public LineBuffer Buffer { get; private set; } = buffer;
public BufferRegion Selection { get; private set; } = new();
}
public class EditorFrameViewModel : ObservableObject
{
private BufferPosition _cursorPosition;
@ -26,7 +32,7 @@ public class EditorFrameViewModel : ObservableObject
public int CursorColumn => _cursorPosition.Column;
public LineBuffer CurrentLineBuffer => Lines[CursorLine];
public SelectableLineBuffer CurrentLine => Lines[CursorLine];
public ICommand MoveCaretCommand { get; }
public ICommand MoveBlockCommand { get; }
@ -34,11 +40,11 @@ public class EditorFrameViewModel : ObservableObject
public ICommand DeleteCommand { get; }
public ICommand ClipBoardCommand { get; }
public ObservableCollection<LineBuffer> Lines { get; private set; } = new() {
new LineBuffer("test"),
new LineBuffer(" is very good;"),
new LineBuffer(" jaja();"),
new LineBuffer("}")
public ObservableCollection<SelectableLineBuffer> Lines { get; private set; } = new() {
new SelectableLineBuffer(new LineBuffer("test {")),
new SelectableLineBuffer(new LineBuffer(" is very good;")),
new SelectableLineBuffer(new LineBuffer(" jaja();")),
new SelectableLineBuffer(new LineBuffer("}"))
};
public EditorFrameViewModel()
@ -56,14 +62,14 @@ public class EditorFrameViewModel : ObservableObject
public void SetCursor(BufferPosition position)
{
CursorPosition = AlignBufferPosition(position);
OnPropertyChanged(nameof(CurrentLineBuffer));
OnPropertyChanged(nameof(CurrentLine));
}
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));
OnPropertyChanged(nameof(CurrentLine));
}
public void Delete(BufferRegion region, bool follow = true)
@ -83,25 +89,25 @@ public class EditorFrameViewModel : ObservableObject
if (follow) {
// remove newline
if (CursorColumn - count < 0 && CursorLine > 0) {
int position = Lines[CursorLine - 1].Tail;
Lines[CursorLine - 1].Insert(
int position = Lines[CursorLine - 1].Buffer.Tail;
Lines[CursorLine - 1].Buffer.Insert(
position,
CurrentLineBuffer.Slice(0));
CurrentLine.Buffer.Slice(0));
Lines.RemoveAt(CursorLine);
SetCursor(CursorLine - 1, position);
} else {
MoveCursor(0, -count);
CurrentLineBuffer.Delete(CursorColumn, count);
CurrentLine.Buffer.Delete(CursorColumn, count);
}
} else {
if (CursorColumn + count > CurrentLineBuffer.Tail &&
if (CursorColumn + count > CurrentLine.Buffer.Tail &&
CursorLine < Lines.Count - 1) {
CurrentLineBuffer.Insert(
CurrentLineBuffer.Tail,
Lines[CursorLine + 1].Slice(0));
CurrentLine.Buffer.Insert(
CurrentLine.Buffer.Tail,
Lines[CursorLine + 1].Buffer.Slice(0));
Lines.RemoveAt(CursorLine + 1);
}
CurrentLineBuffer.Delete(CursorColumn, count);
CurrentLine.Buffer.Delete(CursorColumn, count);
}
}
@ -123,12 +129,12 @@ public class EditorFrameViewModel : ObservableObject
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);
CurrentLine.Buffer.Slice(CursorColumn),
CurrentLine.Buffer.Encoding);
Lines.Insert(CursorLine + 1, new SelectableLineBuffer(split));
SetCursor(new BufferPosition(CursorLine + 1, 0));
}
CurrentLineBuffer.Insert(CursorColumn, lines[i]);
CurrentLine.Buffer.Insert(CursorColumn, lines[i]);
if (follow)
MoveCursor(new BufferPosition(0, lines[i].Length));
}
@ -137,7 +143,7 @@ public class EditorFrameViewModel : ObservableObject
private BufferPosition AlignBufferPosition(BufferPosition position)
{
int line = Math.Max(0, Math.Min(position.Line, Lines.Count - 1));
LineBuffer buffer = Lines[line];
LineBuffer buffer = Lines[line].Buffer;
int column = Math.Max(0, Math.Min(position.Column, buffer.Tail));
return new BufferPosition(line, column);
}