From c35bcb9ff18a84a0be51864c794eeac6dfcf5d07 Mon Sep 17 00:00:00 2001 From: Kan-Ru Chen Date: Sat, 24 Aug 2024 21:40:39 +0900 Subject: [PATCH] refactor: draw candidate window with Direct2D --- .clang-format | 2 + CMakeLists.txt | 4 + ChewingTextService/ChewingTextService.cpp | 20 +- libIME/CMakeLists.txt | 7 +- libIME/CandidateWindow.cpp | 790 ++++++++++++---------- libIME/CandidateWindow.h | 196 +++--- libIME/DrawUtils.cpp | 46 ++ libIME/DrawUtils.h | 6 + libIME/ImeWindow.cpp | 2 +- libIME/ImeWindow.h | 2 + 10 files changed, 626 insertions(+), 449 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..25ed932 --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: Google +IndentWidth: 4 \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e1c65f..19a4d19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,10 @@ add_definitions( /D_UNICODE=1 /DUNICODE=1 # do Unicode build /D_CRT_SECURE_NO_WARNINGS # disable warnings about old libc functions ) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /utf-8") # set source code encoding to UTF-8 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR-") # turn off C++ RTTI set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8") # set source code encoding to UTF-8 diff --git a/ChewingTextService/ChewingTextService.cpp b/ChewingTextService/ChewingTextService.cpp index 7d6c74e..8df4007 100644 --- a/ChewingTextService/ChewingTextService.cpp +++ b/ChewingTextService/ChewingTextService.cpp @@ -18,14 +18,18 @@ // #include "ChewingTextService.h" + +#include #include -#include -#include #include +#include +#include + +#include + #include "ChewingImeModule.h" #include "resource.h" -#include -#include + using namespace std; @@ -717,8 +721,11 @@ void TextService::applyConfig() { font_ = CreateFontIndirect(&lf); // create new font if(messageWindow_) messageWindow_->setFont(font_); - if(candidateWindow_) + // messageWindow_->setFontSize(cfg.fontSize); + if(candidateWindow_) { candidateWindow_->setFont(font_); + candidateWindow_->setFontSize(static_cast(cfg.fontSize)); + } } } @@ -759,6 +766,7 @@ void TextService::updateCandidates(Ime::EditSession* session) { candidateWindow_->clear(); candidateWindow_->setUseCursor(config().cursorCandList); candidateWindow_->setCandPerRow(config().candPerRow); + candidateWindow_->setFontSize(static_cast(config().fontSize)); ::chewing_cand_Enumerate(chewingContext_); int* selKeys = ::chewing_get_selKey(chewingContext_); // keys used to select candidates @@ -798,6 +806,7 @@ void TextService::showCandidates(Ime::EditSession* session) { if(!candidateWindow_) { candidateWindow_ = new Ime::CandidateWindow(this, session); candidateWindow_->setFont(font_); + candidateWindow_->setFontSize(config().fontSize); } updateCandidates(session); candidateWindow_->show(); @@ -821,6 +830,7 @@ void TextService::showMessage(Ime::EditSession* session, std::wstring message, i // FIXME: reuse the window whenever possible messageWindow_ = new Ime::MessageWindow(this, session); messageWindow_->setFont(font_); + messageWindow_->setFontSize(config().fontSize); messageWindow_->setText(message); int x = 0, y = 0; diff --git a/libIME/CMakeLists.txt b/libIME/CMakeLists.txt index 3252432..dd297c7 100644 --- a/libIME/CMakeLists.txt +++ b/libIME/CMakeLists.txt @@ -37,6 +37,11 @@ add_library(libIME_static STATIC ${PROJECT_SOURCE_DIR}/CandidateWindow.cpp ) +target_compile_features(libIME_static PUBLIC cxx_std_17) + target_link_libraries(libIME_static - shlwapi.lib + PUBLIC shlwapi.lib + PUBLIC d2d1.lib + PUBLIC d3d11.lib + PUBLIC dwrite.lib ) diff --git a/libIME/CandidateWindow.cpp b/libIME/CandidateWindow.cpp index b472a6c..7309c7c 100644 --- a/libIME/CandidateWindow.cpp +++ b/libIME/CandidateWindow.cpp @@ -18,417 +18,521 @@ // #include "CandidateWindow.h" -#include "DrawUtils.h" -#include "TextService.h" -#include "EditSession.h" - -#include -#include +#include +#include +#include +#include +#include #include +#include #include +#include + +#include + +#include "DrawUtils.h" +#include "EditSession.h" +#include "TextService.h" using namespace std; namespace Ime { -CandidateWindow::CandidateWindow(TextService* service, EditSession* session): - ImeWindow(service), - refCount_(1), - shown_(false), - candPerRow_(1), - textWidth_(0), - itemHeight_(0), - currentSel_(0), - hasResult_(false), - useCursor_(true), - selKeyWidth_(0) { - - if(service->isImmersive()) { // windows 8 app mode - margin_ = 10; - rowSpacing_ = 8; - colSpacing_ = 12; - } - else { // desktop mode - margin_ = 5; - rowSpacing_ = 4; - colSpacing_ = 8; - } - - HWND parent = service->compositionWindow(session); - create(parent, WS_POPUP|WS_CLIPCHILDREN, WS_EX_TOOLWINDOW|WS_EX_TOPMOST); +CandidateWindow::CandidateWindow(TextService *service, EditSession *session) + : ImeWindow(service), + refCount_(1), + shown_(false), + candPerRow_(1), + textWidth_(0), + itemHeight_(0), + currentSel_(0), + hasResult_(false), + useCursor_(true), + selKeyWidth_(0) { + if (service->isImmersive()) { // windows 8 app mode + margin_ = 10; + rowSpacing_ = 8; + colSpacing_ = 12; + } else { // desktop mode + margin_ = 5; + rowSpacing_ = 4; + colSpacing_ = 8; + } + + HWND parent = service->compositionWindow(session); + create(parent, WS_POPUP | WS_CLIPCHILDREN, + WS_EX_TOOLWINDOW | WS_EX_TOPMOST); + + winrt::check_hresult( + D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, factory_.put())); + + winrt::com_ptr d3device; + winrt::com_ptr dxdevice; + winrt::com_ptr adapter; + winrt::com_ptr factory; + winrt::com_ptr device; + winrt::com_ptr surface; + winrt::com_ptr bitmap; + winrt::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(); + winrt::check_hresult(factory_->CreateDevice(dxdevice.get(), device.put())); + winrt::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; + + winrt::check_hresult(dxdevice->GetAdapter(adapter.put())); + winrt::check_hresult( + adapter->GetParent(__uuidof(factory), factory.put_void())); + + winrt::check_hresult( + factory->CreateSwapChainForHwnd(d3device.get(), hwnd_, &swapChainDesc, + nullptr, nullptr, swapChain_.put())); + winrt::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)); + winrt::check_hresult(target_->CreateBitmapFromDxgiSurface( + surface.get(), &bitmap_props, bitmap.put())); + target_->SetTarget(bitmap.get()); } -CandidateWindow::~CandidateWindow(void) { -} +CandidateWindow::~CandidateWindow(void) {} // IUnknown -STDMETHODIMP CandidateWindow::QueryInterface(REFIID riid, void **ppvObj) { - if (!ppvObj) - return E_INVALIDARG; - - if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_ITfCandidateListUIElement)) { - *ppvObj = (ITfCandidateListUIElement*)this; - } - else { - *ppvObj = NULL; - } - - if (!*ppvObj) { - return E_NOINTERFACE; - } - - AddRef(); - return S_OK; +STDMETHODIMP +CandidateWindow::QueryInterface(REFIID riid, void **ppvObj) { + if (!ppvObj) return E_INVALIDARG; + + if (IsEqualIID(riid, IID_IUnknown) || + IsEqualIID(riid, IID_ITfCandidateListUIElement)) { + *ppvObj = (ITfCandidateListUIElement *)this; + } else { + *ppvObj = NULL; + } + + if (!*ppvObj) { + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; } -STDMETHODIMP_(ULONG) CandidateWindow::AddRef(void) { - return ++refCount_; -} +STDMETHODIMP_(ULONG) CandidateWindow::AddRef(void) { return ++refCount_; } STDMETHODIMP_(ULONG) CandidateWindow::Release(void) { - assert(refCount_ > 0); - const ULONG newCount = --refCount_; - if (refCount_ == 0) - delete this; - return newCount; + assert(refCount_ > 0); + const ULONG newCount = --refCount_; + if (refCount_ == 0) delete this; + return newCount; } // ITfUIElement -STDMETHODIMP CandidateWindow::GetDescription(BSTR *pbstrDescription) { - if (!pbstrDescription) - return E_INVALIDARG; - *pbstrDescription = SysAllocString(L"Candidate window~"); - return S_OK; +STDMETHODIMP +CandidateWindow::GetDescription(BSTR *pbstrDescription) { + if (!pbstrDescription) return E_INVALIDARG; + *pbstrDescription = SysAllocString(L"Candidate window~"); + return S_OK; } // {BD7CCC94-57CD-41D3-A789-AF47890CEB29} -STDMETHODIMP CandidateWindow::GetGUID(GUID *pguid) { - if (!pguid) - return E_INVALIDARG; - *pguid = { 0xbd7ccc94, 0x57cd, 0x41d3, { 0xa7, 0x89, 0xaf, 0x47, 0x89, 0xc, 0xeb, 0x29 } }; - return S_OK; +STDMETHODIMP +CandidateWindow::GetGUID(GUID *pguid) { + if (!pguid) return E_INVALIDARG; + *pguid = {0xbd7ccc94, + 0x57cd, + 0x41d3, + {0xa7, 0x89, 0xaf, 0x47, 0x89, 0xc, 0xeb, 0x29}}; + return S_OK; } -STDMETHODIMP CandidateWindow::Show(BOOL bShow) { - shown_ = bShow; - if (shown_) - show(); - else - hide(); - return S_OK; +STDMETHODIMP +CandidateWindow::Show(BOOL bShow) { + shown_ = bShow; + if (shown_) + show(); + else + hide(); + return S_OK; } -STDMETHODIMP CandidateWindow::IsShown(BOOL *pbShow) { - if (!pbShow) - return E_INVALIDARG; - *pbShow = shown_; - return S_OK; +STDMETHODIMP +CandidateWindow::IsShown(BOOL *pbShow) { + if (!pbShow) return E_INVALIDARG; + *pbShow = shown_; + return S_OK; } // ITfCandidateListUIElement -STDMETHODIMP CandidateWindow::GetUpdatedFlags(DWORD *pdwFlags) { - if (!pdwFlags) - return E_INVALIDARG; - /// XXX update all!!! - *pdwFlags = TF_CLUIE_DOCUMENTMGR | TF_CLUIE_COUNT | TF_CLUIE_SELECTION | TF_CLUIE_STRING | TF_CLUIE_PAGEINDEX | TF_CLUIE_CURRENTPAGE; - return S_OK; +STDMETHODIMP +CandidateWindow::GetUpdatedFlags(DWORD *pdwFlags) { + if (!pdwFlags) return E_INVALIDARG; + /// XXX update all!!! + *pdwFlags = TF_CLUIE_DOCUMENTMGR | TF_CLUIE_COUNT | TF_CLUIE_SELECTION | + TF_CLUIE_STRING | TF_CLUIE_PAGEINDEX | TF_CLUIE_CURRENTPAGE; + return S_OK; } -STDMETHODIMP CandidateWindow::GetDocumentMgr(ITfDocumentMgr **ppdim) { - if (!textService_) - return E_FAIL; - return textService_->currentContext()->GetDocumentMgr(ppdim); +STDMETHODIMP +CandidateWindow::GetDocumentMgr(ITfDocumentMgr **ppdim) { + if (!textService_) return E_FAIL; + return textService_->currentContext()->GetDocumentMgr(ppdim); } -STDMETHODIMP CandidateWindow::GetCount(UINT *puCount) { - if (!puCount) - return E_INVALIDARG; - *puCount = std::min(10, items_.size()); - return S_OK; +STDMETHODIMP +CandidateWindow::GetCount(UINT *puCount) { + if (!puCount) return E_INVALIDARG; + *puCount = std::min(10, items_.size()); + return S_OK; } -STDMETHODIMP CandidateWindow::GetSelection(UINT *puIndex) { - assert(currentSel_ >= 0); - if (!puIndex) - return E_INVALIDARG; - *puIndex = static_cast(currentSel_); - return S_OK; +STDMETHODIMP +CandidateWindow::GetSelection(UINT *puIndex) { + assert(currentSel_ >= 0); + if (!puIndex) return E_INVALIDARG; + *puIndex = static_cast(currentSel_); + return S_OK; } -STDMETHODIMP CandidateWindow::GetString(UINT uIndex, BSTR *pbstr) { - if (!pbstr) - return E_INVALIDARG; - if (uIndex >= items_.size()) - return E_INVALIDARG; - *pbstr = SysAllocString(items_[uIndex].c_str()); - return S_OK; +STDMETHODIMP +CandidateWindow::GetString(UINT uIndex, BSTR *pbstr) { + if (!pbstr) return E_INVALIDARG; + if (uIndex >= items_.size()) return E_INVALIDARG; + *pbstr = SysAllocString(items_[uIndex].c_str()); + return S_OK; } -STDMETHODIMP CandidateWindow::GetPageIndex(UINT *puIndex, UINT uSize, UINT *puPageCnt) { - /// XXX Always return the same single page index. - if (!puPageCnt) - return E_INVALIDARG; - *puPageCnt = 1; - if (puIndex) { - if (uSize < *puPageCnt) { - return E_INVALIDARG; - } - puIndex[0] = 0; - } - return S_OK; +STDMETHODIMP +CandidateWindow::GetPageIndex(UINT *puIndex, UINT uSize, UINT *puPageCnt) { + /// XXX Always return the same single page index. + if (!puPageCnt) return E_INVALIDARG; + *puPageCnt = 1; + if (puIndex) { + if (uSize < *puPageCnt) { + return E_INVALIDARG; + } + puIndex[0] = 0; + } + return S_OK; } -STDMETHODIMP CandidateWindow::SetPageIndex(UINT *puIndex, UINT uPageCnt) { - /// XXX Do not let app set page indices. - if (!puIndex) - return E_INVALIDARG; - return S_OK; +STDMETHODIMP +CandidateWindow::SetPageIndex(UINT *puIndex, UINT uPageCnt) { + /// XXX Do not let app set page indices. + if (!puIndex) return E_INVALIDARG; + return S_OK; } -STDMETHODIMP CandidateWindow::GetCurrentPage(UINT *puPage) { - if (!puPage) - return E_INVALIDARG; - *puPage = 0; - return S_OK; +STDMETHODIMP +CandidateWindow::GetCurrentPage(UINT *puPage) { + if (!puPage) return E_INVALIDARG; + *puPage = 0; + return S_OK; } -LRESULT CandidateWindow::wndProc(UINT msg, WPARAM wp , LPARAM lp) { - switch (msg) { - case WM_PAINT: - onPaint(wp, lp); - break; - case WM_ERASEBKGND: - return TRUE; - break; - case WM_LBUTTONDOWN: - onLButtonDown(wp, lp); - break; - case WM_MOUSEMOVE: - onMouseMove(wp, lp); - break; - case WM_LBUTTONUP: - onLButtonUp(wp, lp); - break; - case WM_MOUSEACTIVATE: - return MA_NOACTIVATE; - default: - return Window::wndProc(msg, wp, lp); - } - return 0; +LRESULT +CandidateWindow::wndProc(UINT msg, WPARAM wp, LPARAM lp) { + switch (msg) { + case WM_PAINT: + onPaint(wp, lp); + break; + case WM_ERASEBKGND: + return TRUE; + break; + case WM_LBUTTONDOWN: + onLButtonDown(wp, lp); + break; + case WM_MOUSEMOVE: + onMouseMove(wp, lp); + break; + case WM_LBUTTONUP: + onLButtonUp(wp, lp); + break; + case WM_MOUSEACTIVATE: + return MA_NOACTIVATE; + default: + return Window::wndProc(msg, wp, lp); + } + return 0; } void CandidateWindow::onPaint(WPARAM wp, LPARAM lp) { - // TODO: check isImmersive_, and draw the window differently - // in Windows 8 app immersive mode to follow windows 8 UX guidelines - PAINTSTRUCT ps; - BeginPaint(hwnd_, &ps); - HDC hDC = ps.hdc; - HFONT oldFont; - RECT rc; - - oldFont = (HFONT)SelectObject(hDC, font_); - - GetClientRect(hwnd_,&rc); - SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT)); - SetBkColor(hDC, GetSysColor(COLOR_WINDOW)); - - // paint window background and border - // draw a flat black border in Windows 8 app immersive mode - // draw a 3d border in desktop mode - if(isImmersive()) { - HPEN pen = ::CreatePen(PS_SOLID, 3, RGB(0, 0, 0)); - HGDIOBJ oldPen = ::SelectObject(hDC, pen); - ::Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom); - ::SelectObject(hDC, oldPen); - ::DeleteObject(pen); - } - else { - // draw a 3d border in desktop mode - ::FillSolidRect(ps.hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, GetSysColor(COLOR_WINDOW)); - ::Draw3DBorder(hDC, &rc, GetSysColor(COLOR_3DFACE), 0); - } - - // paint items - int col = 0; - int x = margin_, y = margin_; - for(int i = 0, n = items_.size(); i < n; ++i) { - paintItem(hDC, i, x, y); - ++col; // go to next column - if(col >= candPerRow_) { - col = 0; - x = margin_; - y += itemHeight_ + rowSpacing_; - } - else { - x += colSpacing_ + selKeyWidth_ + textWidth_; - } - } - SelectObject(hDC, oldFont); - EndPaint(hwnd_, &ps); + RECT rc; + GetClientRect(hwnd_, &rc); + + target_->BeginDraw(); + // paint window background and border + // draw a flat black border in Windows 8 app immersive mode + // draw a 3d border in desktop mode + if (isImmersive()) { + // HPEN pen = ::CreatePen(PS_SOLID, 3, RGB(0, 0, 0)); + // HGDIOBJ oldPen = ::SelectObject(hDC, pen); + // ::Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom); + // ::SelectObject(hDC, oldPen); + // ::DeleteObject(pen); + } else { + // draw a 3d border in desktop mode + ::FillSolidRectD2D(target_.get(), rc.left, rc.top, rc.right - rc.left, + rc.bottom - rc.top, GetSysColor(COLOR_WINDOW)); + ::Draw3DBorderD2D(target_.get(), &rc, GetSysColor(COLOR_3DFACE), 0, 1); + } + + // paint items + int col = 0; + int x = margin_, y = margin_; + for (int i = 0, n = items_.size(); i < n; ++i) { + paintItemD2D(target_.get(), i, x, y); + ++col; // go to next column + if (col >= candPerRow_) { + col = 0; + x = margin_; + y += itemHeight_ + rowSpacing_; + } else { + x += colSpacing_ + selKeyWidth_ + textWidth_; + } + } + target_->EndDraw(); + winrt::check_hresult(swapChain_->Present(1, 0)); } void CandidateWindow::recalculateSize() { - if(items_.empty()) { - resize(margin_ * 2, margin_ * 2); - } - - HDC hDC = ::GetWindowDC(hwnd()); - int height = 0; - int width = 0; - selKeyWidth_ = 0; - textWidth_ = 0; - itemHeight_ = 0; - - HGDIOBJ oldFont = ::SelectObject(hDC, font_); - vector::const_iterator it; - for(int i = 0, n = items_.size(); i < n; ++i) { - SIZE selKeySize; - int lineHeight = 0; - // the selection key string - wchar_t selKey[] = L"?. "; - selKey[0] = selKeys_[i]; - ::GetTextExtentPoint32W(hDC, selKey, 3, &selKeySize); - if(selKeySize.cx > selKeyWidth_) - selKeyWidth_ = selKeySize.cx; - - // the candidate string - SIZE candidateSize; - wstring& item = items_.at(i); - ::GetTextExtentPoint32W(hDC, item.c_str(), item.length(), &candidateSize); - if(candidateSize.cx > textWidth_) - textWidth_ = candidateSize.cx; - int itemHeight = max(candidateSize.cy, selKeySize.cy); - if(itemHeight > itemHeight_) - itemHeight_ = itemHeight; - } - ::SelectObject(hDC, oldFont); - ::ReleaseDC(hwnd(), hDC); - - if(items_.size() <= candPerRow_) { - width = items_.size() * (selKeyWidth_ + textWidth_); - width += colSpacing_ * (items_.size() - 1); - width += margin_ * 2; - height = itemHeight_ + margin_ * 2; - } - else { - width = candPerRow_ * (selKeyWidth_ + textWidth_); - width += colSpacing_ * (candPerRow_ - 1); - width += margin_ * 2; - int rowCount = items_.size() / candPerRow_; - if(items_.size() % candPerRow_) - ++rowCount; - height = itemHeight_ * rowCount + rowSpacing_ * (rowCount - 1) + margin_ * 2; - } - resize(width, height); + if (items_.empty()) { + resize(margin_ * 2, margin_ * 2); + } + + RECT rc; + GetClientRect(hwnd_, &rc); + + winrt::com_ptr pD2DFactory; + winrt::check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, + pD2DFactory.put())); + + winrt::com_ptr pDwriteFactory; + winrt::com_ptr pTextFormat; + winrt::check_hresult(DWriteCreateFactory( + DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory1), + reinterpret_cast(pDwriteFactory.put()))); + winrt::check_hresult(pDwriteFactory->CreateTextFormat( + L"Segoe UI", nullptr, DWRITE_FONT_WEIGHT_NORMAL, + DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, fontSize_, L"", + pTextFormat.put())); + + int height = 0; + int width = 0; + selKeyWidth_ = 0; + textWidth_ = 0; + itemHeight_ = 0; + IDWriteTextLayout *pTextLayout = nullptr; + DWRITE_TEXT_METRICS selKeyMetrics; + DWRITE_TEXT_METRICS itemMetrics; + + vector::const_iterator it; + for (int i = 0, n = items_.size(); i < n; ++i) { + int lineHeight = 0; + // the selection key string + wchar_t selKey[] = L"?. "; + selKey[0] = selKeys_[i]; + pDwriteFactory->CreateTextLayout(selKey, 3, pTextFormat.get(), + D2D1::FloatMax(), D2D1::FloatMax(), + &pTextLayout); + pTextLayout->GetMetrics(&selKeyMetrics); + if (selKeyMetrics.widthIncludingTrailingWhitespace > selKeyWidth_) { + selKeyWidth_ = selKeyMetrics.widthIncludingTrailingWhitespace; + } + + // the candidate string + wstring &item = items_.at(i); + pDwriteFactory->CreateTextLayout(item.c_str(), item.length(), + pTextFormat.get(), D2D1::FloatMax(), + D2D1::FloatMax(), &pTextLayout); + pTextLayout->GetMetrics(&itemMetrics); + if (itemMetrics.widthIncludingTrailingWhitespace > textWidth_) + textWidth_ = itemMetrics.widthIncludingTrailingWhitespace; + int itemHeight = max(itemMetrics.height, selKeyMetrics.height); + if (itemHeight > itemHeight_) itemHeight_ = itemHeight; + } + + if (items_.size() <= candPerRow_) { + width = items_.size() * (selKeyWidth_ + textWidth_); + width += colSpacing_ * (items_.size() - 1); + width += margin_ * 2; + height = itemHeight_ + margin_ * 2; + } else { + width = candPerRow_ * (selKeyWidth_ + textWidth_); + width += colSpacing_ * (candPerRow_ - 1); + width += margin_ * 2; + int rowCount = items_.size() / candPerRow_; + if (items_.size() % candPerRow_) ++rowCount; + height = + itemHeight_ * rowCount + rowSpacing_ * (rowCount - 1) + margin_ * 2; + } + resize(width, height); + + winrt::com_ptr surface; + winrt::com_ptr bitmap; + + target_->SetTarget(nullptr); + swapChain_->ResizeBuffers(0, width, height, DXGI_FORMAT_B8G8R8A8_UNORM, 0); + winrt::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)); + winrt::check_hresult(target_->CreateBitmapFromDxgiSurface( + surface.get(), &bitmap_props, bitmap.put())); + target_->SetTarget(bitmap.get()); } void CandidateWindow::setCandPerRow(int n) { - if(n != candPerRow_) { - candPerRow_ = n; - recalculateSize(); - } + if (n != candPerRow_) { + candPerRow_ = n; + recalculateSize(); + } } -bool CandidateWindow::filterKeyEvent(KeyEvent& keyEvent) { - // select item with arrow keys - int oldSel = currentSel_; - switch(keyEvent.keyCode()) { - case VK_UP: - if(currentSel_ - candPerRow_ >=0) - currentSel_ -= candPerRow_; - break; - case VK_DOWN: - if(currentSel_ + candPerRow_ < items_.size()) - currentSel_ += candPerRow_; - break; - case VK_LEFT: - if(currentSel_ - 1 >=0) - --currentSel_; - break; - case VK_RIGHT: - if(currentSel_ + 1 < items_.size()) - ++currentSel_; - break; - case VK_RETURN: - hasResult_ = true; - return true; - default: - return false; - } - // if currently selected item is changed, redraw - if(currentSel_ != oldSel) { - // repaint the old and new items - RECT rect; - itemRect(oldSel, rect); - ::InvalidateRect(hwnd_, &rect, TRUE); - itemRect(currentSel_, rect); - ::InvalidateRect(hwnd_, &rect, TRUE); - return true; - } - return false; +bool CandidateWindow::filterKeyEvent(KeyEvent &keyEvent) { + // select item with arrow keys + int oldSel = currentSel_; + switch (keyEvent.keyCode()) { + case VK_UP: + if (currentSel_ - candPerRow_ >= 0) currentSel_ -= candPerRow_; + break; + case VK_DOWN: + if (currentSel_ + candPerRow_ < items_.size()) + currentSel_ += candPerRow_; + break; + case VK_LEFT: + if (currentSel_ - 1 >= 0) --currentSel_; + break; + case VK_RIGHT: + if (currentSel_ + 1 < items_.size()) ++currentSel_; + break; + case VK_RETURN: + hasResult_ = true; + return true; + default: + return false; + } + // if currently selected item is changed, redraw + if (currentSel_ != oldSel) { + // repaint the old and new items + RECT rect; + itemRect(oldSel, rect); + ::InvalidateRect(hwnd_, &rect, TRUE); + itemRect(currentSel_, rect); + ::InvalidateRect(hwnd_, &rect, TRUE); + return true; + } + return false; } void CandidateWindow::setCurrentSel(int sel) { - if(sel >= items_.size()) - sel = 0; - if (currentSel_ != sel) { - currentSel_ = sel; - if (isVisible()) - ::InvalidateRect(hwnd_, NULL, TRUE); - } + if (sel >= items_.size()) sel = 0; + if (currentSel_ != sel) { + currentSel_ = sel; + if (isVisible()) ::InvalidateRect(hwnd_, NULL, TRUE); + } } void CandidateWindow::clear() { - items_.clear(); - selKeys_.clear(); - currentSel_ = 0; - hasResult_ = false; + items_.clear(); + selKeys_.clear(); + currentSel_ = 0; + hasResult_ = false; } void CandidateWindow::setUseCursor(bool use) { - useCursor_ = use; - if(isVisible()) - ::InvalidateRect(hwnd_, NULL, TRUE); + useCursor_ = use; + if (isVisible()) ::InvalidateRect(hwnd_, NULL, TRUE); } -void CandidateWindow::paintItem(HDC hDC, int i, int x, int y) { - RECT textRect = {x, y, 0, y + itemHeight_}; - wchar_t selKey[] = L"?. "; - selKey[0] = selKeys_[i]; - textRect.right = textRect.left + selKeyWidth_; - // FIXME: make the color of strings configurable. - COLORREF selKeyColor = RGB(0, 0, 255); - COLORREF oldColor = ::SetTextColor(hDC, selKeyColor); - // paint the selection key - ::ExtTextOut(hDC, textRect.left, textRect.top, ETO_OPAQUE, &textRect, selKey, 3, NULL); - ::SetTextColor(hDC, oldColor); // restore text color - - // paint the candidate string - wstring& item = items_.at(i); - textRect.left += selKeyWidth_; - textRect.right = textRect.left + textWidth_; - // paint the candidate string - ::ExtTextOut(hDC, textRect.left, textRect.top, ETO_OPAQUE, &textRect, item.c_str(), item.length(), NULL); - - if(useCursor_ && i == currentSel_) { // invert the selected item - int left = textRect.left; // - selKeyWidth_; - int top = textRect.top; - int width = textRect.right - left; - int height = itemHeight_; - ::BitBlt(hDC, left, top, width, itemHeight_, hDC, left, top, NOTSRCCOPY); - } +void CandidateWindow::paintItemD2D(ID2D1RenderTarget *pRenderTarget, int i, + int x, int y) { + winrt::com_ptr pDwriteFactory; + winrt::com_ptr pTextFormat; + winrt::com_ptr pSelKeyBrush; + winrt::com_ptr pTextBrush; + winrt::com_ptr pSelectedTextBrush; + 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()); + + RECT textRect = {x, y, 0, y + itemHeight_}; + wchar_t selKey[] = L"?. "; + selKey[0] = selKeys_[i]; + textRect.right = textRect.left + selKeyWidth_; + + // FIXME: make the color of strings configurable. + COLORREF selKeyColor = RGB(0, 0, 255); + COLORREF textColor = GetSysColor(COLOR_WINDOWTEXT); + pRenderTarget->CreateSolidColorBrush( + D2D1::ColorF(GetRValue(selKeyColor) / 255.0f, + GetGValue(selKeyColor) / 255.0f, + GetBValue(selKeyColor) / 255.0f), + pSelKeyBrush.put()); + pRenderTarget->CreateSolidColorBrush( + D2D1::ColorF(GetRValue(textColor) / 255.0f, + GetGValue(textColor) / 255.0f, + GetBValue(textColor) / 255.0f), + pTextBrush.put()); + pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), + pSelectedTextBrush.put()); + + pRenderTarget->DrawText(selKey, 3, pTextFormat.get(), + D2D1::RectF(textRect.left, textRect.top, + textRect.right, textRect.bottom), + pSelKeyBrush.get()); + + wstring &item = items_.at(i); + textRect.left += selKeyWidth_; + textRect.right = textRect.left + textWidth_; + + // invert the selected item + if (useCursor_ && i == currentSel_) { + pRenderTarget->FillRectangle( + D2D1::RectF(textRect.left, textRect.top, textRect.right, + textRect.bottom), + pTextBrush.get()); + pRenderTarget->DrawText(item.c_str(), item.length(), pTextFormat.get(), + D2D1::RectF(textRect.left, textRect.top, + textRect.right, textRect.bottom), + pSelectedTextBrush.get()); + } else { + pRenderTarget->DrawText(item.c_str(), item.length(), pTextFormat.get(), + D2D1::RectF(textRect.left, textRect.top, + textRect.right, textRect.bottom), + pTextBrush.get()); + } } -void CandidateWindow::itemRect(int i, RECT& rect) { - int row, col; - row = i / candPerRow_; - col = i % candPerRow_; - rect.left = margin_ + col * (selKeyWidth_ + textWidth_ + colSpacing_); - rect.top = margin_ + row * (itemHeight_ + rowSpacing_); - rect.right = rect.left + (selKeyWidth_ + textWidth_); - rect.bottom = rect.top + itemHeight_; +void CandidateWindow::itemRect(int i, RECT &rect) { + int row, col; + row = i / candPerRow_; + col = i % candPerRow_; + rect.left = margin_ + col * (selKeyWidth_ + textWidth_ + colSpacing_); + rect.top = margin_ + row * (itemHeight_ + rowSpacing_); + rect.right = rect.left + (selKeyWidth_ + textWidth_); + rect.bottom = rect.top + itemHeight_; } - -} // namespace Ime +} // namespace Ime diff --git a/libIME/CandidateWindow.h b/libIME/CandidateWindow.h index cbcf128..65ce6f4 100644 --- a/libIME/CandidateWindow.h +++ b/libIME/CandidateWindow.h @@ -20,10 +20,16 @@ #ifndef IME_CANDIDATE_WINDOW_H #define IME_CANDIDATE_WINDOW_H -#include "ImeWindow.h" +#include +#include +#include +#include + #include #include +#include "ImeWindow.h" + namespace Ime { class TextService; @@ -31,105 +37,97 @@ class EditSession; class KeyEvent; // TODO: make the candidate window looks different in immersive mode -class CandidateWindow : - public ImeWindow, - public ITfCandidateListUIElement { -public: - CandidateWindow(TextService* service, EditSession* session); - - // IUnknown - STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj); - STDMETHODIMP_(ULONG) AddRef(void); - STDMETHODIMP_(ULONG) Release(void); - - // ITfUIElement - STDMETHODIMP GetDescription(BSTR *pbstrDescription); - STDMETHODIMP GetGUID(GUID *pguid); - STDMETHODIMP Show(BOOL bShow); - STDMETHODIMP IsShown(BOOL *pbShow); - - // ITfCandidateListUIElement - STDMETHODIMP GetUpdatedFlags(DWORD *pdwFlags); - STDMETHODIMP GetDocumentMgr(ITfDocumentMgr **ppdim); - STDMETHODIMP GetCount(UINT *puCount); - STDMETHODIMP GetSelection(UINT *puIndex); - STDMETHODIMP GetString(UINT uIndex, BSTR *pstr); - STDMETHODIMP GetPageIndex(UINT *puIndex, UINT uSize, UINT *puPageCnt); - STDMETHODIMP SetPageIndex(UINT *puIndex, UINT uPageCnt); - STDMETHODIMP GetCurrentPage(UINT *puPage); - - const std::vector& items() const { - return items_; - } - - void setItems(const std::vector& items, const std::vector& sekKeys) { - items_ = items; - selKeys_ = selKeys_; - recalculateSize(); - refresh(); - } - - void add(std::wstring item, wchar_t selKey) { - items_.push_back(item); - selKeys_.push_back(selKey); - } - - void clear(); - - int candPerRow() const { - return candPerRow_; - } - void setCandPerRow(int n); - - virtual void recalculateSize(); - - bool filterKeyEvent(KeyEvent& keyEvent); - - int currentSel() const { - return currentSel_; - } - void setCurrentSel(int sel); - - wchar_t currentSelKey() const { - return selKeys_.at(currentSel_); - } - - bool hasResult() const { - return hasResult_; - } - - bool useCursor() const { - return useCursor_; - } - - void setUseCursor(bool use); - -protected: - LRESULT wndProc(UINT msg, WPARAM wp , LPARAM lp); - void onPaint(WPARAM wp, LPARAM lp); - void paintItem(HDC hDC, int i, int x, int y); - void itemRect(int i, RECT& rect); - -protected: // COM object should not be deleted directly. calling Release() instead. - ~CandidateWindow(void); - -private: - ULONG refCount_; - BOOL shown_; - - int selKeyWidth_; - int textWidth_; - int itemHeight_; - int candPerRow_; - int colSpacing_; - int rowSpacing_; - std::vector selKeys_; - std::vector items_; - int currentSel_; - bool hasResult_; - bool useCursor_; +class CandidateWindow : public ImeWindow, public ITfCandidateListUIElement { + public: + CandidateWindow(TextService *service, EditSession *session); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + // ITfUIElement + STDMETHODIMP GetDescription(BSTR *pbstrDescription); + STDMETHODIMP GetGUID(GUID *pguid); + STDMETHODIMP Show(BOOL bShow); + STDMETHODIMP IsShown(BOOL *pbShow); + + // ITfCandidateListUIElement + STDMETHODIMP GetUpdatedFlags(DWORD *pdwFlags); + STDMETHODIMP GetDocumentMgr(ITfDocumentMgr **ppdim); + STDMETHODIMP GetCount(UINT *puCount); + STDMETHODIMP GetSelection(UINT *puIndex); + STDMETHODIMP GetString(UINT uIndex, BSTR *pstr); + STDMETHODIMP GetPageIndex(UINT *puIndex, UINT uSize, UINT *puPageCnt); + STDMETHODIMP SetPageIndex(UINT *puIndex, UINT uPageCnt); + STDMETHODIMP GetCurrentPage(UINT *puPage); + + const std::vector &items() const { return items_; } + + void setItems(const std::vector &items, + const std::vector &sekKeys) { + items_ = items; + selKeys_ = selKeys_; + recalculateSize(); + refresh(); + } + + void add(std::wstring item, wchar_t selKey) { + items_.push_back(item); + selKeys_.push_back(selKey); + } + + void clear(); + + int candPerRow() const { return candPerRow_; } + void setCandPerRow(int n); + + virtual void recalculateSize(); + + bool filterKeyEvent(KeyEvent &keyEvent); + + int currentSel() const { return currentSel_; } + void setCurrentSel(int sel); + + wchar_t currentSelKey() const { return selKeys_.at(currentSel_); } + + bool hasResult() const { return hasResult_; } + + bool useCursor() const { return useCursor_; } + + void setUseCursor(bool use); + + protected: + LRESULT wndProc(UINT msg, WPARAM wp, LPARAM lp); + void onPaint(WPARAM wp, LPARAM lp); + void paintItemD2D(ID2D1RenderTarget *pRenderTarget, int i, int x, int y); + void itemRect(int i, RECT &rect); + + protected: // COM object should not be deleted directly. calling Release() + // instead. + ~CandidateWindow(void); + + private: + winrt::com_ptr target_; + winrt::com_ptr swapChain_; + winrt::com_ptr factory_; + + ULONG refCount_; + BOOL shown_; + + int selKeyWidth_; + int textWidth_; + int itemHeight_; + int candPerRow_; + int colSpacing_; + int rowSpacing_; + std::vector selKeys_; + std::vector items_; + int currentSel_; + bool hasResult_; + bool useCursor_; }; -} +} // namespace Ime #endif diff --git a/libIME/DrawUtils.cpp b/libIME/DrawUtils.cpp index 9f2d6c1..e7609cb 100644 --- a/libIME/DrawUtils.cpp +++ b/libIME/DrawUtils.cpp @@ -18,6 +18,10 @@ // #include "DrawUtils.h" +#include +#include +#include +#include void FillSolidRect(HDC dc, LPRECT rc, COLORREF color) { SetBkColor(dc, color); @@ -34,6 +38,48 @@ void FillSolidRect(HDC dc, int l, int t, int w, int h, COLORREF color) { ::ExtTextOut(dc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL); } +void FillSolidRectD2D(ID2D1RenderTarget *pRenderTarget, int l, int t, int w, + int h, COLORREF color) { + ID2D1SolidColorBrush *pBrush = nullptr; + pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(GetRValue(color) / 255.0f, + GetGValue(color) / 255.0f, + GetBValue(color) / 255.0f), + &pBrush); + pRenderTarget->FillRectangle( + D2D1::RectF(static_cast(l), static_cast(t), + static_cast(l + w), static_cast(t + h)), + pBrush); + pBrush->Release(); +} + +void Draw3DBorderD2D(ID2D1RenderTarget *pRenderTarget, LPRECT rc, + COLORREF light, COLORREF dark, int width) { + ID2D1SolidColorBrush *pLightBrush = nullptr; + ID2D1SolidColorBrush *pDarkBrush = nullptr; + pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(GetRValue(light) / 255.0f, + GetGValue(light) / 255.0f, + GetBValue(light) / 255.0f), + &pLightBrush); + pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(GetRValue(dark) / 255.0f, + GetGValue(dark) / 255.0f, + GetBValue(dark) / 255.0f), + &pDarkBrush); + pRenderTarget->DrawLine(D2D1::Point2F(rc->left, rc->bottom), + D2D1::Point2F(rc->left, rc->top), pLightBrush, width); + pRenderTarget->DrawLine(D2D1::Point2F(rc->left, rc->top), + D2D1::Point2F(rc->right - width, rc->top), + pLightBrush, width); + pRenderTarget->DrawLine(D2D1::Point2F(rc->right - width, rc->top), + D2D1::Point2F(rc->right - width, rc->bottom - width), + pDarkBrush, width); + pRenderTarget->DrawLine(D2D1::Point2F(rc->right - width, rc->bottom - width), + D2D1::Point2F(rc->left, rc->bottom - width), + pDarkBrush, width); + + pLightBrush->Release(); + pDarkBrush->Release(); +} + void Draw3DBorder(HDC hdc, LPRECT rc, COLORREF light, COLORREF dark, int width) { MoveToEx(hdc, rc->left, rc->bottom, NULL); diff --git a/libIME/DrawUtils.h b/libIME/DrawUtils.h index 0b3ef3f..f52207d 100644 --- a/libIME/DrawUtils.h +++ b/libIME/DrawUtils.h @@ -21,10 +21,16 @@ #define IME_DRAW_UTIL_H #include +#include void FillSolidRect( HDC dc, LPRECT rc, COLORREF color ); void FillSolidRect( HDC dc, int l, int t, int w, int h, COLORREF color ); void Draw3DBorder(HDC hdc, LPRECT rc, COLORREF light, COLORREF dark, int width = 1); void DrawBitmap(HDC dc, HBITMAP bmp, int x, int y, int w, int h, int srcx, int srcy ); +void FillSolidRectD2D(ID2D1RenderTarget *pRenderTarget, int l, int t, int w, + int h, COLORREF color); +void Draw3DBorderD2D(ID2D1RenderTarget *pRenderTarget, LPRECT rc, + COLORREF light, COLORREF dark, int width); + #endif \ No newline at end of file diff --git a/libIME/ImeWindow.cpp b/libIME/ImeWindow.cpp index afd7fff..8f5d7b3 100644 --- a/libIME/ImeWindow.cpp +++ b/libIME/ImeWindow.cpp @@ -22,7 +22,7 @@ namespace Ime { ImeWindow::ImeWindow(TextService* service): - textService_(service) { + textService_(service), fontSize_(16.0f) { if(service->isImmersive()) { // windows 8 app mode margin_ = 10; diff --git a/libIME/ImeWindow.h b/libIME/ImeWindow.h index 785a26e..d1d9306 100644 --- a/libIME/ImeWindow.h +++ b/libIME/ImeWindow.h @@ -39,6 +39,7 @@ class ImeWindow: public Window { } void setFont(HFONT f); + void setFontSize(float size) { fontSize_ = size; } virtual void recalculateSize(); protected: @@ -50,6 +51,7 @@ class ImeWindow: public Window { TextService* textService_; POINTS oldPos; HFONT font_; + float fontSize_; int margin_; };