Skip to content

Commit

Permalink
feat: Use 9-patch bitmap to draw candidate window
Browse files Browse the repository at this point in the history
  • Loading branch information
kanru committed Sep 1, 2024
1 parent f57c41d commit 10c6469
Show file tree
Hide file tree
Showing 14 changed files with 498 additions and 55 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ 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
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /GL /Gw")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL /Gw")
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION $<CONFIG:Release>)

enable_testing()
Expand Down
4 changes: 3 additions & 1 deletion ChewingTextService/ChewingTextService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,9 @@ void TextService::showCandidates(Ime::EditSession* session) {
// The candidate window created should be a child window of the composition window.
// Please see Ime::CandidateWindow::CandidateWindow() for an example.
if(!candidateWindow_) {
candidateWindow_ = new Ime::CandidateWindow(this, session);
std::wstring bitmap_path = static_cast<ImeModule*>(imeModule())->programDir();
bitmap_path += L"\\Assets\\bubble.9.png";
candidateWindow_ = new Ime::CandidateWindow(this, session, bitmap_path);
candidateWindow_->setFont(font_);
candidateWindow_->setFontSize(config().fontSize);
}
Expand Down
Binary file added assets/bubble.9.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions installer/windows-chewing-tsf.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
<File Source="Dictionary\tsi.dat" Bitness="always32" />
<File Source="Dictionary\word.dat" Bitness="always32" />
</Directory>
<Directory Id="Assets" Name="Assets">
<File Source="assets\bubble.9.png" Bitness="always32" />
</Directory>
<File Source="x86\chewing-msvc.dll" Bitness="always32" />
<Component Bitness="always32">
<File Source="x86\ChewingTextService.dll">
Expand Down
90 changes: 57 additions & 33 deletions libIME/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,47 +1,71 @@
project(libIME)
find_package(Corrosion QUIET)
if(NOT Corrosion_FOUND)
FetchContent_Declare(
Corrosion
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
GIT_TAG 64289b1d79d6d19cd2e241db515381a086bb8407 # v0.5
FIND_PACKAGE_ARGS
)
FetchContent_MakeAvailable(Corrosion)
endif()

corrosion_import_crate(MANIFEST_PATH ../rustlib/Cargo.toml CRATES rustlib CRATE_TYPES staticlib)
# corrosion_set_env_vars(rustlib "CFLAGS=-MDd" "CXXFLAGS=-MDd")
corrosion_add_cxxbridge(rustlib_bridge
CRATE rustlib
FILES lib.rs
)

add_library(libIME_static STATIC
# Core TSF part
${PROJECT_SOURCE_DIR}/ImeModule.cpp
${PROJECT_SOURCE_DIR}/ImeModule.h
${PROJECT_SOURCE_DIR}/libIME.cpp
${PROJECT_SOURCE_DIR}/libIME.h
${PROJECT_SOURCE_DIR}/TextService.cpp
${PROJECT_SOURCE_DIR}/TextService.h
${PROJECT_SOURCE_DIR}/KeyEvent.cpp
${PROJECT_SOURCE_DIR}/KeyEvent.h
${PROJECT_SOURCE_DIR}/EditSession.cpp
${PROJECT_SOURCE_DIR}/EditSession.h
${PROJECT_SOURCE_DIR}/DisplayAttributeInfo.cpp
${PROJECT_SOURCE_DIR}/DisplayAttributeInfo.h
${PROJECT_SOURCE_DIR}/DisplayAttributeInfoEnum.cpp
${PROJECT_SOURCE_DIR}/DisplayAttributeInfoEnum.h
${PROJECT_SOURCE_DIR}/DisplayAttributeProvider.cpp
${PROJECT_SOURCE_DIR}/DisplayAttributeProvider.h
${PROJECT_SOURCE_DIR}/LangBarButton.cpp
${PROJECT_SOURCE_DIR}/LangBarButton.h
${PROJECT_SOURCE_DIR}/Utils.cpp
${PROJECT_SOURCE_DIR}/Utils.h
${PROJECT_SOURCE_DIR}/ComPtr.h
${PROJECT_SOURCE_DIR}/WindowsVersion.h
ImeModule.cpp
ImeModule.h
libIME.cpp
libIME.h
TextService.cpp
TextService.h
KeyEvent.cpp
KeyEvent.h
EditSession.cpp
EditSession.h
DisplayAttributeInfo.cpp
DisplayAttributeInfo.h
DisplayAttributeInfoEnum.cpp
DisplayAttributeInfoEnum.h
DisplayAttributeProvider.cpp
DisplayAttributeProvider.h
LangBarButton.cpp
LangBarButton.h
Utils.cpp
Utils.h
ComPtr.h
WindowsVersion.h
# GUI-related code
${PROJECT_SOURCE_DIR}/DrawUtils.h
${PROJECT_SOURCE_DIR}/DrawUtils.cpp
${PROJECT_SOURCE_DIR}/Window.cpp
${PROJECT_SOURCE_DIR}/Window.h
${PROJECT_SOURCE_DIR}/ImeWindow.cpp
${PROJECT_SOURCE_DIR}/ImeWindow.h
${PROJECT_SOURCE_DIR}/MessageWindow.cpp
${PROJECT_SOURCE_DIR}/MessageWindow.h
${PROJECT_SOURCE_DIR}/CandidateWindow.h
${PROJECT_SOURCE_DIR}/CandidateWindow.cpp
DrawUtils.h
DrawUtils.cpp
Window.cpp
Window.h
ImeWindow.cpp
ImeWindow.h
MessageWindow.cpp
MessageWindow.h
CandidateWindow.h
CandidateWindow.cpp
NinePatch.h
NinePatch.cpp
)

target_compile_features(libIME_static PUBLIC cxx_std_17)

target_link_libraries(libIME_static
PUBLIC rustlib_bridge
PUBLIC shlwapi.lib
PUBLIC d2d1.lib
PUBLIC d3d11.lib
PUBLIC dwrite.lib
PUBLIC dcomp.lib
)
set_target_properties(rustlib_bridge PROPERTIES
MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
set_target_properties(libIME_static PROPERTIES
MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
60 changes: 41 additions & 19 deletions libIME/CandidateWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,31 @@
#include <d2d1_1.h>
#include <d2d1_1helper.h>
#include <d3d11_1.h>
#include <dcomp.h>
#include <dwrite_1.h>
#include <dxgi1_2.h>
#include <tchar.h>
#include <unknwnbase.h>
#include <windows.h>
#include <winrt/base.h>

#include <cassert>
#include <string>

#include "DrawUtils.h"
#include "EditSession.h"
#include "NinePatch.h"
#include "TextService.h"
#include "rustlib_bridge/lib.h"

using namespace std;
using winrt::check_hresult;
using winrt::com_ptr;

namespace Ime {

CandidateWindow::CandidateWindow(TextService *service, EditSession *session)
CandidateWindow::CandidateWindow(TextService *service, EditSession *session,
wstring bitmap_path)
: ImeWindow(service),
refCount_(1),
shown_(false),
Expand All @@ -51,20 +57,21 @@ CandidateWindow::CandidateWindow(TextService *service, EditSession *session)
currentSel_(0),
hasResult_(false),
useCursor_(true),
selKeyWidth_(0) {
selKeyWidth_(0),
ninePatch_(bitmap_path) {
if (service->isImmersive()) { // windows 8 app mode
margin_ = 10;
rowSpacing_ = 8;
colSpacing_ = 12;
} else { // desktop mode
margin_ = 5;
margin_ = ninePatch_.GetMargin();
rowSpacing_ = 4;
colSpacing_ = 8;
}

HWND parent = service->compositionWindow(session);
create(parent, WS_POPUP | WS_CLIPCHILDREN,
WS_EX_TOOLWINDOW | WS_EX_TOPMOST);
WS_EX_NOREDIRECTIONBITMAP | WS_EX_TOOLWINDOW | WS_EX_TOPMOST);

check_hresult(
D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, factory_.put()));
Expand All @@ -86,32 +93,42 @@ CandidateWindow::CandidateWindow(TextService *service, EditSession *session)
target_.put()));

DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.Width = 0;
swapChainDesc.Height = 0;
swapChainDesc.Width = 100;
swapChainDesc.Height = 100;
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;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;

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(factory->CreateSwapChainForComposition(
d3device.get(), &swapChainDesc, 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));
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,
D2D1_ALPHA_MODE_PREMULTIPLIED));
check_hresult(target_->CreateBitmapFromDxgiSurface(
surface.get(), &bitmap_props, bitmap.put()));
target_->SetTarget(bitmap.get());

// Setup Direct Composition
check_hresult(DCompositionCreateDevice(
dxdevice.get(), __uuidof(dcompDevice_), dcompDevice_.put_void()));

check_hresult(
dcompDevice_->CreateTargetForHwnd(hwnd_, true, dcompTarget.put()));

check_hresult(dcompDevice_->CreateVisual(dcompVisual.put()));
check_hresult(dcompVisual->SetContent(swapChain_.get()));
check_hresult(dcompTarget->SetRoot(dcompVisual.get()));
check_hresult(dcompDevice_->Commit());
}

CandidateWindow::~CandidateWindow(void) {}
Expand Down Expand Up @@ -291,9 +308,13 @@ void CandidateWindow::onPaint(WPARAM wp, LPARAM lp) {
D2D1::RectF(rc.left, rc.top, rc.right, rc.bottom), pBrush.get(),
3.0f);
} else {
::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);
// ::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);
ninePatch_.DrawBitmap(
target_.get(), D2D1::RectF(rc.left, rc.top, rc.right, rc.bottom));
}

// paint items
Expand Down Expand Up @@ -403,7 +424,8 @@ void CandidateWindow::resizeSwapChain(int width, int height) {
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));
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,
D2D1_ALPHA_MODE_PREMULTIPLIED));
check_hresult(target_->CreateBitmapFromDxgiSurface(
surface.get(), &bitmap_props, bitmap.put()));
target_->SetTarget(bitmap.get());
Expand Down Expand Up @@ -492,7 +514,7 @@ void CandidateWindow::paintItemD2D(ID2D1RenderTarget *pRenderTarget, int i,
textRect.right = textRect.left + selKeyWidth_;

// FIXME: make the color of strings configurable.
COLORREF selKeyColor = RGB(0, 0, 255);
COLORREF selKeyColor = RGB(255, 0, 0);
COLORREF textColor = GetSysColor(COLOR_WINDOWTEXT);
pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(selKeyColor),
pSelKeyBrush.put());
Expand Down
10 changes: 9 additions & 1 deletion libIME/CandidateWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@
#include <Unknwn.h>
#include <d2d1_1.h>
#include <d3d11_1.h>
#include <dcomp.h>
#include <winrt/base.h>

#include <string>
#include <vector>

#include "ImeWindow.h"
#include "NinePatch.h"

namespace Ime {

Expand All @@ -39,7 +41,8 @@ class KeyEvent;
// TODO: make the candidate window looks different in immersive mode
class CandidateWindow : public ImeWindow, public ITfCandidateListUIElement {
public:
CandidateWindow(TextService *service, EditSession *session);
CandidateWindow(TextService *service, EditSession *session,
std::wstring bitmap_path);

// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj);
Expand Down Expand Up @@ -112,6 +115,11 @@ class CandidateWindow : public ImeWindow, public ITfCandidateListUIElement {
winrt::com_ptr<ID2D1DeviceContext> target_;
winrt::com_ptr<IDXGISwapChain1> swapChain_;
winrt::com_ptr<ID2D1Factory1> factory_;
winrt::com_ptr<IDCompositionDevice> dcompDevice_;
winrt::com_ptr<IDCompositionTarget> dcompTarget;
winrt::com_ptr<IDCompositionVisual> dcompVisual;

NinePatch ninePatch_;

ULONG refCount_;
BOOL shown_;
Expand Down
Loading

0 comments on commit 10c6469

Please sign in to comment.