From d92320154f753946e64fd0d24cff9a637e7acc1b Mon Sep 17 00:00:00 2001 From: Pro-Qin <102397304+Pro-Qin@users.noreply.github.com> Date: Sat, 7 Nov 2026 15:32:22 +0800 Subject: [PATCH 1/9] =?UTF-8?q?fix(quickdraw):=20=E7=BB=93=E6=9E=9C?= =?UTF-8?q?=E5=8C=BA=E5=AE=BD=E5=BA=A6=E5=A1=AB=E6=BB=A1=E5=B9=B6=E5=9C=A8?= =?UTF-8?q?=E6=AF=8F=E6=AC=A1=E5=B0=BA=E5=AF=B8=E5=8F=98=E5=8C=96=E5=90=8E?= =?UTF-8?q?=E9=87=8D=E6=96=B0=E5=B1=85=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 闪抽窗口用 SizeToContent 随结果自适应大小,名字长短不同会让整组结果偏移: 结果区原先以自然宽度居中,窗口变宽后位置不再重算,视觉上就是"抽到不同人时窗口乱跳"。 - QuickDrawPage.axaml: ScrollViewer/ItemsControl 改为宽度填满,仅 WrapPanel 居中; 单张结果卡 MaxWidth=360 且 DisplayText 允许换行,避免超长姓名撑爆布局 - QuickDrawPage.axaml.cs: 排版完成后触发 ResultLayoutChanged,把居中时机交给宿主 - App.axaml.cs: SizeChanged 用 e.NewSize 重算位置(Bounds 滞后于触发本次布局的尺寸), 新增 _positioningQuickDrawWindow 防重入、位置相同则不写 Position, 并移除 PositionChanged -> 重新居中的反馈环 结果:无论抽到几个字的名字,窗口都在屏幕中央且不再抖动。 --- SecRandom/App.axaml.cs | 92 +++++++++++++------ SecRandom/Views/MainPages/QuickDrawPage.axaml | 8 +- .../Views/MainPages/QuickDrawPage.axaml.cs | 15 +++ 3 files changed, 87 insertions(+), 28 deletions(-) diff --git a/SecRandom/App.axaml.cs b/SecRandom/App.axaml.cs index 36abc616..2290a330 100644 --- a/SecRandom/App.axaml.cs +++ b/SecRandom/App.axaml.cs @@ -140,6 +140,7 @@ public partial class App : Application private static FloatingWindow? _floatingWindow; private static Window? _quickDrawWindow; private static NotificationChannelSettings? _quickDrawNotificationSettings; + private static bool _positioningQuickDrawWindow; private static MainWindow? _mainWindow; private static MainWindow? _settingsWindow; private static Task? _runtimeServicesStartupTask; @@ -2672,9 +2673,10 @@ private static Window GetOrCreateQuickDrawWindow() if (_quickDrawWindow is { IsLoaded: true }) return _quickDrawWindow; + var quickDrawPage = IAppHost.GetService(); _quickDrawWindow = new Window { - Content = IAppHost.GetService(), + Content = quickDrawPage, Title = @"SecRandom", MinWidth = 280, MinHeight = 160, @@ -2689,41 +2691,73 @@ private static Window GetOrCreateQuickDrawWindow() Background = Brushes.Transparent, TransparencyLevelHint = [WindowTransparencyLevel.Transparent] }; - _quickDrawWindow.Loaded += (_, _) => + // 结果卡片随名字长短变化时窗口会自适应缩放;每次尺寸变化都必须按新尺寸重新居中, + // 否则窗口以左上角为锚点扩张,视觉中心就会漂移——这正是"名字长短不一画面就跑偏"的根因。 + EventHandler resultLayoutChanged = (_, _) => RequestQuickDrawWindowCentering(_quickDrawWindow); + quickDrawPage.ResultLayoutChanged += resultLayoutChanged; + _quickDrawWindow.Opened += (_, _) => { - _quickDrawWindow.Width = 280; + ApplyQuickDrawWindowBounds(_quickDrawWindow); + RequestQuickDrawWindowCentering(_quickDrawWindow); }; - _quickDrawWindow.Opened += (_, _) => + _quickDrawWindow.SizeChanged += (_, e) => RepositionQuickDrawWindow(_quickDrawWindow, e.NewSize); + _quickDrawWindow.ScalingChanged += (_, _) => { ApplyQuickDrawWindowBounds(_quickDrawWindow); - Dispatcher.UIThread.Post( - () => PositionOrCenterQuickDrawWindow(_quickDrawWindow), - DispatcherPriority.Render); + RequestQuickDrawWindowCentering(_quickDrawWindow); }; - _quickDrawWindow.SizeChanged += (_, _) => Dispatcher.UIThread.Post( - () => PositionOrCenterQuickDrawWindow(_quickDrawWindow), - DispatcherPriority.Render); - _quickDrawWindow.PositionChanged += (_, _) => ApplyQuickDrawWindowBounds(_quickDrawWindow); - _quickDrawWindow.ScalingChanged += (_, _) => ApplyQuickDrawWindowBounds(_quickDrawWindow); _quickDrawWindow.Closed += (_, _) => { + quickDrawPage.ResultLayoutChanged -= resultLayoutChanged; _quickDrawWindow = null; _quickDrawNotificationSettings = null; }; return _quickDrawWindow; } - private static void PositionOrCenterQuickDrawWindow(Window? window) + /// + /// 结果区布局稳定后请求一次居中:窗口尺寸在动画/换行过程中会分几步落定, + /// 单靠 SizeChanged 可能在最后一帧之后还有一次排版变化,因此在 Loaded 优先级补一次。 + /// + private static void RequestQuickDrawWindowCentering(Window? window) { - if (_quickDrawNotificationSettings is { } settings) - PositionQuickDrawNotificationWindow(window, settings); - else - CenterQuickDrawWindow(window); + if (window is null) + return; + + Dispatcher.UIThread.Post( + () => + { + if (!ReferenceEquals(window, _quickDrawWindow)) + return; + + RepositionQuickDrawWindow(window, window.Bounds.Size); + }, + DispatcherPriority.Loaded); + } + + private static void RepositionQuickDrawWindow(Window? window, Size logicalSize) + { + if (window is null || _positioningQuickDrawWindow) + return; + + try + { + _positioningQuickDrawWindow = true; + if (_quickDrawNotificationSettings is { } settings) + PositionQuickDrawNotificationWindow(window, settings, logicalSize); + else + CenterQuickDrawWindow(window, logicalSize); + } + finally + { + _positioningQuickDrawWindow = false; + } } private static void PositionQuickDrawNotificationWindow( Window? window, - NotificationChannelSettings settings) + NotificationChannelSettings settings, + Size? logicalSize = null) { if (window is null) return; @@ -2747,8 +2781,9 @@ private static void PositionQuickDrawNotificationWindow( return; var scale = window.RenderScaling; - var width = (int)Math.Round(window.Bounds.Width * scale); - var height = (int)Math.Round(window.Bounds.Height * scale); + var size = logicalSize ?? window.Bounds.Size; + var width = (int)Math.Round(size.Width * scale); + var height = (int)Math.Round(size.Height * scale); if (width <= 0 || height <= 0) return; @@ -2767,9 +2802,11 @@ private static void PositionQuickDrawNotificationWindow( 3 or 4 => area.Center.Y - height / 2, _ => area.Center.Y - height / 2 }; - window.Position = new PixelPoint( + var target = new PixelPoint( Math.Clamp(x + settings.HorizontalOffset, area.X, Math.Max(area.X, area.Right - width)), Math.Clamp(y + settings.VerticalOffset, area.Y, Math.Max(area.Y, area.Bottom - height))); + if (window.Position != target) + window.Position = target; } private static void ApplyQuickDrawWindowBounds(Window? window) @@ -2787,25 +2824,28 @@ private static void ApplyQuickDrawWindowBounds(Window? window) window.MaxHeight = Math.Max(window.MinHeight, screen.WorkingArea.Height / scale - edgePadding); } - private static void CenterQuickDrawWindow(Window? window) + private static void CenterQuickDrawWindow(Window? window, Size? logicalSize = null) { if (window is null) return; - var screen = window.Screens.ScreenFromWindow(window); + var screen = window.Screens.ScreenFromWindow(window) ?? window.Screens.Primary; if (screen is null) return; var scale = window.RenderScaling; - var width = (int)Math.Round(window.Bounds.Width * scale); - var height = (int)Math.Round(window.Bounds.Height * scale); + var size = logicalSize ?? window.Bounds.Size; + var width = (int)Math.Round(size.Width * scale); + var height = (int)Math.Round(size.Height * scale); if (width <= 0 || height <= 0) return; var area = screen.WorkingArea; - window.Position = new PixelPoint( + var target = new PixelPoint( area.X + (area.Width - width) / 2, area.Y + (area.Height - height) / 2); + if (window.Position != target) + window.Position = target; } #endregion diff --git a/SecRandom/Views/MainPages/QuickDrawPage.axaml b/SecRandom/Views/MainPages/QuickDrawPage.axaml index 818c9608..a94d10fb 100644 --- a/SecRandom/Views/MainPages/QuickDrawPage.axaml +++ b/SecRandom/Views/MainPages/QuickDrawPage.axaml @@ -56,11 +56,13 @@ + @@ -70,6 +72,7 @@ + /// 结果区完成一次排版(预览/揭晓/通知)后触发,宿主窗口据此按最新尺寸重新居中。 + /// 事件本身不含尺寸:窗口尺寸由宿主读取,避免页面耦合窗口实现。 + /// + public event EventHandler? ResultLayoutChanged; + public void StartDraw() { if (ViewModel.StartDrawCommand.CanExecute(null)) @@ -200,6 +206,15 @@ await DrawAnimationHelper.RevealAsync( catch { } + finally + { + RaiseResultLayoutChanged(); + } + } + + private void RaiseResultLayoutChanged() + { + ResultLayoutChanged?.Invoke(this, EventArgs.Empty); } private Task TryPresentAsync(DrawPresentationPhase phase) From a629ed559745ae146ef6c8c827d96327c261cac9 Mon Sep 17 00:00:00 2001 From: Pro-Qin <102397304+Pro-Qin@users.noreply.github.com> Date: Sat, 7 Nov 2026 15:32:38 +0800 Subject: [PATCH 2/9] =?UTF-8?q?feat(floating-window):=20=E6=82=AC=E6=B5=AE?= =?UTF-8?q?=E7=AA=97=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A=E4=B9=89=E6=96=87?= =?UTF-8?q?=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 桌面上的悬浮窗除了按钮没有任何可编辑内容,老师想挂一句"本节实验课,先交报告" 只能贴便利贴。新增一条横幅文字,位于按钮面板上方。 - FloatingWindowSettingsConfig: EnableFloatingWindowText / FloatingWindowText,默认关闭 - FloatingWindow.axaml: CustomTextBlock 作为 ExpandedContent 首元素,IsHitTestVisible=False, 因此不占用按钮命中区域、不参与按钮面板的宽度计算 - ApplyCustomText: 换行折叠为空格并 Trim;字号随 FloatingWindowSize 缩放并 clamp 到 11-20; MaxWidth clamp 到面板宽度,长文本以 CharacterEllipsis 省略而不是把窗口撑宽 - 收纳为 docked 手柄时随 ExpandedContent 一起隐藏,32px 手柄不会被文字挤压 - 设置页 个性化 -> 悬浮窗 新增 S_Display_CustomText 分组(TextBox MaxLength=120 + 开关), 中英日三语资源同步;修改后复用既有 live-apply 链路立即生效 --- .../FloatingWindowSettingsConfig.cs | 3 ++ .../FloatingWindow/Resources.Designer.cs | 18 ++++++++++ .../FloatingWindow/Resources.en-US.resx | 2 +- .../FloatingWindow/Resources.ja-JP.resx | 2 +- .../FloatingWindow/Resources.resx | 6 ++++ SecRandom/Views/FloatingWindow.axaml | 11 ++++++ SecRandom/Views/FloatingWindow.axaml.cs | 34 ++++++++++++++++++- .../FloatingWindowSettingsPage.axaml | 17 ++++++++++ 8 files changed, 90 insertions(+), 3 deletions(-) diff --git a/SecRandom.Core/Models/SubConfigs/FloatingWindowSettingsConfig.cs b/SecRandom.Core/Models/SubConfigs/FloatingWindowSettingsConfig.cs index f0df4945..0c28b1b4 100644 --- a/SecRandom.Core/Models/SubConfigs/FloatingWindowSettingsConfig.cs +++ b/SecRandom.Core/Models/SubConfigs/FloatingWindowSettingsConfig.cs @@ -22,6 +22,9 @@ public partial class FloatingWindowSettingsConfig : ObservableObject [ObservableProperty] private int _dockedWindowSize = 32; [ObservableProperty] private bool _draggable = true; [ObservableProperty] private int _floatingWindowSize = 56; + // 悬浮窗自定义文字:默认关闭;空字符串等于不显示,旧配置缺字段时按默认值加载 + [ObservableProperty] private bool _enableFloatingWindowText; + [ObservableProperty] private string _floatingWindowText = string.Empty; [ObservableProperty] private int _longPressDuration = 500; [ObservableProperty] private bool _doNotStealFocus = true; [ObservableProperty] private bool _hideOnForeground = false; diff --git a/SecRandom/Langs/SettingsPages/FloatingWindow/Resources.Designer.cs b/SecRandom/Langs/SettingsPages/FloatingWindow/Resources.Designer.cs index 0b3ef1aa..b4c280a4 100644 --- a/SecRandom/Langs/SettingsPages/FloatingWindow/Resources.Designer.cs +++ b/SecRandom/Langs/SettingsPages/FloatingWindow/Resources.Designer.cs @@ -634,5 +634,23 @@ public static string O_StickDisplay_Arrow { return ResourceManager.GetString("O_StickDisplay_Arrow", resourceCulture); } } + + /// + /// Looks up a localized string similar to 自定义文字. + /// + public static string S_Display_CustomText { + get { + return ResourceManager.GetString("S_Display_CustomText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 在悬浮窗按钮上方显示一行自定义文字,过长时自动省略. + /// + public static string S_Display_CustomText_D { + get { + return ResourceManager.GetString("S_Display_CustomText_D", resourceCulture); + } + } } } diff --git a/SecRandom/Langs/SettingsPages/FloatingWindow/Resources.en-US.resx b/SecRandom/Langs/SettingsPages/FloatingWindow/Resources.en-US.resx index 42194658..f67dd719 100644 --- a/SecRandom/Langs/SettingsPages/FloatingWindow/Resources.en-US.resx +++ b/SecRandom/Langs/SettingsPages/FloatingWindow/Resources.en-US.resx @@ -51,5 +51,5 @@ Collapsed handle style Choose a roll-call icon, the word “Draw,” or a direction arrow for the edge handle Edge CollapseHandle sizeAdjust the square side length of the edge handle - IconTextArrow + IconTextArrowCustom textShow one line of custom text above the floating buttons, long text is trimmed to fit (120 characters max) diff --git a/SecRandom/Langs/SettingsPages/FloatingWindow/Resources.ja-JP.resx b/SecRandom/Langs/SettingsPages/FloatingWindow/Resources.ja-JP.resx index 64722778..9fa6b950 100644 --- a/SecRandom/Langs/SettingsPages/FloatingWindow/Resources.ja-JP.resx +++ b/SecRandom/Langs/SettingsPages/FloatingWindow/Resources.ja-JP.resx @@ -3,5 +3,5 @@ フローティングウィンドウウィンドウ表示起動時に表示起動後にフローティングウィンドウを自動で開きます不透明度メインとクイック抽選の両方に適用します最前面表示モード他のウィンドウより前に表示するか選択しますフローティングウィンドウの最前面方法を選択します、UIAccess は再起動時に管理者権限を要求し、取り消しまたは失敗した場合は今回の起動で通常の最前面表示を使用します最前面にしない最前面UIAccess 最前面 フローティング操作点呼点呼の入口を表示しますクイック抽選クイック抽選の入口を表示します抽選抽選の入口を表示しますタイマー 移動と端への吸着端に吸着ドラッグ終了時に現在の画面の左右どちらか近い端へ吸着しますドラッグ可能無効にすると本体とハンドルをドラッグできませんフォーカスを奪わないクリック時に現在のウィンドウからフォーカスを奪わないようにしますセミコロン ; で区切り、プロセス名が一致したら非表示にします(例: WeChat.exe)長押し判定時間前面プロセス名前面ウィンドウのタイトルまたはプロセス名が一致したら自動で非表示にします自動収納までの遅延長押し操作に必要なミリ秒数前面ウィンドウで非表示吸着後に収納するまでの秒数で、0 で展開したままにしますセミコロン ; で区切り、タイトルに一致する文字があれば非表示にします前面ウィンドウタイトル - 表示する操作メインウィンドウに表示する機能を選択します操作の配置矩形は2列、縦と横は1列で配置します矩形縦横表示形式アイコン、文字、または両方を選択しますアイコンと文字アイコンのみ文字のみ操作のサイズ各操作の正方形サイズを調整します極小とても小さい小中大とても大きい極大収納ハンドルの表示点呼アイコン、「抽選」の文字、または矢印を選択します端への収納ハンドルサイズ収納ハンドルの正方形サイズを調整しますアイコン文字矢印 + 表示する操作メインウィンドウに表示する機能を選択します操作の配置矩形は2列、縦と横は1列で配置します矩形縦横表示形式アイコン、文字、または両方を選択しますアイコンと文字アイコンのみ文字のみ操作のサイズ各操作の正方形サイズを調整します極小とても小さい小中大とても大きい極大収納ハンドルの表示点呼アイコン、「抽選」の文字、または矢印を選択します端への収納ハンドルサイズ収納ハンドルの正方形サイズを調整しますアイコン文字矢印カスタムテキストフローティングウィンドウのボタン上に 1 行のカスタムテキストを表示し、長い場合は省略します(最大 120 文字) diff --git a/SecRandom/Langs/SettingsPages/FloatingWindow/Resources.resx b/SecRandom/Langs/SettingsPages/FloatingWindow/Resources.resx index f3251725..3927f453 100644 --- a/SecRandom/Langs/SettingsPages/FloatingWindow/Resources.resx +++ b/SecRandom/Langs/SettingsPages/FloatingWindow/Resources.resx @@ -213,4 +213,10 @@ 箭头 + + 自定义文字 + + + 在悬浮窗按钮上方显示一行自定义文字,过长时自动省略(最多 120 字) + diff --git a/SecRandom/Views/FloatingWindow.axaml b/SecRandom/Views/FloatingWindow.axaml index bb954678..d0e5090f 100644 --- a/SecRandom/Views/FloatingWindow.axaml +++ b/SecRandom/Views/FloatingWindow.axaml @@ -30,6 +30,17 @@ + +