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

@ -11,6 +11,7 @@ public class Caret : Adorner
private readonly double _height; private readonly double _height;
private readonly Brush _brush; private readonly Brush _brush;
private bool _blink; private bool _blink;
private bool _enabled;
private Point _cursorPosition; private Point _cursorPosition;
private Point _targetPosition; private Point _targetPosition;
private (Point from, Point to)[] _lines = []; private (Point from, Point to)[] _lines = [];
@ -21,12 +22,13 @@ public class Caret : Adorner
{ {
IsHitTestVisible = false; IsHitTestVisible = false;
_enabled = true;
_height = height; _height = height;
_brush = brush; _brush = brush;
_pen = new Pen(brush, 1.92); _pen = new Pen(brush, 1.92);
_blinkTimer = new DispatcherTimer(); _blinkTimer = new DispatcherTimer();
_blinkTimer.Interval = TimeSpan.FromMilliseconds(428); _blinkTimer.Interval = TimeSpan.FromMilliseconds(324);
_blinkTimer.Tick += new EventHandler((s, e) => { _blinkTimer.Tick += new EventHandler((s, e) => {
Toggle(_blink = !_blink); Toggle(_blink = !_blink);
}); });
@ -50,6 +52,15 @@ public class Caret : Adorner
public void Toggle(bool hide) public void Toggle(bool hide)
{ {
_pen.Brush = hide ? Brushes.Transparent : _brush; _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) public void UpdateSelection((Point from, Point to)[] lines)
@ -61,10 +72,9 @@ public class Caret : Adorner
public void UpdatePosition(Point newPosition) public void UpdatePosition(Point newPosition)
{ {
_cursorPosition = _targetPosition = newPosition; _cursorPosition = _targetPosition = newPosition;
_enabled = true;
Toggle(_blink = false); Toggle(_blink = false);
//_blinkTimer.Stop(); _blinkTimer.Start();
//_moveTimer.Start();
InvalidateVisual();
} }
protected override void OnRender(DrawingContext drawingContext) protected override void OnRender(DrawingContext drawingContext)

View File

@ -16,25 +16,39 @@
<Setter Property="CurrentLineBuffer" Value="{Binding CurrentLineBuffer}" /> <Setter Property="CurrentLineBuffer" Value="{Binding CurrentLineBuffer}" />
</Style> </Style>
</UserControl.Style> </UserControl.Style>
<ScrollViewer x:Name="ScrollView" <ScrollViewer x:Name="ScrollView"
VerticalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"> HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Lines}"> <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> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True" /> <VirtualizingStackPanel IsItemsHost="True" />
</ItemsPanelTemplate> </ItemsPanelTemplate>
</ItemsControl.ItemsPanel> </ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<local:LineRenderer LineBuffer="{Binding}" /> <local:LineRenderer LineBuffer="{Binding Buffer}"
Selection="{Binding Selection}" />
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
</Grid>
</ScrollViewer> </ScrollViewer>
</UserControl> </UserControl>

View File

@ -3,12 +3,12 @@ using Qrakhen.Qamp.Editor.Input;
using Qrakhen.Qamp.Editor.Primitives; using Qrakhen.Qamp.Editor.Primitives;
using Qrakhen.Qamp.Editor.ViewModel; using Qrakhen.Qamp.Editor.ViewModel;
using Qrakhen.Qamp.Memory; using Qrakhen.Qamp.Memory;
using System.Diagnostics.Eventing.Reader;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Shapes;
namespace Qrakhen.Qamp.Editor.Controls; namespace Qrakhen.Qamp.Editor.Controls;
@ -22,6 +22,12 @@ public partial class EditorFrame : UserControl
Loaded += OnLoaded; Loaded += OnLoaded;
PreviewKeyDown += OnKeyDown; PreviewKeyDown += OnKeyDown;
PreviewMouseDown += OnMouseDown; PreviewMouseDown += OnMouseDown;
ScrollView.ScrollChanged += OnScroll;
}
private void OnScroll(object sender, ScrollChangedEventArgs e)
{
UpdateCaret(false);
} }
protected void OnMouseDown(object sender, MouseButtonEventArgs e) protected void OnMouseDown(object sender, MouseButtonEventArgs e)
@ -89,13 +95,13 @@ public partial class EditorFrame : UserControl
private BufferPosition CalculateCursorPosition(Point point, 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); 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) private Point CalculateCaretPosition(int line, int index, double scrollY, double scrollX)
{ {
var ft = TextHelper.GetText(new string('_', index), 14, null, VisualTreeHelper.GetDpi(this).PixelsPerDip); var ft = TextHelper.GetText(new string('_', index), 14, null, VisualTreeHelper.GetDpi(this).PixelsPerDip);
return new Point(ft.Width + scrollX + 2, line * TextHelper.LineHeight + scrollY); return new Point(ft.Width - scrollX + 34, line * TextHelper.LineHeight - scrollY);
} }
private void MoveCaret(Point point) private void MoveCaret(Point point)
@ -103,12 +109,17 @@ public partial class EditorFrame : UserControl
Caret?.UpdatePosition(point); Caret?.UpdatePosition(point);
} }
private void SelectRegion((Point from, Point to)[] lines) private void HideCaret()
{ {
Caret?.UpdateSelection(lines); Caret?.Disable();
} }
private void UpdateCaret() private void AutoScroll(Point point)
{
MoveCaret(point);
}
private void UpdateCaret(bool autoScroll = true)
{ {
int line = CursorPosition.Line; int line = CursorPosition.Line;
int charIndex = CursorPosition.Column; int charIndex = CursorPosition.Column;
@ -116,12 +127,25 @@ public partial class EditorFrame : UserControl
double scrollY = ScrollView.VerticalOffset; double scrollY = ScrollView.VerticalOffset;
double scrollX = ScrollView.HorizontalOffset; double scrollX = ScrollView.HorizontalOffset;
MoveCaret( Point position = CalculateCaretPosition(
CalculateCaretPosition(
line, line,
charIndex, charIndex,
scrollY, scrollY,
scrollX)); 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) { if (!CurrentSelection.IsVoid) {

View File

@ -1,13 +1,8 @@
using Qrakhen.Qamp.Memory; using Qrakhen.Qamp.Memory;
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Text;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
namespace Qrakhen.Qamp.Editor.Controls; namespace Qrakhen.Qamp.Editor.Controls;

View File

@ -7,39 +7,74 @@
xmlns:vm="clr-namespace:Qrakhen.Qamp.Editor.ViewModel" xmlns:vm="clr-namespace:Qrakhen.Qamp.Editor.ViewModel"
xmlns:controls="clr-namespace:Qrakhen.Qamp.Editor.Controls" xmlns:controls="clr-namespace:Qrakhen.Qamp.Editor.Controls"
mc:Ignorable="d" mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=vm:FileViewModel}" Background="#161718"
Title="MainWindow" Height="450" Width="800"> d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
Title="MainWindow" Height="640" Width="920">
<Grid> <Grid>
<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">
</MenuItem>
<MenuItem Header="Run" Foreground="#fefefe">
</MenuItem>
</Menu>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="5*" /> <ColumnDefinition Width="5*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid Grid.Column="0" Background="#242526">
</Grid>
<GridSplitter Grid.Column="0"
ResizeDirection="Columns"
Width="1"
Background="#40fefefe" />
<Grid Grid.Column="1"> <Grid Grid.Column="1">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="5*" /> <RowDefinition Height="5*" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid Grid.Row="0"> <Grid Grid.Row="0">
<Grid.ColumnDefinitions> <controls:EditorFrame />
<ColumnDefinition Width="32" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<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"/>
<controls:EditorFrame Grid.Column="1" />
</Grid> </Grid>
<Grid Grid.Row="1" Background="#161718"> <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> </Grid>
<Grid Grid.Row="2"
Background="#141516">
<Label Foreground="#80efefef"
FontSize="10"
Padding="2">Fun &amp; Status</Label>
</Grid>
</Grid>
</Window> </Window>

View File

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