From a4c245dbdcb7b960329e4a2f1467da20475a0755 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Mon, 3 Aug 2026 00:05:36 +0200 Subject: [PATCH] Make messages selectable --- src/Decho/Converters/StringConverters.cs | 2 +- .../ViewModels/MessageComposerViewModel.cs | 10 +- src/Decho/Views/MessageItemView.axaml | 8 +- src/Decho/Views/MessageItemView.axaml.cs | 2 +- src/Decho/Views/MessageListView.axaml.cs | 119 +++++++++--------- 5 files changed, 71 insertions(+), 70 deletions(-) diff --git a/src/Decho/Converters/StringConverters.cs b/src/Decho/Converters/StringConverters.cs index 6500292..8ef34f0 100644 --- a/src/Decho/Converters/StringConverters.cs +++ b/src/Decho/Converters/StringConverters.cs @@ -33,4 +33,4 @@ public sealed class BoolToOpacityConverter : IValueConverter { throw new NotSupportedException(); } -} +} \ No newline at end of file diff --git a/src/Decho/ViewModels/MessageComposerViewModel.cs b/src/Decho/ViewModels/MessageComposerViewModel.cs index ecabf44..7c074e8 100644 --- a/src/Decho/ViewModels/MessageComposerViewModel.cs +++ b/src/Decho/ViewModels/MessageComposerViewModel.cs @@ -202,6 +202,11 @@ public sealed class MessageComposerViewModel : ViewModelBase ReplyTarget = null; } + public void UpdateAutocomplete(string text, int caretIndex) + { + Autocomplete.Update(text, caretIndex); + } + internal void Send() { string text = Draft.Trim(); @@ -230,9 +235,4 @@ public sealed class MessageComposerViewModel : ViewModelBase ? $"{StagedFiles.Count} file(s) staged" : string.Empty; } - - public void UpdateAutocomplete(string text, int caretIndex) - { - Autocomplete.Update(text, caretIndex); - } } \ No newline at end of file diff --git a/src/Decho/Views/MessageItemView.axaml b/src/Decho/Views/MessageItemView.axaml index 068fa3d..452dab2 100644 --- a/src/Decho/Views/MessageItemView.axaml +++ b/src/Decho/Views/MessageItemView.axaml @@ -50,10 +50,10 @@ Opacity="0.5" IsVisible="{Binding HasReply}" PointerPressed="OnReplyQuotePointerPressed" /> - + diff --git a/src/Decho/Views/MessageItemView.axaml.cs b/src/Decho/Views/MessageItemView.axaml.cs index 384a24f..3f3ccfb 100644 --- a/src/Decho/Views/MessageItemView.axaml.cs +++ b/src/Decho/Views/MessageItemView.axaml.cs @@ -117,7 +117,7 @@ public partial class MessageItemView : UserControl private void BuildMessageInlines(string content) { - TextBlock? tb = MessageContent; + SelectableTextBlock? tb = MessageContent; if (tb is null) { return; diff --git a/src/Decho/Views/MessageListView.axaml.cs b/src/Decho/Views/MessageListView.axaml.cs index 5de4ec7..27bed58 100644 --- a/src/Decho/Views/MessageListView.axaml.cs +++ b/src/Decho/Views/MessageListView.axaml.cs @@ -17,12 +17,72 @@ public partial class MessageListView : UserControl private bool _loadingMore; private double _loadingMoreExtent; + private MessageViewModel? _highlightedMessage; + + private CancellationTokenSource? _highlightCts; + public MessageListView() { InitializeComponent(); DataContextChanged += OnDataContextChanged; } + public void ScrollToMessage(string messageId) + { + if (_chat is null) return; + + MessageViewModel? target = _chat.Messages.FirstOrDefault(m => m.Model.Id == messageId); + if (target is null) return; + + ScrollViewer? scroll = this.FindControl("MessageScrollViewer"); + if (scroll is null) return; + + ClearHighlight(); + target.IsHighlighted = true; + _highlightedMessage = target; + _highlightCts = new CancellationTokenSource(); + CancellationToken ct = _highlightCts.Token; + + Dispatcher.UIThread.Post(() => + { + double? targetY = FindItemY(scroll, target); + if (targetY is not null) + scroll.Offset = new Vector(scroll.Offset.X, targetY.Value); + }, DispatcherPriority.Render); + + _ = Task.Run(async () => + { + try + { + await Task.Delay(2000, ct); + Dispatcher.UIThread.Post(() => + { + target.IsHighlighted = false; + if (_highlightedMessage == target) + _highlightedMessage = null; + }); + } + catch (OperationCanceledException) + { + } + }); + } + + private static double? FindItemY(Visual parent, MessageViewModel target) + { + foreach (Visual child in parent.GetVisualChildren()) + { + if (child.DataContext == target) + return child.Bounds.Y; + + double? result = FindItemY(child, target); + if (result.HasValue) + return result; + } + + return null; + } + private void OnDataContextChanged(object? sender, EventArgs args) { if (_chat is not null) @@ -142,65 +202,6 @@ public partial class MessageListView : UserControl Dispatcher.UIThread.Post(scroll.ScrollToEnd, DispatcherPriority.Background); } - public void ScrollToMessage(string messageId) - { - if (_chat is null) return; - - MessageViewModel? target = _chat.Messages.FirstOrDefault(m => m.Model.Id == messageId); - if (target is null) return; - - ScrollViewer? scroll = this.FindControl("MessageScrollViewer"); - if (scroll is null) return; - - ClearHighlight(); - target.IsHighlighted = true; - _highlightedMessage = target; - _highlightCts = new CancellationTokenSource(); - CancellationToken ct = _highlightCts.Token; - - Dispatcher.UIThread.Post(() => - { - double? targetY = FindItemY(scroll, target); - if (targetY is not null) - scroll.Offset = new Vector(scroll.Offset.X, targetY.Value); - }, DispatcherPriority.Render); - - _ = Task.Run(async () => - { - try - { - await Task.Delay(2000, ct); - Dispatcher.UIThread.Post(() => - { - target.IsHighlighted = false; - if (_highlightedMessage == target) - _highlightedMessage = null; - }); - } - catch (OperationCanceledException) - { - } - }); - } - - private static double? FindItemY(Visual parent, MessageViewModel target) - { - foreach (Visual child in parent.GetVisualChildren()) - { - if (child.DataContext == target) - return child.Bounds.Y; - - double? result = FindItemY(child, target); - if (result.HasValue) - return result; - } - - return null; - } - - private MessageViewModel? _highlightedMessage; - private CancellationTokenSource? _highlightCts; - private void ClearHighlight() { _highlightCts?.Cancel();