From 9445badf60545ba35869e4b20be8d1ed8c079fef Mon Sep 17 00:00:00 2001 From: Kan-Ru Chen Date: Mon, 9 Sep 2024 08:47:48 +0900 Subject: [PATCH] refactor: reimplement MessageWindow in rust --- ChewingTextService/ChewingTextService.cpp | 40 +- ChewingTextService/ChewingTextService.h | 10 +- libIME/CMakeLists.txt | 23 +- libIME/Cargo.lock | 130 ++++++ libIME/Cargo.toml | 15 + libIME/ImeModule.cpp | 4 + libIME/MessageWindow.cpp | 204 ---------- libIME/MessageWindow.h | 64 --- libIME/idl/libime2.idl | 53 +++ libIME/src/lib.rs | 10 +- libIME/src/window/message_window.rs | 463 ++++++++++++++++++++++ libIME/src/window/mod.rs | 279 +++++++++++++ 12 files changed, 992 insertions(+), 303 deletions(-) delete mode 100644 libIME/MessageWindow.cpp delete mode 100644 libIME/MessageWindow.h create mode 100644 libIME/idl/libime2.idl create mode 100644 libIME/src/window/message_window.rs create mode 100644 libIME/src/window/mod.rs diff --git a/ChewingTextService/ChewingTextService.cpp b/ChewingTextService/ChewingTextService.cpp index 9aa31e4..395671d 100644 --- a/ChewingTextService/ChewingTextService.cpp +++ b/ChewingTextService/ChewingTextService.cpp @@ -27,11 +27,14 @@ #include #include #include +#include +#include #include #include "ChewingImeModule.h" #include "resource.h" +#include "libime2.h" using namespace std; @@ -70,7 +73,6 @@ TextService::TextService(ImeModule* module): shapeMode_(-1), outputSimpChinese_(false), lastKeyDownCode_(0), - messageWindow_(NULL), messageTimerId_(0), candidateWindow_(NULL), imeModeIcon_(NULL), @@ -722,9 +724,10 @@ void TextService::applyConfig() { ::DeleteObject(font_); // delete old font lf.lfHeight = cfg.fontSize; // apply the new size font_ = CreateFontIndirect(&lf); // create new font - if(messageWindow_) - messageWindow_->setFont(font_); - // messageWindow_->setFontSize(cfg.fontSize); + if(messageWindow_) { + // messageWindow_->setFont(font_); + messageWindow_->setFontSize(cfg.fontSize); + } if(candidateWindow_) { candidateWindow_->setFont(font_); candidateWindow_->setFontSize(static_cast(cfg.fontSize)); @@ -833,10 +836,11 @@ void TextService::showMessage(Ime::EditSession* session, std::wstring message, i // remove previous message if there's any hideMessage(); // FIXME: reuse the window whenever possible - messageWindow_ = new Ime::MessageWindow(this, session); - messageWindow_->setFont(font_); + HWND parent = this->compositionWindow(session); + messageWindow_ = nullptr; + CreateMessageWindow(parent, messageWindow_.put_void()); messageWindow_->setFontSize(config().fontSize); - messageWindow_->setText(message); + messageWindow_->setText(message.c_str()); int x = 0, y = 0; if(isComposing()) { @@ -849,7 +853,7 @@ void TextService::showMessage(Ime::EditSession* session, std::wstring message, i messageWindow_->move(x, y); messageWindow_->show(); - messageTimerId_ = ::SetTimer(messageWindow_->hwnd(), 1, duration * 1000, (TIMERPROC)TextService::onMessageTimeout); + messageTimerId_ = ::SetTimer(messageWindow_->hwnd(), 1, duration * 1000, nullptr); } void TextService::hideMessage() { @@ -858,27 +862,11 @@ void TextService::hideMessage() { messageTimerId_ = 0; } if(messageWindow_) { - delete messageWindow_; - messageWindow_ = NULL; - } -} - -// called when the message window timeout -void TextService::onMessageTimeout() { - hideMessage(); -} - -// static -void CALLBACK TextService::onMessageTimeout(HWND hwnd, UINT msg, UINT_PTR id, DWORD time) { - Ime::MessageWindow* messageWindow = (Ime::MessageWindow*)Ime::Window::fromHwnd(hwnd); - assert(messageWindow); - if(messageWindow) { - TextService* pThis = (Chewing::TextService*)messageWindow->textService(); - pThis->onMessageTimeout(); + messageWindow_->destroy(); + messageWindow_ = nullptr; } } - void TextService::updateLangButtons() { if(!chewingContext_) return; diff --git a/ChewingTextService/ChewingTextService.h b/ChewingTextService/ChewingTextService.h index a21ac81..c432d72 100644 --- a/ChewingTextService/ChewingTextService.h +++ b/ChewingTextService/ChewingTextService.h @@ -22,13 +22,17 @@ #include #include -#include #include #include #include #include "ChewingImeModule.h" #include +#include +#include + +#include "libime2.h" + namespace Chewing { class TextService: public Ime::TextService { @@ -91,8 +95,6 @@ class TextService: public Ime::TextService { // message window void showMessage(Ime::EditSession* session, std::wstring message, int duration = 3); void hideMessage(); - void onMessageTimeout(); - static void CALLBACK onMessageTimeout(HWND hwnd, UINT msg, UINT_PTR id, DWORD time); void updateLangButtons(); // update status of language bar buttons @@ -110,7 +112,7 @@ class TextService: public Ime::TextService { ChewingContext* chewingContext_; Ime::CandidateWindow* candidateWindow_; bool showingCandidates_; - Ime::MessageWindow* messageWindow_; + winrt::com_ptr messageWindow_; UINT messageTimerId_; HFONT font_; diff --git a/libIME/CMakeLists.txt b/libIME/CMakeLists.txt index 26f6a35..fea943a 100644 --- a/libIME/CMakeLists.txt +++ b/libIME/CMakeLists.txt @@ -20,6 +20,17 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_SIMULATE_ID MATCHES "MSVC corrosion_set_env_vars(libime2 "CFLAGS=-EHsc" "CXXFLAGS=-EHsc") endif() +find_program(MIDL midl) +add_custom_command( + OUTPUT + dlldata.c + libime2.h + libime2_i.c + libime2_p.c + COMMAND MIDL ${CMAKE_CURRENT_SOURCE_DIR}/idl/libime2.idl + MAIN_DEPENDENCY idl/libime2.idl +) + add_library(libIME_static STATIC # Core TSF part ImeModule.cpp @@ -49,14 +60,19 @@ add_library(libIME_static STATIC Window.h ImeWindow.cpp ImeWindow.h - MessageWindow.cpp - MessageWindow.h CandidateWindow.h CandidateWindow.cpp NinePatch.h NinePatch.cpp + + dlldata.c + libime2.h + libime2_i.c + libime2_p.c ) +target_include_directories(libIME_static PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) + target_link_libraries(libIME_static PUBLIC libime2_bridge PUBLIC shlwapi.lib @@ -64,4 +80,7 @@ target_link_libraries(libIME_static PUBLIC d3d11.lib PUBLIC dwrite.lib PUBLIC dcomp.lib + + PUBLIC Propsys.lib + PUBLIC RuntimeObject.lib ) \ No newline at end of file diff --git a/libIME/Cargo.lock b/libIME/Cargo.lock index 0eb98f2..3f02ce6 100644 --- a/libIME/Cargo.lock +++ b/libIME/Cargo.lock @@ -48,6 +48,8 @@ dependencies = [ "log", "nine_patch_drawable", "win_dbg_logger", + "windows", + "windows-core", ] [[package]] @@ -120,3 +122,131 @@ checksum = "7d1b4c22244dc27534d81e2f6fc3efd6b20e50c010f177efc20b719ec759a779" dependencies = [ "log", ] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/libIME/Cargo.toml b/libIME/Cargo.toml index 9b3500b..306cf95 100644 --- a/libIME/Cargo.toml +++ b/libIME/Cargo.toml @@ -11,3 +11,18 @@ cxx = { version = "1.0.128", features = ["c++17"] } log = "0.4.22" nine_patch_drawable = "0.1.0" win_dbg_logger = "0.1.0" +windows-core = "0.58.0" +windows = { version = "0.58.0", features = [ + "implement", + "Foundation_Numerics", + "Win32_Graphics_Direct2D", + "Win32_Graphics_Direct2D_Common", + "Win32_Graphics_Direct3D", + "Win32_Graphics_Direct3D11", + "Win32_Graphics_DirectComposition", + "Win32_Graphics_DirectWrite", + "Win32_Graphics_Dxgi", + "Win32_Graphics_Dxgi_Common", + "Win32_Graphics_Gdi", + "Win32_UI_WindowsAndMessaging", +] } diff --git a/libIME/ImeModule.cpp b/libIME/ImeModule.cpp index 5cb1462..67bb4cd 100644 --- a/libIME/ImeModule.cpp +++ b/libIME/ImeModule.cpp @@ -33,6 +33,8 @@ #include "TextService.h" #include "DisplayAttributeProvider.h" +#include "libime2.h" + using namespace std; namespace Ime { @@ -64,7 +66,9 @@ ImeModule::ImeModule(HMODULE module, const CLSID& textServiceClsid): textServiceClsid_(textServiceClsid), refCount_(1) { + LibIME2Init(); Window::registerClass(hInstance_); + ImeWindowRegisterClass(hInstance_); // regiser default display attributes inputAttrib_ = new DisplayAttributeInfo(g_inputDisplayAttributeGuid); diff --git a/libIME/MessageWindow.cpp b/libIME/MessageWindow.cpp deleted file mode 100644 index a0ca21a..0000000 --- a/libIME/MessageWindow.cpp +++ /dev/null @@ -1,204 +0,0 @@ -// -// Copyright (C) 2013 Hong Jen Yee (PCMan) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Library General Public -// License as published by the Free Software Foundation; either -// version 2 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Library General Public License for more details. -// -// You should have received a copy of the GNU Library General Public -// License along with this library; if not, write to the -// Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, -// Boston, MA 02110-1301, USA. -// - -#include "MessageWindow.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "DrawUtils.h" -#include "TextService.h" - -using winrt::check_hresult; -using winrt::com_ptr; - -namespace Ime { - -MessageWindow::MessageWindow(TextService* service, EditSession* session) - : ImeWindow(service) { - HWND parent = service->compositionWindow(session); - create(parent, WS_POPUP | WS_CLIPCHILDREN, - WS_EX_TOOLWINDOW | WS_EX_TOPMOST); - - check_hresult( - D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, factory_.put())); - - com_ptr d3device; - com_ptr dxdevice; - com_ptr adapter; - com_ptr factory; - com_ptr device; - com_ptr surface; - com_ptr bitmap; - check_hresult(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, - D3D11_CREATE_DEVICE_BGRA_SUPPORT, nullptr, - 0, D3D11_SDK_VERSION, d3device.put(), - nullptr, nullptr)); - dxdevice = d3device.as(); - check_hresult(factory_->CreateDevice(dxdevice.get(), device.put())); - check_hresult(device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, - target_.put())); - - DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; - swapChainDesc.Width = 0; - swapChainDesc.Height = 0; - swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - swapChainDesc.Stereo = false; - swapChainDesc.SampleDesc.Count = 1; // don't use multi-sampling - swapChainDesc.SampleDesc.Quality = 0; - swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - swapChainDesc.BufferCount = 2; // use double buffering to enable flip - swapChainDesc.Scaling = DXGI_SCALING_STRETCH; - swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; - swapChainDesc.Flags = 0; - - check_hresult(dxdevice->GetAdapter(adapter.put())); - check_hresult(adapter->GetParent(__uuidof(factory), factory.put_void())); - - check_hresult(factory->CreateSwapChainForHwnd(d3device.get(), hwnd_, - &swapChainDesc, nullptr, - nullptr, swapChain_.put())); - check_hresult( - swapChain_->GetBuffer(0, __uuidof(surface), surface.put_void())); - auto bitmap_props = D2D1::BitmapProperties1( - D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, - D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)); - check_hresult(target_->CreateBitmapFromDxgiSurface( - surface.get(), &bitmap_props, bitmap.put())); - target_->SetTarget(bitmap.get()); -} - -MessageWindow::~MessageWindow(void) {} - -// virtual -void MessageWindow::recalculateSize() { - com_ptr pDwriteFactory; - com_ptr pTextFormat; - DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory1), - reinterpret_cast(pDwriteFactory.put())); - pDwriteFactory->CreateTextFormat( - L"Segoe UI", nullptr, DWRITE_FONT_WEIGHT_NORMAL, - DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, fontSize_, L"", - pTextFormat.put()); - com_ptr pTextLayout; - DWRITE_TEXT_METRICS metrics; - D2D1_POINT_2F origin; - pDwriteFactory->CreateTextLayout(text_.c_str(), text_.length(), - pTextFormat.get(), D2D1::FloatMax(), - D2D1::FloatMax(), pTextLayout.put()); - pTextLayout->GetMetrics(&metrics); - - auto width = metrics.width + margin_ * 2; - auto height = metrics.height + margin_ * 2; - SetWindowPos(hwnd_, HWND_TOPMOST, 0, 0, width, height, - SWP_NOACTIVATE | SWP_NOMOVE); - - resizeSwapChain(width, height); -} - -void MessageWindow::resizeSwapChain(int width, int height) { - com_ptr surface; - com_ptr bitmap; - - target_->SetTarget(nullptr); - swapChain_->ResizeBuffers(0, width, height, DXGI_FORMAT_B8G8R8A8_UNORM, 0); - check_hresult( - swapChain_->GetBuffer(0, __uuidof(surface), surface.put_void())); - auto bitmap_props = D2D1::BitmapProperties1( - D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, - D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)); - check_hresult(target_->CreateBitmapFromDxgiSurface( - surface.get(), &bitmap_props, bitmap.put())); - target_->SetTarget(bitmap.get()); -} - -void MessageWindow::setText(std::wstring text) { - // FIXMEl: use different appearance under immersive mode - text_ = text; - recalculateSize(); - if (IsWindowVisible(hwnd_)) { - InvalidateRect(hwnd_, NULL, TRUE); - } -} - -LRESULT MessageWindow::wndProc(UINT msg, WPARAM wp, LPARAM lp) { - switch (msg) { - case WM_PAINT: - onPaint(wp, lp); - break; - case WM_MOUSEACTIVATE: - return MA_NOACTIVATE; - default: - return ImeWindow::wndProc(msg, wp, lp); - } - return 0; -} - -void MessageWindow::onPaint(WPARAM wp, LPARAM lp) { - RECT rc, textrc = {0}; - GetClientRect(hwnd_, &rc); - - target_->BeginDraw(); - - com_ptr pTextBrush; - - if (isImmersive()) { - // draw a flat black border in Windows 8 app immersive mode - com_ptr pBrush; - check_hresult(target_->CreateSolidColorBrush( - D2D1::ColorF(D2D1::ColorF::Black), pBrush.put())); - check_hresult(target_->CreateSolidColorBrush( - D2D1::ColorF(GetSysColor(COLOR_WINDOWTEXT)), pTextBrush.put())); - target_->Clear(D2D1::ColorF(GetSysColor(COLOR_WINDOW))); - target_->DrawRectangle( - D2D1::RectF(rc.left, rc.top, rc.right, rc.bottom), pBrush.get(), - 3.0f); - } else { - check_hresult(target_->CreateSolidColorBrush( - D2D1::ColorF(GetSysColor(COLOR_INFOTEXT)), pTextBrush.put())); - target_->Clear(D2D1::ColorF(GetSysColor(COLOR_INFOBK))); - ::FillSolidRectD2D(target_.get(), rc.left, rc.top, rc.right, rc.bottom, - GetSysColor(COLOR_INFOBK)); - ::Draw3DBorderD2D(target_.get(), &rc, GetSysColor(COLOR_3DFACE), 0, 1); - } - - com_ptr pDwriteFactory; - com_ptr pTextFormat; - DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory1), - reinterpret_cast(pDwriteFactory.put())); - pDwriteFactory->CreateTextFormat( - L"Segoe UI", nullptr, DWRITE_FONT_WEIGHT_NORMAL, - DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, fontSize_, L"", - pTextFormat.put()); - target_->DrawText( - text_.c_str(), text_.length(), pTextFormat.get(), - D2D1::RectF(margin_, margin_, D2D1::FloatMax(), D2D1::FloatMax()), - pTextBrush.get()); - - check_hresult(target_->EndDraw()); - check_hresult(swapChain_->Present(1, 0)); - ValidateRect(hwnd_, nullptr); -} - -} // namespace Ime diff --git a/libIME/MessageWindow.h b/libIME/MessageWindow.h deleted file mode 100644 index 6a514b0..0000000 --- a/libIME/MessageWindow.h +++ /dev/null @@ -1,64 +0,0 @@ -// -// Copyright (C) 2013 Hong Jen Yee (PCMan) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Library General Public -// License as published by the Free Software Foundation; either -// version 2 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Library General Public License for more details. -// -// You should have received a copy of the GNU Library General Public -// License along with this library; if not, write to the -// Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, -// Boston, MA 02110-1301, USA. -// - -#ifndef IME_MESSAGE_WINDOW_H -#define IME_MESSAGE_WINDOW_H - -#include -#include -#include -#include - -#include - -#include "EditSession.h" -#include "ImeWindow.h" - -namespace Ime { - -class TextService; - -class MessageWindow : public ImeWindow { - public: - MessageWindow(TextService* service, EditSession* session = NULL); - virtual ~MessageWindow(void); - - std::wstring text() { return text_; } - void setText(std::wstring text); - - TextService* textService() { return textService_; } - - virtual void recalculateSize(); - - protected: - LRESULT wndProc(UINT msg, WPARAM wp, LPARAM lp); - void onPaint(WPARAM wp, LPARAM lp); - void resizeSwapChain(int width, int height); - - private: - winrt::com_ptr target_; - winrt::com_ptr swapChain_; - winrt::com_ptr factory_; - - std::wstring text_; -}; - -} // namespace Ime - -#endif diff --git a/libIME/idl/libime2.idl b/libIME/idl/libime2.idl new file mode 100644 index 0000000..d84f53d --- /dev/null +++ b/libIME/idl/libime2.idl @@ -0,0 +1,53 @@ +import "unknwn.idl"; + +[ +object, +uuid(d73284e1-59aa-42ef-84ca-1633beca464b), +local +] +interface IWindow : IUnknown +{ + HWND hwnd(); + boolean create(HWND parent, DWORD style, [defaultvalue(0)] DWORD exStyle); + void destroy(); + boolean isVisible(); + boolean isWindow(); + void move(int x, int y); + void size([out] int *width, [out] int *height); + void resize(int width, int height); + void clientRect([out] RECT *rect); + void rect([out] RECT *rect); + void show(); + void hide(); + void refresh(); + + LRESULT wndProc(UINT msg, WPARAM wp, LPARAM lp); +} + +[ +object, +uuid(d4eee9d6-60a0-4169-b3b8-d99f66ebe61a), +local +] +interface ICandidateWindow : IWindow +{ + void setFontSize(float size); +} + +[ +object, +uuid(7375ef7b-4564-46eb-b8d1-e27228428623), +local +] +interface IMessageWindow : IWindow +{ + const unsigned long ID_TIMEOUT = 1; + void setFontSize(DWORD fontSize); + void setText(LPCWSTR text); +} + +[local] void LibIME2Init(); +[local] void CreateImeWindow([out] void **window); +[local] void CreateMessageWindow(HWND parent, [out] void **messagewindow); +[local] IWindow *ImeWindowFromHwnd(HWND hwnd); +[local] boolean ImeWindowRegisterClass(HINSTANCE hinstance); \ No newline at end of file diff --git a/libIME/src/lib.rs b/libIME/src/lib.rs index 07c8057..96906b0 100644 --- a/libIME/src/lib.rs +++ b/libIME/src/lib.rs @@ -1,3 +1,5 @@ +mod window; + #[cxx::bridge] mod ffi { struct RectF { @@ -39,9 +41,6 @@ use nine_patch_drawable::{PatchKind, Section}; pub struct NinePatchDrawable(nine_patch_drawable::NinePatchDrawable); pub fn nine_patch_uninit() -> Box { - // FIXME: move to rustlib::init() - win_dbg_logger::init(); - win_dbg_logger::rust_win_dbg_logger_init_debug(); make_nine_patch(&[], 0, 0, 0) } @@ -102,3 +101,8 @@ pub fn nine_patch_scale_to( }) .collect() } + +#[no_mangle] +unsafe extern "C" fn LibIME2Init() { + win_dbg_logger::rust_win_dbg_logger_init_debug(); +} diff --git a/libIME/src/window/message_window.rs b/libIME/src/window/message_window.rs new file mode 100644 index 0000000..068e4df --- /dev/null +++ b/libIME/src/window/message_window.rs @@ -0,0 +1,463 @@ +use core::f32; +use std::{ + cell::RefCell, + ffi::{c_int, c_uint, c_void}, + ops::Deref, +}; + +use windows::{ + core::{h, implement, interface, w, ComObject, ComObjectInner, Interface, PCWSTR}, + Foundation::Numerics::Matrix3x2, + Win32::{ + Foundation::{HWND, LPARAM, LRESULT, RECT, WPARAM}, + Graphics::{ + Direct2D::{ + Common::{D2D1_ALPHA_MODE_IGNORE, D2D1_COLOR_F, D2D1_PIXEL_FORMAT, D2D_RECT_F}, + D2D1CreateFactory, ID2D1DeviceContext, ID2D1Factory1, ID2D1SolidColorBrush, + D2D1_BITMAP_OPTIONS_CANNOT_DRAW, D2D1_BITMAP_OPTIONS_TARGET, + D2D1_BITMAP_PROPERTIES1, D2D1_BRUSH_PROPERTIES, D2D1_DEVICE_CONTEXT_OPTIONS_NONE, + D2D1_DRAW_TEXT_OPTIONS_NONE, D2D1_FACTORY_OPTIONS, + D2D1_FACTORY_TYPE_SINGLE_THREADED, D2D1_UNIT_MODE_DIPS, + }, + Direct3D::{D3D_DRIVER_TYPE, D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP}, + Direct3D11::{ + D3D11CreateDevice, ID3D11Device, D3D11_CREATE_DEVICE_BGRA_SUPPORT, + D3D11_SDK_VERSION, + }, + DirectWrite::{ + DWriteCreateFactory, IDWriteFactory1, IDWriteTextFormat, + DWRITE_FACTORY_TYPE_SHARED, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, + DWRITE_FONT_WEIGHT_NORMAL, DWRITE_MEASURING_MODE_NATURAL, DWRITE_TEXT_METRICS, + }, + Dxgi::{ + Common::{DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_SAMPLE_DESC}, + IDXGIDevice, IDXGIFactory2, IDXGISurface, IDXGISwapChain1, DXGI_ERROR_UNSUPPORTED, + DXGI_PRESENT, DXGI_SWAP_CHAIN_DESC1, DXGI_SWAP_CHAIN_FLAG, + DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, DXGI_USAGE_RENDER_TARGET_OUTPUT, + }, + Gdi::{ + BeginPaint, EndPaint, GetSysColor, COLOR_INFOBK, COLOR_INFOTEXT, PAINTSTRUCT, + SYS_COLOR_INDEX, + }, + }, + UI::WindowsAndMessaging::{ + DefWindowProcW, GetClientRect, KillTimer, SetWindowPos, HWND_TOPMOST, SWP_NOACTIVATE, + SWP_NOMOVE, WM_NCDESTROY, WM_PAINT, WM_TIMER, WS_CLIPCHILDREN, WS_EX_TOOLWINDOW, + WS_EX_TOPMOST, WS_POPUP, + }, + }, +}; +use windows_core::{Result, HSTRING}; + +use super::{IWindow, IWindow_Impl, IWindow_Vtbl, Window}; + +const ID_TIMEOUT: usize = 1; + +#[interface("7375ef7b-4564-46eb-b8d1-e27228428623")] +unsafe trait IMessageWindow: IWindow { + fn set_font_size(&self, font_size: u32); + fn set_text(&self, text: PCWSTR); +} + +#[derive(Debug)] +#[implement(IMessageWindow, IWindow)] +struct MessageWindow { + text: RefCell, + factory: ID2D1Factory1, + dwrite_factory: IDWriteFactory1, + text_format: RefCell, + target: RefCell>, + swapchain: RefCell>, + brush: RefCell>, + dpi: f32, + window: ComObject, +} + +impl MessageWindow { + fn recalculate_size(&self) -> Result<()> { + let text_layout = unsafe { + self.dwrite_factory.CreateTextLayout( + self.text.borrow().as_wide(), + self.text_format.borrow().deref(), + f32::MAX, + f32::MAX, + )? + }; + let mut metrics = DWRITE_TEXT_METRICS::default(); + unsafe { text_layout.GetMetrics(&mut metrics).unwrap() }; + + // FIXME + let margin = 5.0; + let width = metrics.width + margin * 2.0; + let height = metrics.height + margin * 2.0; + unsafe { + SetWindowPos( + self.window.hwnd.get(), + HWND_TOPMOST, + 0, + 0, + width as i32, + height as i32, + SWP_NOACTIVATE | SWP_NOMOVE, + )? + }; + self.resize_swap_chain(width as u32, height as u32)?; + + Ok(()) + } + + fn resize_swap_chain(&self, width: u32, height: u32) -> Result<()> { + let target = self.target.borrow(); + let swapchain = self.swapchain.borrow(); + + if target.is_some() { + let target = target.as_ref().unwrap(); + let swapchain = swapchain.as_ref().unwrap(); + unsafe { target.SetTarget(None) }; + + if unsafe { + swapchain + .ResizeBuffers( + 0, + width, + height, + DXGI_FORMAT_UNKNOWN, + DXGI_SWAP_CHAIN_FLAG(0), + ) + .is_ok() + } { + create_swapchain_bitmap(swapchain, target)?; + } else { + self.target.take(); + self.swapchain.take(); + self.brush.take(); + } + + self.on_paint()?; + } + + Ok(()) + } + + fn on_paint(&self) -> Result<()> { + let create_target = self.target.borrow().is_none(); + if create_target { + let device = create_device()?; + let target = create_render_target(&self.factory, &device)?; + unsafe { target.SetDpi(self.dpi, self.dpi) }; + + let swapchain = create_swapchain(&device, self.window.hwnd.get())?; + create_swapchain_bitmap(&swapchain, &target)?; + + self.brush + .replace(create_brush(&target, create_color(COLOR_INFOTEXT)).ok()); + self.target.replace(Some(target)); + self.swapchain.replace(Some(swapchain)); + } + let target = self.target.borrow(); + let swapchain = self.swapchain.borrow(); + let target = target.as_ref().unwrap(); + unsafe { + let mut rc = RECT::default(); + GetClientRect(self.window.hwnd.get(), &mut rc)?; + + target.BeginDraw(); + target.Clear(Some(&create_color(COLOR_INFOBK))); + + target.DrawText( + self.text.borrow().as_wide(), + self.text_format.borrow().deref(), + &D2D_RECT_F { + left: 5.0, + top: 5.0, + right: f32::MAX, + bottom: f32::MAX, + }, + self.brush.borrow().as_ref().unwrap(), + D2D1_DRAW_TEXT_OPTIONS_NONE, + DWRITE_MEASURING_MODE_NATURAL, + ); + target.EndDraw(None, None)?; + + if swapchain + .as_ref() + .unwrap() + .Present(1, DXGI_PRESENT(0)) + .is_err() + { + _ = target; + _ = swapchain; + self.target.take(); + self.swapchain.take(); + self.brush.take(); + } + } + + Ok(()) + } +} + +#[no_mangle] +unsafe extern "C" fn CreateMessageWindow(parent: HWND, ret: *mut *mut c_void) { + let window = Window::new().into_object(); + window.create( + parent, + (WS_POPUP | WS_CLIPCHILDREN).0, + (WS_EX_TOOLWINDOW | WS_EX_TOPMOST).0, + ); + + let factory: ID2D1Factory1 = D2D1CreateFactory( + D2D1_FACTORY_TYPE_SINGLE_THREADED, + Some(&D2D1_FACTORY_OPTIONS::default()), + ) + .expect("failed to create Direct2D factory"); + + let mut dpi = 0.0; + let mut dpiy = 0.0; + factory.GetDesktopDpi(&mut dpi, &mut dpiy); + + let dwrite_factory: IDWriteFactory1 = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED).unwrap(); + let text_format = dwrite_factory + .CreateTextFormat( + w!("Segoe UI"), + None, + DWRITE_FONT_WEIGHT_NORMAL, + DWRITE_FONT_STYLE_NORMAL, + DWRITE_FONT_STRETCH_NORMAL, + 16.0, + w!(""), + ) + .unwrap(); + + let message_window = MessageWindow { + text: RefCell::new(h!("").to_owned()), + factory, + dwrite_factory, + text_format: RefCell::new(text_format), + target: None.into(), + swapchain: None.into(), + brush: None.into(), + dpi, + window, + } + .into_object(); + Window::register_hwnd(message_window.hwnd(), message_window.to_interface()); + ret.write(message_window.into_interface::().into_raw()) +} + +impl IWindow_Impl for MessageWindow_Impl { + unsafe fn hwnd(&self) -> HWND { + self.window.hwnd() + } + + unsafe fn create(&self, parent: HWND, style: u32, ex_style: u32) -> bool { + self.window.create(parent, style, ex_style) + } + + unsafe fn destroy(&self) { + self.window.destroy() + } + + unsafe fn is_visible(&self) -> bool { + self.window.is_visible() + } + + unsafe fn is_window(&self) -> bool { + self.window.is_window() + } + + unsafe fn r#move(&self, x: c_int, y: c_int) { + self.window.r#move(x, y); + } + + unsafe fn size(&self, width: *mut c_int, height: *mut c_int) { + self.window.size(width, height) + } + + unsafe fn resize(&self, width: c_int, height: c_int) { + self.window.resize(width, height) + } + + unsafe fn client_rect(&self, rect: *mut RECT) { + self.window.client_rect(rect) + } + + unsafe fn rect(&self, rect: *mut RECT) { + self.window.rect(rect) + } + + unsafe fn show(&self) { + self.window.show() + } + + unsafe fn hide(&self) { + self.window.hide() + } + + unsafe fn refresh(&self) { + self.window.refresh() + } + + unsafe fn wnd_proc(&self, msg: c_uint, wp: WPARAM, lp: LPARAM) -> LRESULT { + match msg { + WM_PAINT => { + let mut ps = PAINTSTRUCT::default(); + BeginPaint(self.hwnd(), &mut ps); + let _ = self.on_paint(); + let _ = EndPaint(self.hwnd(), &ps); + LRESULT(0) + } + WM_TIMER => { + if wp.0 == ID_TIMEOUT { + self.hide(); + KillTimer(self.hwnd(), ID_TIMEOUT).expect("failed to kill timer"); + } + LRESULT(0) + } + WM_NCDESTROY => { + self.target.take(); + self.swapchain.take(); + self.brush.take(); + LRESULT(0) + } + _ => DefWindowProcW(self.hwnd(), msg, wp, lp), + } + } +} + +impl IMessageWindow_Impl for MessageWindow_Impl { + unsafe fn set_font_size(&self, font_size: u32) { + self.text_format.replace( + self.dwrite_factory + .CreateTextFormat( + w!("Segoe UI"), + None, + DWRITE_FONT_WEIGHT_NORMAL, + DWRITE_FONT_STYLE_NORMAL, + DWRITE_FONT_STRETCH_NORMAL, + font_size as f32, + self.text.borrow().deref(), + ) + .unwrap(), + ); + } + unsafe fn set_text(&self, text: PCWSTR) { + self.text.replace(text.to_hstring().unwrap()); + self.recalculate_size().unwrap(); + if self.is_visible() { + self.refresh(); + } + } +} + +fn create_color(gdi_color_index: SYS_COLOR_INDEX) -> D2D1_COLOR_F { + let color = unsafe { GetSysColor(gdi_color_index) }; + D2D1_COLOR_F { + r: (color & 0xFF) as f32 / 255.0, + g: ((color >> 8) & 0xFF) as f32 / 255.0, + b: ((color >> 16) & 0xFF) as f32 / 255.0, + a: 1.0, + } +} + +fn create_brush(target: &ID2D1DeviceContext, color: D2D1_COLOR_F) -> Result { + let properties = D2D1_BRUSH_PROPERTIES { + opacity: 0.8, + transform: Matrix3x2::identity(), + }; + + unsafe { target.CreateSolidColorBrush(&color, Some(&properties)) } +} + +fn create_device_with_type(drive_type: D3D_DRIVER_TYPE) -> Result { + let flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + + let mut device = None; + + unsafe { + D3D11CreateDevice( + None, + drive_type, + None, + flags, + None, + D3D11_SDK_VERSION, + Some(&mut device), + None, + None, + ) + .map(|()| device.unwrap()) + } +} + +fn create_device() -> Result { + let mut result = create_device_with_type(D3D_DRIVER_TYPE_HARDWARE); + + if let Err(err) = &result { + if err.code() == DXGI_ERROR_UNSUPPORTED { + result = create_device_with_type(D3D_DRIVER_TYPE_WARP); + } + } + + result +} + +fn create_render_target( + factory: &ID2D1Factory1, + device: &ID3D11Device, +) -> Result { + unsafe { + let d2device = factory.CreateDevice(&device.cast::()?)?; + + let target = d2device.CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE)?; + + target.SetUnitMode(D2D1_UNIT_MODE_DIPS); + + Ok(target) + } +} + +fn get_dxgi_factory(device: &ID3D11Device) -> Result { + let dxdevice = device.cast::()?; + unsafe { dxdevice.GetAdapter()?.GetParent() } +} + +fn create_swapchain_bitmap(swapchain: &IDXGISwapChain1, target: &ID2D1DeviceContext) -> Result<()> { + let surface: IDXGISurface = unsafe { swapchain.GetBuffer(0)? }; + + let props = D2D1_BITMAP_PROPERTIES1 { + pixelFormat: D2D1_PIXEL_FORMAT { + format: DXGI_FORMAT_B8G8R8A8_UNORM, + alphaMode: D2D1_ALPHA_MODE_IGNORE, + }, + dpiX: 96.0, + dpiY: 96.0, + bitmapOptions: D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, + ..Default::default() + }; + + unsafe { + let bitmap = target.CreateBitmapFromDxgiSurface(&surface, Some(&props))?; + target.SetTarget(&bitmap); + }; + + Ok(()) +} + +fn create_swapchain(device: &ID3D11Device, window: HWND) -> Result { + let factory = get_dxgi_factory(device)?; + + let props = DXGI_SWAP_CHAIN_DESC1 { + Format: DXGI_FORMAT_B8G8R8A8_UNORM, + SampleDesc: DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, + BufferCount: 2, + SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, + ..Default::default() + }; + + unsafe { factory.CreateSwapChainForHwnd(device, window, &props, None, None) } +} diff --git a/libIME/src/window/mod.rs b/libIME/src/window/mod.rs new file mode 100644 index 0000000..19cf2a2 --- /dev/null +++ b/libIME/src/window/mod.rs @@ -0,0 +1,279 @@ +use std::{ + cell::{Cell, OnceCell, RefCell}, + collections::HashMap, + ffi::{c_int, c_uint, c_void}, + ptr::null_mut, +}; + +use windows::{ + core::{implement, interface, w, IUnknown, IUnknown_Vtbl, Interface, Weak}, + Win32::{ + Foundation::{FALSE, HINSTANCE, HWND, LPARAM, LRESULT, RECT, WPARAM}, + Graphics::Gdi::{ + GetMonitorInfoW, InvalidateRect, MonitorFromRect, HBRUSH, MONITORINFO, + MONITOR_DEFAULTTONEAREST, + }, + UI::WindowsAndMessaging::{ + CreateWindowExW, DefWindowProcW, DestroyWindow, GetClientRect, GetWindowRect, IsWindow, + IsWindowVisible, LoadCursorW, MoveWindow, RegisterClassExW, SetWindowPos, ShowWindow, + CS_IME, HICON, HWND_TOP, IDC_ARROW, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOZORDER, SW_HIDE, + SW_SHOWNA, WINDOW_EX_STYLE, WINDOW_STYLE, WM_NCDESTROY, WNDCLASSEXW, + }, + }, +}; +use windows_core::PCWSTR; + +mod message_window; + +thread_local! { + static MODULE_HINSTANCE: OnceCell = const { OnceCell::new() }; + static HWND_MAP: RefCell>> = RefCell::new(HashMap::new()); +} + +#[interface("d73284e1-59aa-42ef-84ca-1633beca464b")] +pub(crate) unsafe trait IWindow: IUnknown { + fn hwnd(&self) -> HWND; + fn create(&self, parent: HWND, style: u32, ex_style: u32) -> bool; + fn destroy(&self); + fn is_visible(&self) -> bool; + fn is_window(&self) -> bool; + fn r#move(&self, x: c_int, y: c_int); + fn size(&self, width: *mut c_int, height: *mut c_int); + fn resize(&self, width: c_int, height: c_int); + fn client_rect(&self, rect: *mut RECT); + fn rect(&self, rect: *mut RECT); + fn show(&self); + fn hide(&self); + fn refresh(&self); + fn wnd_proc(&self, msg: c_uint, wp: WPARAM, lp: LPARAM) -> LRESULT; +} + +#[derive(Debug)] +#[implement(IWindow)] +pub(crate) struct Window { + hwnd: Cell, +} + +impl Window { + fn new() -> Window { + Window { + hwnd: Cell::new(HWND::default()), + } + } + fn register_hwnd(hwnd: HWND, window: IWindow) { + let weak_ref = window + .downgrade() + .expect("unable to create weak ref from window"); + HWND_MAP.with_borrow_mut(|hwnd_map| { + hwnd_map.insert(hwnd.0, weak_ref); + }) + } +} + +#[no_mangle] +pub unsafe extern "C" fn CreateImeWindow(ret: *mut *mut c_void) { + let window: IWindow = Window::new().into(); + ret.write(window.into_raw()) +} + +#[no_mangle] +pub unsafe extern "C" fn ImeWindowFromHwnd(hwnd: HWND) -> *mut IWindow { + HWND_MAP.with_borrow(|hwnd_map| { + if let Some(window) = hwnd_map.get(&hwnd.0).and_then(Weak::upgrade) { + window.clone().into_raw().cast() + } else { + null_mut() + } + }) +} + +#[no_mangle] +pub unsafe extern "C" fn ImeWindowRegisterClass(hinstance: HINSTANCE) -> bool { + MODULE_HINSTANCE.with(|hinst_cell| { + let hinst = hinst_cell.get_or_init(|| hinstance); + let mut wc = WNDCLASSEXW::default(); + wc.cbSize = size_of::() as u32; + wc.style = CS_IME; + wc.lpfnWndProc = Some(wnd_proc); + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = *hinst; + wc.hCursor = LoadCursorW(None, IDC_ARROW).expect("failed to load cursor"); + wc.hIcon = HICON::default(); + wc.lpszMenuName = PCWSTR::null(); + wc.lpszClassName = w!("LibIme2Window"); + wc.hbrBackground = HBRUSH::default(); + wc.hIconSm = HICON::default(); + + RegisterClassExW(&wc) > 0 + }) +} + +unsafe extern "system" fn wnd_proc( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + let result = HWND_MAP.with_borrow(|hwnd_map| { + if let Some(window) = hwnd_map.get(&hwnd.0).and_then(Weak::upgrade) { + window.wnd_proc(msg, wparam, lparam) + } else { + DefWindowProcW(hwnd, msg, wparam, lparam) + } + }); + if msg == WM_NCDESTROY { + HWND_MAP.with(|refcell| { + if let Ok(mut hwnd_map) = refcell.try_borrow_mut() { + hwnd_map.remove(&hwnd.0); + } + }); + } + result +} + +impl IWindow_Impl for Window_Impl { + unsafe fn hwnd(&self) -> HWND { + self.hwnd.get() + } + + unsafe fn create(&self, parent: HWND, style: u32, ex_style: u32) -> bool { + MODULE_HINSTANCE.with(|hinst| { + let hwnd = CreateWindowExW( + WINDOW_EX_STYLE(ex_style), + w!("LibIme2Window"), + None, + WINDOW_STYLE(style), + 0, + 0, + 0, + 0, + parent, + None, + hinst.get(), + None, + ); + match hwnd { + Ok(hwnd) => { + self.hwnd.set(hwnd); + true + } + Err(_) => false, + } + }) + } + + unsafe fn destroy(&self) { + if !self.hwnd().is_invalid() { + unsafe { + let _ = DestroyWindow(self.hwnd()); + } + self.hwnd.set(HWND::default()); + } + } + + unsafe fn is_visible(&self) -> bool { + IsWindowVisible(self.hwnd()).as_bool() + } + + unsafe fn is_window(&self) -> bool { + IsWindow(self.hwnd()).as_bool() + } + + unsafe fn r#move(&self, mut x: c_int, mut y: c_int) { + let mut w = 0; + let mut h = 0; + self.size(&mut w, &mut h); + + let mut rc = RECT { + left: x, + top: y, + right: x + w, + bottom: y + h, + }; + + // ensure that the window does not fall outside of the screen. + let monitor = MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST); + let mut mi = MONITORINFO::default(); + mi.cbSize = size_of::() as u32; + if GetMonitorInfoW(monitor, &mut mi).as_bool() { + rc = mi.rcWork; + } + + if x < rc.left { + x = rc.left; + } else if (x + w) > rc.right { + x = rc.right - w; + } + + if y < rc.top { + y = rc.top; + } else if (y + h) > rc.bottom { + y = rc.bottom - h; + } + + let _ = MoveWindow(self.hwnd(), x, y, w, h, true); + } + + unsafe fn size(&self, width: *mut c_int, height: *mut c_int) { + let mut rc = RECT::default(); + GetWindowRect(self.hwnd(), &mut rc).expect("failed to get window rect"); + unsafe { + width.write(rc.right - rc.left); + height.write(rc.bottom - rc.top); + } + } + + unsafe fn resize(&self, width: c_int, height: c_int) { + SetWindowPos( + self.hwnd(), + HWND_TOP, + 0, + 0, + width, + height, + SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE, + ) + .expect("failed to resize window"); + } + + unsafe fn client_rect(&self, rect: *mut RECT) { + GetClientRect(self.hwnd(), rect).expect("failed to get client area"); + } + + unsafe fn rect(&self, rect: *mut RECT) { + GetWindowRect(self.hwnd(), rect).expect("failed to get window rect"); + } + + unsafe fn show(&self) { + if !self.hwnd().is_invalid() { + let _ = ShowWindow(self.hwnd(), SW_SHOWNA); + } + } + + unsafe fn hide(&self) { + if !self.hwnd().is_invalid() { + let _ = ShowWindow(self.hwnd(), SW_HIDE); + } + } + + unsafe fn refresh(&self) { + if !self.hwnd().is_invalid() { + let _ = InvalidateRect(self.hwnd(), None, FALSE); + } + } + + unsafe fn wnd_proc(&self, msg: c_uint, wp: WPARAM, lp: LPARAM) -> LRESULT { + DefWindowProcW(self.hwnd(), msg, wp, lp) + } +} + +impl Drop for Window { + fn drop(&mut self) { + if !self.hwnd.get().is_invalid() { + unsafe { + let _ = DestroyWindow(self.hwnd.get()); + } + } + } +}