Skip to content

Commit

Permalink
refactor: 操纵 toast 的代码集中在 ToastPage 中
Browse files Browse the repository at this point in the history
  • Loading branch information
Blinue committed Oct 21, 2024
1 parent c922ad6 commit 72d4eca
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 94 deletions.
99 changes: 85 additions & 14 deletions src/Magpie.App/ToastPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,60 @@
#if __has_include("ToastPage.g.cpp")
#include "ToastPage.g.cpp"
#endif
#include "Win32Utils.h"

using namespace winrt;
using namespace Windows::UI::Xaml::Controls;

namespace winrt::Magpie::App::implementation {

MUXC::TeachingTip ToastPage::ShowMessage(const hstring& message) {
HideMessage();
fire_and_forget ToastPage::ShowMessageOnWindow(hstring message, uint64_t hwndTarget) {
// !!! HACK !!!
// 重用 TeachingTip 有一个 bug: 前一个 Toast 正在消失时新的 Toast 不会显示。为了
// 规避它,我们每次都创建新的 TeachingTip,但要保留旧对象的引用,因为播放动画时销毁
// 会导致崩溃。oldTeachingTIp 的生存期可确保动画播放完毕。
MUXC::TeachingTip oldTeachingTip = MessageTeachingTip();
if (oldTeachingTip) {
// 先卸载再关闭,始终关闭 TeachingTip 确保调用者可以检查是否可见
UnloadObject(oldTeachingTip);
oldTeachingTip.IsOpen(false);
}

// 备份要使用的变量,后面避免使用 this
CoreDispatcher dispatcher = Dispatcher();
auto weakThis = get_weak();

// oldTeachingTip 卸载后弹窗不会立刻隐藏,稍微等待防止弹窗闪烁
co_await resume_foreground(dispatcher, CoreDispatcherPriority::Low);

if (!weakThis.get()) {
co_return;
}

RECT frameRect;
if (!Win32Utils::GetWindowFrameRect((HWND)hwndTarget, frameRect)) {
co_return;
}

// 更改所有者关系使弹窗始终在 hwndTarget 上方
SetWindowLongPtr(_hwndToast, GWLP_HWNDPARENT, (LONG_PTR)hwndTarget);
// hwndToast 的输入已被附加到了 hWnd 上,这是所有者窗口的默认行为,但我们不需要。
// 见 https://devblogs.microsoft.com/oldnewthing/20130412-00/?p=4683
AttachThreadInput(
GetCurrentThreadId(),
GetWindowThreadProcessId((HWND)hwndTarget, nullptr),
FALSE
);

SetWindowPos(
_hwndToast,
NULL,
(frameRect.left + frameRect.right) / 2,
(frameRect.top + frameRect.bottom * 4) / 5,
0,
0,
SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW
);

// 创建新的 TeachingTip
MUXC::TeachingTip curTeachingTip = FindName(L"MessageTeachingTip").as<MUXC::TeachingTip>();
Expand All @@ -34,10 +80,11 @@ MUXC::TeachingTip ToastPage::ShowMessage(const hstring& message) {
});

curTeachingTip.IsOpen(true);
_prevTeachingTip = curTeachingTip;

// 第三个参数用于延长 oldTeachingTip 的生存期,确保关闭动画播放完毕
[](CoreDispatcher dispatcher, weak_ref<MUXC::TeachingTip> weakCurTeachingTip) -> fire_and_forget {
// 第三个参数用于延长 oldTeachingTip 的生存期,确保关闭动画播放完毕后再析构。
// TeachingTip 的显示和隐藏动画总计 500ms,显示时长不应少于这个时间。
// https://github.com/Blinue/microsoft-ui-xaml/blob/75f7666f5907aad29de1cb2e49405cc06d433fba/dev/TeachingTip/TeachingTip.h#L239-L240
[](CoreDispatcher dispatcher, weak_ref<MUXC::TeachingTip> weakCurTeachingTip, MUXC::TeachingTip) -> fire_and_forget {
// 显示时长固定 2 秒
co_await 2s;
co_await dispatcher;
Expand All @@ -47,17 +94,41 @@ MUXC::TeachingTip ToastPage::ShowMessage(const hstring& message) {
if (curTeachingTip && curTeachingTip.IsLoaded()) {
curTeachingTip.IsOpen(false);
}
}(Dispatcher(), curTeachingTip);
}(dispatcher, curTeachingTip, oldTeachingTip);

return curTeachingTip;
}
// 定期更新弹窗位置,这里应避免使用 this
RECT prevframeRect{};
const HWND hwndToast = _hwndToast;
do {
co_await resume_background();
// 等待一帧的时间可以使弹窗的移动更平滑
DwmFlush();
co_await dispatcher;

void ToastPage::HideMessage() {
if (_prevTeachingTip && _prevTeachingTip.IsLoaded()) {
// 先卸载再关闭,始终关闭 TeachingTip 确保调用者可以检查是否可见
UnloadObject(_prevTeachingTip);
_prevTeachingTip.IsOpen(false);
}
if (!IsWindow((HWND)hwndTarget) || !IsWindow(hwndToast)) {
break;
}

if (!Win32Utils::GetWindowFrameRect((HWND)hwndTarget, frameRect)) {
break;
}

// 窗口没有移动则无需更新
if (frameRect == prevframeRect) {
continue;
}
prevframeRect = frameRect;

SetWindowPos(
hwndToast,
NULL,
(frameRect.left + frameRect.right) / 2,
(frameRect.top + frameRect.bottom * 4) / 5,
0,
0,
SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW
);
} while (curTeachingTip.IsOpen());
}

}
7 changes: 4 additions & 3 deletions src/Magpie.App/ToastPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
namespace winrt::Magpie::App::implementation {

struct ToastPage : ToastPageT<ToastPage> {
MUXC::TeachingTip ShowMessage(const hstring& message);
void HideMessage();
ToastPage(uint64_t hwndToast) : _hwndToast((HWND)hwndToast) {}

fire_and_forget ShowMessageOnWindow(hstring message, uint64_t hwndTarget);

private:
MUXC::TeachingTip _prevTeachingTip{ nullptr };
HWND _hwndToast;
};

}
Expand Down
5 changes: 2 additions & 3 deletions src/Magpie.App/ToastPage.idl
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
namespace Magpie.App {
runtimeclass ToastPage : Windows.UI.Xaml.Controls.Page {
ToastPage();
ToastPage(UInt64 hwndToast);

Microsoft.UI.Xaml.Controls.TeachingTip ShowMessage(String message);
void HideMessage();
void ShowMessageOnWindow(String message, UInt64 hwndTarget);

// https://github.com/microsoft/microsoft-ui-xaml/issues/7579
void UnloadObject(Windows.UI.Xaml.DependencyObject object);
Expand Down
78 changes: 5 additions & 73 deletions src/Magpie.App/ToastService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include "Utils.h"
#include <windows.ui.xaml.hosting.desktopwindowxamlsource.h>
#include <winrt/Windows.UI.Xaml.Hosting.h>
#include "Win32Utils.h"
#include "XamlUtils.h"

using namespace winrt;
Expand Down Expand Up @@ -38,77 +37,10 @@ void ToastService::Uninitialize() noexcept {
_toastThread.join();
}

fire_and_forget ToastService::ShowMessageOnWindow(std::wstring_view message, HWND hWnd) noexcept {
auto that = this;
std::wstring capturedMessage(message);

// 切换到 toast 线程
co_await _Dispatcher();

that->_toastPage.HideMessage();

co_await resume_foreground(that->_dispatcher, CoreDispatcherPriority::Low);

RECT frameRect;
if (!Win32Utils::GetWindowFrameRect(hWnd, frameRect)) {
co_return;
}

// 更改所有者关系使弹窗始终在 hWnd 上方
SetWindowLongPtr(that->_hwndToast, GWLP_HWNDPARENT, (LONG_PTR)hWnd);
// _hwndToast 的输入已被附加到了 hWnd 上,这是所有者窗口的默认行为,但我们不需要。
// 见 https://devblogs.microsoft.com/oldnewthing/20130412-00/?p=4683
AttachThreadInput(
GetCurrentThreadId(),
GetWindowThreadProcessId(hWnd, nullptr),
FALSE
);

SetWindowPos(
that->_hwndToast,
NULL,
(frameRect.left + frameRect.right) / 2,
(frameRect.top + frameRect.bottom * 4) / 5,
0,
0,
SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW
);

MUXC::TeachingTip teachingTip = that->_toastPage.ShowMessage(capturedMessage);

// 定期更新弹窗位置
RECT prevframeRect{};
do {
co_await resume_background();
// 等待一帧的时间可以使弹窗的移动更平滑
DwmFlush();
co_await that->_dispatcher;

if (!IsWindow(hWnd)) {
break;
}

RECT frameRect;
if (!Win32Utils::GetWindowFrameRect(hWnd, frameRect)) {
break;
}

// 窗口没有移动则无需更新
if (frameRect == prevframeRect) {
continue;
}
prevframeRect = frameRect;

SetWindowPos(
that->_hwndToast,
NULL,
(frameRect.left + frameRect.right) / 2,
(frameRect.top + frameRect.bottom * 4) / 5,
0,
0,
SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW
);
} while (teachingTip.IsOpen());
void ToastService::ShowMessageOnWindow(std::wstring_view message, HWND hwndTarget) noexcept {
_Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this, message(std::wstring(message)), hwndTarget]() {
_toastPage.ShowMessageOnWindow(message, (uint64_t)hwndTarget);
});
}

void ToastService::_ToastThreadProc() noexcept {
Expand Down Expand Up @@ -157,7 +89,7 @@ void ToastService::_ToastThreadProc() noexcept {
xamlSourceNative2->get_WindowHandle(&hwndXamlIsland);
SetWindowPos(hwndXamlIsland, NULL, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_SHOWWINDOW);

_toastPage = ToastPage();
_toastPage = ToastPage((uint64_t)_hwndToast);
xamlSource.Content(_toastPage);

_dispatcher = _toastPage.Dispatcher();
Expand Down
2 changes: 1 addition & 1 deletion src/Magpie.App/ToastService.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ToastService {

void Uninitialize() noexcept;

fire_and_forget ShowMessageOnWindow(std::wstring_view message, HWND hWnd) noexcept;
void ShowMessageOnWindow(std::wstring_view message, HWND hwndTarget) noexcept;

private:
ToastService() = default;
Expand Down

0 comments on commit 72d4eca

Please sign in to comment.