Skip to content

Commit

Permalink
feat: 实现显示消息
Browse files Browse the repository at this point in the history
  • Loading branch information
Blinue committed Oct 15, 2024
1 parent 1994f02 commit 7792afd
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 15 deletions.
62 changes: 61 additions & 1 deletion src/Magpie.App/ToastPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,66 @@
#include "ToastPage.g.cpp"
#endif

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

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


IAsyncAction ToastPage::ShowMessage(const hstring& message) {
// !!! HACK !!!
// 重用 TeachingTip 有一个 bug: 前一个 Toast 正在消失时新的 Toast 不会显示。为了
// 规避它,我们每次都创建新的 TeachingTip,但要保留旧对象的引用,因为播放动画时销毁
// 会导致崩溃。oldToastTeachingTip 的生存期可确保动画播放完毕。
MUXC::TeachingTip oldTeachingTip = MessageTeachingTip();
if (oldTeachingTip) {
UnloadObject(oldTeachingTip);
}

weak_ref<MUXC::TeachingTip> weakTeachingTip;
{
// 创建新的 TeachingTip
MUXC::TeachingTip newTeachingTip = FindName(L"MessageTeachingTip").as<MUXC::TeachingTip>();
MessageTextBlock().Text(message);
newTeachingTip.IsOpen(true);

// !!! HACK !!!
// 移除关闭按钮。必须在模板加载完成后做,TeachingTip 没有 Opening 事件,但可以监听 MessageTextBlock 的
// LayoutUpdated 事件,它在 TeachingTip 显示前必然会被引发。
MessageTextBlock().LayoutUpdated([weak(weak_ref(newTeachingTip))](IInspectable const&, IInspectable const&) {
auto teachingTip = weak.get();
if (!teachingTip) {
return;
}

IControlProtected protectedAccessor = teachingTip.as<IControlProtected>();

// 隐藏关闭按钮
if (DependencyObject closeButton = protectedAccessor.GetTemplateChild(L"AlternateCloseButton")) {
closeButton.as<FrameworkElement>().Visibility(Visibility::Collapsed);
}

// 减小 Flyout 尺寸
if (DependencyObject container = protectedAccessor.GetTemplateChild(L"TailOcclusionGrid")) {
container.as<FrameworkElement>().MinWidth(0.0);
}
});

weakTeachingTip = newTeachingTip;
}

auto weakThis = get_weak();
CoreDispatcher dispatcher = Dispatcher();
// 显示时长固定 2 秒
co_await 2s;
co_await dispatcher;

if (weakThis.get()) {
MUXC::TeachingTip curTeachingTip = MessageTeachingTip();
if (curTeachingTip == weakTeachingTip.get()) {
// 如果已经显示新的 Toast 则无需关闭,因为 newTeachingTip 已被卸载(但仍在生存期内)
curTeachingTip.IsOpen(false);
}
}
}

}
14 changes: 9 additions & 5 deletions src/Magpie.App/ToastPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
#include "ToastPage.g.h"

namespace winrt::Magpie::App::implementation {
struct ToastPage : ToastPageT<ToastPage> {

};

struct ToastPage : ToastPageT<ToastPage> {
IAsyncAction ShowMessage(const hstring& message);
};

}

namespace winrt::Magpie::App::factory_implementation {
struct ToastPage : ToastPageT<ToastPage, implementation::ToastPage> {
};

struct ToastPage : ToastPageT<ToastPage, implementation::ToastPage> {
};

}
3 changes: 2 additions & 1 deletion src/Magpie.App/ToastPage.idl
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
namespace Magpie.App {
[default_interface]
runtimeclass ToastPage : Windows.UI.Xaml.Controls.Page {
ToastPage();

Windows.Foundation.IAsyncAction ShowMessage(String message);

// https://github.com/microsoft/microsoft-ui-xaml/issues/7579
void UnloadObject(Windows.UI.Xaml.DependencyObject object);
Expand Down
4 changes: 2 additions & 2 deletions src/Magpie.App/ToastPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
mc:Ignorable="d">
<muxc:TeachingTip x:Name="MessageTeachingTip"
x:Load="False"
PreferredPlacement="Center"
ShouldConstrainToRootBounds="False">
<TextBlock x:Name="MessageTextBlock"
Text="test测试测试测试测试测试测试" />
<TextBlock x:Name="MessageTextBlock" />
</muxc:TeachingTip>
</Page>
29 changes: 23 additions & 6 deletions src/Magpie.App/ToastService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ void ToastService::Uninitialize() noexcept {
_toastThread.join();
}

void ToastService::ShowMessage(std::wstring_view message) noexcept {
_Dispatcher().TryRunAsync(CoreDispatcherPriority::Normal, [this, captured(std::wstring(message))]() {
_toastPage.ShowMessage(captured);
});
}

void ToastService::_ToastThreadProc() noexcept {
#ifdef _DEBUG
SetThreadDescription(GetCurrentThread(), L"Toast 线程");
Expand All @@ -58,11 +64,11 @@ void ToastService::_ToastThreadProc() noexcept {
// 创建窗口失败也应进入消息循环。Win10 中关闭任意线程的 DesktopWindowXamlSource 都会使主线程会崩溃,
// 在程序退出前,xamlSource 不能析构。见 https://github.com/microsoft/terminal/pull/15397
HWND hwndToast = CreateWindowEx(
WS_EX_NOREDIRECTIONBITMAP | WS_EX_TOOLWINDOW,
WS_EX_TOPMOST | WS_EX_NOREDIRECTIONBITMAP | WS_EX_TOOLWINDOW,
CommonSharedConstants::TOAST_WINDOW_CLASS_NAME,
L"Toast",
WS_POPUP | WS_VISIBLE,
200, 200, 0, 0,
0, 0, 0, 0,
NULL,
NULL,
wil::GetModuleInstanceHandle(),
Expand All @@ -80,11 +86,13 @@ void ToastService::_ToastThreadProc() noexcept {
xamlSourceNative2->get_WindowHandle(&hwndXamlIsland);
SetWindowPos(hwndXamlIsland, NULL, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_SHOWWINDOW);

ToastPage toastPage;
xamlSource.Content(toastPage);
_toastPage = ToastPage();
xamlSource.Content(_toastPage);

auto tt = toastPage.FindName(L"MessageTeachingTip").as<MUXC::TeachingTip>();
tt.IsOpen(true);
_dispatcher = _toastPage.Dispatcher();
// 如果主线程正在等待则唤醒主线程
_dispatcherInitialized.store(true, std::memory_order_release);
_dispatcherInitialized.notify_one();

MSG msg;
while (GetMessage(&msg, nullptr, 0, 0)) {
Expand All @@ -110,4 +118,13 @@ void ToastService::_ToastThreadProc() noexcept {
xamlSource.Close();
}

const CoreDispatcher& ToastService::_Dispatcher() noexcept {
if (!_dispatcherInitializedCache) {
_dispatcherInitialized.wait(false, std::memory_order_acquire);
_dispatcherInitializedCache = true;
}

return _dispatcher;
}

}
11 changes: 11 additions & 0 deletions src/Magpie.App/ToastService.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,23 @@ class ToastService {

void Uninitialize() noexcept;

void ShowMessage(std::wstring_view message) noexcept;

private:
ToastService() = default;

void _ToastThreadProc() noexcept;

// 确保 _dispatcher 完成初始化
const CoreDispatcher& _Dispatcher() noexcept;

std::thread _toastThread;

ToastPage _toastPage{ nullptr };
CoreDispatcher _dispatcher{ nullptr };
std::atomic<bool> _dispatcherInitialized = false;
// 只能在主线程访问,省下检查 _dispatcherInitialized 的开销
bool _dispatcherInitializedCache = false;
};

}

0 comments on commit 7792afd

Please sign in to comment.