From 7e1731f4dc18e8e584534e4b4fe2bd68248b974f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Wed, 15 Apr 2026 02:12:01 +0200 Subject: [PATCH] Fix compose initial focus behavior --- .../Controls/WebViewEditorControl.cs | 8 +- .../Views/Mail/ComposePage.xaml.cs | 84 ++++++++++++++++--- 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/Wino.Mail.WinUI/Controls/WebViewEditorControl.cs b/Wino.Mail.WinUI/Controls/WebViewEditorControl.cs index 92eafafe..97d1870c 100644 --- a/Wino.Mail.WinUI/Controls/WebViewEditorControl.cs +++ b/Wino.Mail.WinUI/Controls/WebViewEditorControl.cs @@ -296,12 +296,18 @@ public sealed partial class WebViewEditorControl : Control, IDisposable, IEditor return; } + if (focusControlAsWell) + { + Focus(FocusState.Programmatic); + _chromium.Focus(FocusState.Programmatic); + _chromium.Focus(FocusState.Keyboard); + } + await _chromium.ExecuteScriptSafeAsync("focusEditor();"); if (focusControlAsWell) { _chromium.Focus(FocusState.Keyboard); - _chromium.Focus(FocusState.Programmatic); } } diff --git a/Wino.Mail.WinUI/Views/Mail/ComposePage.xaml.cs b/Wino.Mail.WinUI/Views/Mail/ComposePage.xaml.cs index 43bedfe5..08a8b0a9 100644 --- a/Wino.Mail.WinUI/Views/Mail/ComposePage.xaml.cs +++ b/Wino.Mail.WinUI/Views/Mail/ComposePage.xaml.cs @@ -38,7 +38,10 @@ public sealed partial class ComposePage : ComposePageAbstract, IPopoutClient, IRecipient { + private const int InitialFocusRetryCount = 3; + private bool _isPoppedOut; + private bool _isInitialFocusHandled; public bool SupportsPopOut => !_isPoppedOut; public event EventHandler? PopOutRequested; @@ -307,7 +310,7 @@ public sealed partial class ComposePage : ComposePageAbstract, _disposables.Add(WebViewEditor); ViewModel.GetHTMLBodyFunction = WebViewEditor.GetHtmlBodyAsync; - ViewModel.RenderHtmlBodyAsyncFunc = WebViewEditor.RenderHtmlAsync; + ViewModel.RenderHtmlBodyAsyncFunc = RenderComposeHtmlAsync; } private void ShowCCBCCClicked(object sender, RoutedEventArgs e) @@ -373,9 +376,10 @@ public sealed partial class ComposePage : ComposePageAbstract, { if (draftMailItemViewModel == null || !draftMailItemViewModel.IsDraft) return; - // Reset the initial focus flag so ToBox gets focus for the new draft. - isInitialFocusHandled = false; + // Reset the initial focus flag for the newly loaded draft. + _isInitialFocusHandled = false; await ViewModel.RefreshDraftAsync(draftMailItemViewModel); + await ApplyInitialFocusAsync(); } private void ImportanceClicked(object sender, RoutedEventArgs e) @@ -434,21 +438,19 @@ public sealed partial class ComposePage : ComposePageAbstract, } } - // Hack: Tokenizing text box losing focus somehow on page Loaded and shifting focus to this element. - // For once we'll switch back to it once CCBBCGotFocus element got focus. - - private bool isInitialFocusHandled = false; - private void ComposerLoaded(object sender, RoutedEventArgs e) { - ToBox.Focus(FocusState.Programmatic); + if (ShouldFocusRecipients()) + { + ToBox.Focus(FocusState.Programmatic); + } } private void CCBBCGotFocus(object sender, RoutedEventArgs e) { - if (!isInitialFocusHandled) + if (ShouldFocusRecipients() && !_isInitialFocusHandled) { - isInitialFocusHandled = true; + _isInitialFocusHandled = true; ToBox.Focus(FocusState.Programmatic); } } @@ -555,4 +557,64 @@ public sealed partial class ComposePage : ComposePageAbstract, } finally { deferral.Complete(); } } + + private bool ShouldFocusRecipients() + => !ShouldFocusEditor(); + + private bool ShouldFocusEditor() + { + var inReplyTo = ViewModel.CurrentMimeMessage?.InReplyTo; + + if (string.IsNullOrWhiteSpace(inReplyTo)) + { + inReplyTo = ViewModel.CurrentMailDraftItem?.MailCopy?.InReplyTo; + } + + if (string.IsNullOrWhiteSpace(inReplyTo) && ViewModel.CurrentMimeMessage?.Headers.Contains(HeaderId.InReplyTo) == true) + { + inReplyTo = ViewModel.CurrentMimeMessage.Headers[HeaderId.InReplyTo]; + } + + return !string.IsNullOrWhiteSpace(inReplyTo); + } + + private async Task ApplyInitialFocusAsync() + { + if (_isInitialFocusHandled) + { + return; + } + + _isInitialFocusHandled = true; + + for (var attempt = 0; attempt < InitialFocusRetryCount; attempt++) + { + if (ShouldFocusEditor()) + { + await WebViewEditor.FocusEditorAsync(true); + + if (FocusManager.GetFocusedElement(XamlRoot) is WebView2) + { + return; + } + } + else + { + ToBox.Focus(FocusState.Programmatic); + + if (FocusManager.GetFocusedElement(XamlRoot) == ToBox) + { + return; + } + } + + await Task.Delay(TimeSpan.FromMilliseconds(50)); + } + } + + private async Task RenderComposeHtmlAsync(string html) + { + await WebViewEditor.RenderHtmlAsync(html); + await ApplyInitialFocusAsync(); + } }