first commit

This commit is contained in:
fanlumaster committed 2025-03-25 15:27:42 +08:00
commit abff7464e0
18 files changed
+1361

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
---
BasedOnStyle: Microsoft
IndentWidth: 4
SortIncludes: true
ColumnLimit: 80
+18
View File
@@ -0,0 +1,18 @@
CompileFlags:
Add: [
-DUNICODE,
-D_UNICODE,
-DFANY_DEBUG,
"-std=c++17",
"-IC:\\Users\\SonnyCalcr\\EDisk\\CppCodes\\Win32Codes\\DirectWriteExperiment\\includes", # you should replace this to your own include path of this project
"-IC:\\Users\\SonnyCalcr\\EDisk\\CppCodes\\Win32Codes\\DirectWriteExperiment\\fmt\\include", # you should replace this to your own include path of fmt
"-IC:\\Users\\SonnyCalcr\\.nuget\\packages\\microsoft.web.webview2\\1.0.3124.44\\build\\native\\include",
"-IC:\\Users\\SonnyCalcr\\.nuget\\packages\\microsoft.windows.implementationlibrary\\1.0.240803.1\\include",
-Wno-microsoft-goto,
-Wno-nonportable-include-path,
-Wno-microsoft-extra-qualification,
# -Wno-unused-value,
] # ignore some specific warnings
CompilationDatabase: build/ # Search build/ directory for compile_commands.json
# Diagnostics:
# UnusedIncludes: None # ignore unused includes
+2
View File
@@ -0,0 +1,2 @@
build/
log.*
+3
View File
@@ -0,0 +1,3 @@
[submodule "fmt"]
path = fmt
url = https://github.com/fmtlib/fmt.git
+11
View File
@@ -0,0 +1,11 @@
{
"Debug snippets": {
"scope": "c,cpp",
"prefix": "dbug",
"body": [
"#ifdef FANY_DEBUG",
"#endif",
],
"description": "Add debug macro"
}
}
+41
View File
@@ -0,0 +1,41 @@
cmake_minimum_required(VERSION 3.25.0)
set(PROJECT_NAME "fanimeui")
project(${PROJECT_NAME} VERSION 1.0)
add_subdirectory(fmt)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
add_definitions(
/D_UNICODE=1
/DUNICODE=1
)
# add_definitions(
# /DFANY_DEBUG=1
# )
include_directories("./includes/")
set(HEADER_FILES
./includes/main.h
)
set(SRC_FILES
"src/webview2/MainWindow.cpp"
)
add_executable(fanimeui WIN32
${HEADER_FILES}
${SRC_FILES}
)
set_target_properties(${PROJECT_NAME} PROPERTIES VS_GLOBAL_WebView2LoaderPreference "Static")
set_target_properties(${PROJECT_NAME} PROPERTIES VS_USER_PROPS "${CMAKE_SOURCE_DIR}/FanIME_UI.props")
set_target_properties(${PROJECT_NAME} PROPERTIES VS_PACKAGE_REFERENCES "Microsoft.Web.WebView2_1.0.3124.44;Microsoft.Windows.ImplementationLibrary_1.0.240803.1")
target_link_libraries(${PROJECT_NAME} fmt::fmt)
+29
View File
@@ -0,0 +1,29 @@
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.vcxproj'">
<ProjectCapability Include="PackageReferences" />
</ItemGroup>
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.vcxproj'">
<!-- NuGet 包恢复配置 -->
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<NuGetPackageImportStamp>true</NuGetPackageImportStamp>
<!-- 目标平台和框架配置 -->
<Platform Condition="'$(Platform)' == ''">x64</Platform>
<TargetFramework>native</TargetFramework>
<!-- NuGet 包路径配置 -->
<NuGetPackageRoot Condition="'$(NuGetPackageRoot)' == ''">$(UserProfile)\.nuget\packages</NuGetPackageRoot>
<!-- WebView2 配置 -->
<WebView2LoaderPreference>Static</WebView2LoaderPreference>
<!-- NuGet 目标框架配置 -->
<NuGetTargetMoniker Condition="'$(NuGetTargetMoniker)' == ''">native,Version=v0.0</NuGetTargetMoniker>
<RuntimeIdentifiers Condition="'$(RuntimeIdentifiers)' == ''">win;win-x86;win-x64;win-arm;win-arm64</RuntimeIdentifiers>
</PropertyGroup>
</Project>
+7
View File
@@ -0,0 +1,7 @@
# Win32 direct2d and some webview experiment
Try to figure out all needed rendering things about Windows TSF input method.
![](https://s2.loli.net/2025/03/20/4ZdbHLWAeBJOqIo.png)
![](https://s2.loli.net/2025/03/24/n1X7RZr9LjEWO23.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 820 KiB

Submodule
+1
Submodule server/fmt added at b2dfcb2b80.
@@ -0,0 +1,142 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>横向候选框</title>
<style>
body {
font-family: Arial, sans-serif;
height: 100vh;
margin: 0;
overflow: hidden;
border-radius: 6px;
background: transparent;
}
.container {
margin-top: 0px;
margin-left: 0px;
background-color: #202020;
padding: 2px;
border-radius: 6px;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
width: fit-content;
user-select: none;
border: 2px solid #9b9b9b2e;
}
.pinyin-row {
padding-right: 1px;
margin-bottom: 2px;
}
.candidates-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.row {
padding-right: 1px;
margin: 0 1px;
}
.cand:hover {
border-radius: 6px;
background-color: #414141;
}
.row-wrapper {
position: relative;
}
.first {
background-color: #3e3e3eb9;
border-radius: 6px;
}
.first::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
height: 16px;
width: 3px;
background: linear-gradient(to bottom, #ff7eb3, #ff758c, #ff5a5f);
border-radius: 8px;
}
.text {
padding-left: 8px;
padding-right: 8px;
color: #e9e8e8;
}
</style>
</head>
<body>
<div class="container">
<div class="pinyin-row">
<div class="row pinyin">
<div class="text">ni'uo</div>
</div>
</div>
<div class="candidates-row">
<!--1Anchor-->
<div class="row-wrapper">
<div class="row cand first">
<div class="text">1. 你说什么</div>
</div>
</div>
<!--2Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">2. 笔画</div>
</div>
</div>
<!--3Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">3. 量子</div>
</div>
</div>
<!--4Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">4. 牛魔</div>
</div>
</div>
<!--5Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">5. 仙人</div>
</div>
</div>
<!--6Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">6. 可恨</div>
</div>
</div>
<!--7Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">7. 木槿</div>
</div>
</div>
<!--8Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">8. 无量</div>
</div>
</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,143 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>垂直候选框</title>
<style>
body {
font-family: Arial, sans-serif;
height: 100vh;
margin: 0;
overflow: hidden;
border-radius: 6px;
background: transparent;
}
.container {
/* margin-top: 50px;
margin-left: 50px; */
margin-top: 0px;
margin-left: 0px;
background-color: #202020;
padding: 2px;
border-radius: 6px;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5);
width: 100px;
user-select: none;
border: 2px solid #9b9b9b2e;
}
.row {
justify-content: space-between;
padding: 2px;
margin-top: 2px;
}
.cand:hover {
border-radius: 6px;
background-color: #414141;
}
.row-wrapper {
position: relative;
}
/* .cand:hover::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
height: 16px;
width: 3px;
background: linear-gradient(to bottom, #ff7eb3, #ff758c, #ff5a5f);
border-radius: 8px;
} */
.first {
background-color: #3e3e3eb9;
border-radius: 6px;
}
.first::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
height: 16px;
width: 3px;
background: linear-gradient(to bottom, #ff7eb3, #ff758c, #ff5a5f);
border-radius: 8px;
}
.text {
padding-left: 8px;
color: #e9e8e8;
}
</style>
</script>
</head>
<body>
<div class="container">
<!--0Anchor-->
<div class="row pinyin">
<div class="text">ni'uo</div>
</div>
<!--1Anchor-->
<div class="row-wrapper">
<div class="row cand first">
<div class="text">1. 你说</div>
</div>
</div>
<!--2Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">2. 笔画</div>
</div>
</div>
<!--3Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">3. 量子</div>
</div>
</div>
<!--4Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">4. 牛魔</div>
</div>
</div>
<!--5Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">5. 仙人</div>
</div>
</div>
<!--6Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">6. 可恨</div>
</div>
</div>
<!--7Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">7. 木槿</div>
</div>
</div>
<!--8Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">8. 无量</div>
</div>
</div>
<!--9Anchor-->
</div>
</body>
</html>
+3
View File
@@ -0,0 +1,3 @@
#include <windows.h>
#include <stdint.h>
#include <algorithm>
+26
View File
@@ -0,0 +1,26 @@
# generate compile to exe files
$currentDirectory = Get-Location
$cmakeListsPath = Join-Path -Path $currentDirectory -ChildPath "CMakeLists.txt"
if (-not (Test-Path $cmakeListsPath))
{
Write-Host("No CMakeLists.txt in current directory, please check.")
return
}
Write-Host "Start generating and compiling..."
$buildFolderPath = ".\build"
if (-not (Test-Path $buildFolderPath))
{
New-Item -ItemType Directory -Path $buildFolderPath | Out-Null
Write-Host "build folder created."
}
cmake -G "Visual Studio 17 2022" -A x64 -S . -B ./build/
if ($LASTEXITCODE -eq 0)
{
cmake --build ./build/ --config DEBUG
}
+65
View File
@@ -0,0 +1,65 @@
#
# generate, compile and run exe files
#
function getExePathFromCMakeLists()
{
$content = Get-Content -Path "./CMakeLists.txt"
$lastLine = ""
$contentContainedExeName = ""
foreach($line in $content)
{
if ($line.StartsWith("add_executable"))
{
$index = $line.IndexOf("(")
$contentContainedExeName = $line.Substring($index + 1)
if ([string]::IsNullOrEmpty($contentContainedExeName))
{
$lastLine = $line
continue
}
break
} elseif ($lastLine.StartsWith("add_executable"))
{
$contentContainedExeName = $line
break
}
$lastLine = $line
}
$result = -split $contentContainedExeName
$exePath = "./build/DEBUG/" + $result[0] + ".exe"
return $exePath
}
$currentDirectory = Get-Location
$cmakeListsPath = Join-Path -Path $currentDirectory -ChildPath "CMakeLists.txt"
if (-not (Test-Path $cmakeListsPath))
{
Write-Host("No CMakeLists.txt in current directory, please check.")
return
}
Write-Host "Start generating and compiling..."
$buildFolderPath = ".\build"
if (-not (Test-Path $buildFolderPath))
{
New-Item -ItemType Directory -Path $buildFolderPath | Out-Null
Write-Host "build folder created."
}
cmake -G "Visual Studio 17 2022" -A x64 -S . -B ./build/
if ($LASTEXITCODE -eq 0)
{
cmake --build ./build/ --config DEBUG
if ($LASTEXITCODE -eq 0)
{
$exePath = getExePathFromCMakeLists
Write-Host "start running as follows..."
Write-Host "=================================================="
Invoke-Expression $exePath
}
}
+38
View File
@@ -0,0 +1,38 @@
#
# run exe file that has already been compiled before
#
function getExePathFromCMakeLists()
{
$content = Get-Content -Path "./CMakeLists.txt"
$lastLine = ""
$contentContainedExeName = ""
foreach($line in $content)
{
if ($line.StartsWith("add_executable"))
{
$index = $line.IndexOf("(")
$contentContainedExeName = $line.Substring($index + 1)
if ([string]::IsNullOrEmpty($contentContainedExeName))
{
$lastLine = $line
continue
}
break
} elseif ($lastLine.StartsWith("add_executable"))
{
$contentContainedExeName = $line
break
}
$lastLine = $line
}
$result = -split $contentContainedExeName
$exePath = "./build/DEBUG/" + $result[0] + ".exe"
return $exePath
}
$exePath = getExePathFromCMakeLists
Write-Host "start running as follows..."
Write-Host "=================================================="
Invoke-Expression $exePath
+252
View File
@@ -0,0 +1,252 @@
#include <d2d1.h>
#include <dwmapi.h>
#include <dwrite.h>
#include <minwindef.h>
#include <windef.h>
#include <windows.h>
#pragma comment(lib, "d2d1")
#pragma comment(lib, "dwrite")
#pragma comment(lib, "dwmapi.lib")
ID2D1Factory *pD2DFactory = nullptr;
ID2D1HwndRenderTarget *pRenderTarget = nullptr;
ID2D1SolidColorBrush *pBrush = nullptr;
IDWriteFactory *pDWriteFactory = nullptr;
IDWriteTextFormat *pTextFormat = nullptr;
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
FLOAT GetWindowScale(HWND hwnd);
bool InitD2DAndDWrite()
{
HRESULT hr = D2D1CreateFactory( //
D2D1_FACTORY_TYPE_SINGLE_THREADED, //
&pD2DFactory //
);
if (FAILED(hr))
return false;
hr = DWriteCreateFactory( //
DWRITE_FACTORY_TYPE_SHARED, //
__uuidof(IDWriteFactory), //
reinterpret_cast<IUnknown **>(&pDWriteFactory) //
);
if (FAILED(hr))
return false;
hr = pDWriteFactory->CreateTextFormat(L"Segoe UI", //
nullptr, //
DWRITE_FONT_WEIGHT_NORMAL, //
DWRITE_FONT_STYLE_NORMAL, //
DWRITE_FONT_STRETCH_NORMAL, //
24.0f, //
L"en-us", //
&pTextFormat //
);
if (FAILED(hr))
return false;
return true;
}
bool InitD2DRenderTarget(HWND hwnd)
{
if (!pD2DFactory)
return false;
RECT rc;
GetClientRect(hwnd, &rc);
auto renderTargetProps = D2D1::RenderTargetProperties( //
D2D1_RENDER_TARGET_TYPE_DEFAULT, //
D2D1::PixelFormat( //
DXGI_FORMAT_UNKNOWN, //
D2D1_ALPHA_MODE_PREMULTIPLIED //
) //
);
auto hwndRenderTargetProps = D2D1::HwndRenderTargetProperties( //
hwnd, //
D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top), //
D2D1_PRESENT_OPTIONS_IMMEDIATELY //
);
HRESULT hr = pD2DFactory->CreateHwndRenderTarget( //
renderTargetProps, //
hwndRenderTargetProps, //
&pRenderTarget //
);
if (SUCCEEDED(hr))
{
hr = pRenderTarget->CreateSolidColorBrush( //
D2D1::ColorF(D2D1::ColorF::White), //
&pBrush //
);
}
return SUCCEEDED(hr);
}
void OnPaint(HWND hwnd)
{
if (!pRenderTarget)
return;
pRenderTarget->BeginDraw();
// Clear baakground to transparent
// pRenderTarget->Clear(D2D1::ColorF(0, 0, 0, 0));
pRenderTarget->Clear(D2D1::ColorF(25 / 255.0, 25 / 255.0, 25 / 255.0, 0.5));
/*
FLOAT scale = GetWindowScale(hwnd);
D2D1_RECT_F borderRect = D2D1::RectF( //
static_cast<FLOAT>(0), //
static_cast<FLOAT>(0), //
static_cast<FLOAT>(600 / scale), //
static_cast<FLOAT>(600 / scale) //
);
pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::White, 1.0f));
E
pRenderTarget->FillRectangle(D2D1::RectF(50, 50, 400, 300), pBrush)
pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Red, 1.0f));
pRenderTarget->FillRectangle(D2D1::RectF(200, 200, 500, 400), pBrush);
pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Green, 0.3f));
pRenderTarget->FillRectangle(D2D1::RectF(300, 100, 600, 250), pBrush);
pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::White, 1.0f)); //
pRenderTarget->DrawTextW( //
L"Transparent Window and Direct2D", //
31, //
pTextFormat, //
D2D1::RectF(5, 5, 600, 200), //
pBrush //
);
*/
HRESULT hr = pRenderTarget->EndDraw();
if (hr == D2DERR_RECREATE_TARGET)
{
pRenderTarget->Release();
InitD2DRenderTarget(hwnd);
}
}
HWND CreateTransparentWindow(HINSTANCE hInstance)
{
WNDCLASS wc = {0};
wc.lpfnWndProc = WndProc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.hInstance = hInstance;
wc.lpszClassName = L"TransparentWindowClass";
if (!RegisterClass(&wc))
return nullptr;
HWND hwnd = CreateWindowEx(WS_EX_LAYERED, //
wc.lpszClassName, //
L"TransparentWindow", //
WS_POPUP, //
100, 100, 600, 600, //
nullptr, nullptr, hInstance, nullptr);
if (hwnd)
{
SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA);
MARGINS mar = {-1};
DwmExtendFrameIntoClientArea(hwnd, &mar);
}
return hwnd;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
InitD2DRenderTarget(hwnd);
return 0;
case WM_PAINT:
case WM_DISPLAYCHANGE:
OnPaint(hwnd);
ValidateRect(hwnd, nullptr);
return 0;
case WM_MOUSEMOVE: {
float x = (float)LOWORD(lParam);
float y = (float)HIWORD(lParam);
RECT rc;
GetClientRect(hwnd, &rc);
if (x >= rc.left && x <= rc.right && y >= rc.top && y <= rc.bottom)
{
SetCursor(LoadCursor(nullptr, IDC_ARROW));
}
break;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow)
{
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
if (!InitD2DAndDWrite())
return -1;
HWND hwnd = CreateTransparentWindow(hInstance);
if (!hwnd)
return -1;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (pBrush)
pBrush->Release();
if (pRenderTarget)
pRenderTarget->Release();
if (pDWriteFactory)
pDWriteFactory->Release();
if (pTextFormat)
pTextFormat->Release();
if (pD2DFactory)
pD2DFactory->Release();
return (int)msg.wParam;
}
FLOAT GetWindowScale(HWND hwnd)
{
UINT dpi = GetDpiForWindow(hwnd);
FLOAT scale = dpi / 96.0f;
return scale;
}
+574
View File
@@ -0,0 +1,574 @@
#include "WebView2.h"
#include "fmt/core.h"
#include "fmt/xchar.h"
#include <chrono>
#include <dwmapi.h>
#include <fstream>
#include <intsafe.h>
#include <iostream>
#include <sstream>
#include <stdlib.h>
#include <string>
#include <synchapi.h>
#include <tchar.h>
#include <vector>
#include <windows.h>
#include <winnt.h>
#include <winuser.h>
#include <wrl.h>
#include <wrl/client.h>
//
// Message define
//
UINT WM_SHOW_MAIN_WINDOW;
UINT WM_HIDE_MAIN_WINDOW;
UINT WM_MOVE_CANDIDATE_WINDOW;
static TCHAR szWindowClass[] = _T("global_candidate_window");
static TCHAR szTitle[] = _T("WebView sample");
std::wstring candStr = L"";
static std::wstring HTMLString = LR"(
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vertical Candidate Window</title>
<style>
body {
font-family: Arial, sans-serif;
height: 100vh;
margin: 0;
overflow: hidden;
border-radius: 6px;
background: transparent;
}
.container {
/* margin-top: 50px;
margin-left: 50px; */
margin-top: 0px;
margin-left: 0px;
background-color: #202020;
padding: 2px;
border-radius: 6px;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5);
width: 100px;
user-select: none;
border: 2px solid #9b9b9b2e;
}
.row {
justify-content: space-between;
padding: 2px;
margin-top: 2px;
}
.cand:hover {
border-radius: 6px;
background-color: #414141;
}
.row-wrapper {
position: relative;
}
/* .cand:hover::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
height: 16px;
width: 3px;
background: linear-gradient(to bottom, #ff7eb3, #ff758c, #ff5a5f);
border-radius: 8px;
} */
.first {
background-color: #3e3e3eb9;
border-radius: 6px;
}
.first::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
height: 16px;
width: 3px;
background: linear-gradient(to bottom, #ff7eb3, #ff758c, #ff5a5f);
border-radius: 8px;
}
.text {
padding-left: 8px;
color: #e9e8e8;
}
</style>
</script>
</head>
<body>
<div class="container">
<div class="row pinyin">
<div class="text">ni'uo</div>
</div>
<div class="row-wrapper">
<div class="row cand first">
<div class="text">1. 你说</div>
</div>
</div>
<div class="row-wrapper">
<div class="row cand">
<div class="text">2. 笔画</div>
</div>
</div>
<div class="row-wrapper">
<div class="row cand">
<div class="text">3. 量子</div>
</div>
</div>
<div class="row-wrapper">
<div class="row cand">
<div class="text">4. 牛魔</div>
</div>
</div>
<div class="row-wrapper">
<div class="row cand">
<div class="text">5. 仙人</div>
</div>
</div>
<div class="row-wrapper">
<div class="row cand">
<div class="text">6. 可恨</div>
</div>
</div>
<div class="row-wrapper">
<div class="row cand">
<div class="text">7. 木槿</div>
</div>
</div>
<div class="row-wrapper">
<div class="row cand">
<div class="text">8. 无量</div>
</div>
</div>
</div>
</body>
</html>
)";
static std::wstring BodyString = LR"(
<div class="container">
<!--0Anchor-->
<div class="row pinyin">
<div class="text">{0}</div>
</div>
<!--1Anchor-->
<div class="row-wrapper">
<div class="row cand first">
<div class="text">{1}</div>
</div>
</div>
<!--2Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">{2}</div>
</div>
</div>
<!--3Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">{3}</div>
</div>
</div>
<!--4Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">{4}</div>
</div>
</div>
<!--5Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">{5}</div>
</div>
</div>
<!--6Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">{6}</div>
</div>
</div>
<!--7Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">{7}</div>
</div>
</div>
<!--8Anchor-->
<div class="row-wrapper">
<div class="row cand">
<div class="text">{8}</div>
</div>
</div>
<!--9Anchor-->
</div>
)";
HINSTANCE hInst = 0;
using namespace Microsoft::WRL;
static ComPtr<ICoreWebView2Controller> webviewController;
static ComPtr<ICoreWebView2> webview;
ComPtr<ICoreWebView2_3> webview3;
ComPtr<ICoreWebView2Controller2> webviewController2;
void LogMessageW(const wchar_t *message)
{
std::time_t now = std::time(nullptr);
std::tm *localTime = std::localtime(&now);
wchar_t timeBuffer[80];
wcsftime(timeBuffer, sizeof(timeBuffer) / sizeof(wchar_t),
L"%Y-%m-%d %H:%M:%S", localTime);
std::wstring logfilePath = L"C:"
L"\\Users\\SonnyCalcr\\EDisk\\CppCodes\\Win32Cod"
L"es\\DirectWriteExperiment\\log.txt";
std::wofstream logFile(logfilePath, std::ios_base::app);
if (logFile.is_open())
{
logFile.imbue(std::locale("Chinese_China.65001"));
logFile << L"[" << timeBuffer << L"] " << message;
logFile << std::endl;
logFile.close();
}
}
void UpdateHtmlContentWithJavaScript(ComPtr<ICoreWebView2> webview,
const std::wstring &newContent)
{
if (webview != nullptr)
{
std::wstring script =
L"document.body.innerHTML = `" + newContent + L"`;";
webview->ExecuteScript(script.c_str(), nullptr);
}
}
void MeasureDomUpdateTime(ComPtr<ICoreWebView2> webview)
{
std::wstring script =
LR"(document.body.innerHTML = '<div>1. 原来</div> <div>2. 如此</div> <div>3. 竟然</div> <div>4. 这样</div> <div>5. 可恶</div> <div>6. 棋盘</div> <div>7. 磨合</div> <div>8. 樱花</div> </body>';)";
auto start = std::chrono::high_resolution_clock::now();
webview->ExecuteScript(script.c_str(), nullptr);
auto end = std::chrono::high_resolution_clock::now();
auto duration =
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::wstring message =
L"DOM update time: " + std::to_wstring(duration.count()) + L" μs";
LogMessageW(message.c_str());
}
void inflateCandidateWindow(std::wstring &str)
{
std::wstringstream wss(str);
std::wstring token;
std::vector<std::wstring> words;
while (std::getline(wss, token, L','))
{
words.push_back(token);
}
int size = words.size();
while (words.size() < 9)
{
words.push_back(L"");
}
std::wstring result = fmt::format( //
BodyString, //
words[0], //
words[1], //
words[2], //
words[3], //
words[4], //
words[5], //
words[6], //
words[7], //
words[8] //
); //
if (size < 9)
{
result = result.substr( //
0, //
result.find(fmt::format(L"<!--{}Anchor-->", size)) //
) //
+ //
L"</div>"; //
}
UpdateHtmlContentWithJavaScript(webview, result);
}
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
if (message == WM_SHOW_MAIN_WINDOW)
{
ShowWindow(hWnd, SW_SHOWNOACTIVATE);
return 0;
}
if (message == WM_HIDE_MAIN_WINDOW)
{
ShowWindow(hWnd, SW_HIDE);
if (candStr.find(L",") == 1)
{
inflateCandidateWindow(candStr);
}
else
{
UpdateHtmlContentWithJavaScript(webview, L"");
}
return 0;
}
switch (message)
{
case WM_MOUSEACTIVATE:
// Stop the window from being activated by mouse click
return MA_NOACTIVATE;
case WM_COPYDATA: {
COPYDATASTRUCT *pcds = (COPYDATASTRUCT *)lParam;
if (pcds->dwData == 0)
{
POINT *pt = (POINT *)pcds->lpData;
MoveWindow( //
hWnd, //
pt->x, //
pt->y + 3, //
(108 + 15) * 1.5, //
(246 + 15) * 1.5, //
TRUE //
); //
}
else if (pcds->dwData == 1)
{
WCHAR *p = (WCHAR *)pcds->lpData;
std::wstring str = p;
candStr = str;
#ifdef FANY_DEBUG
LogMessageW(str.c_str());
#endif
inflateCandidateWindow(str);
}
return 1;
}
case WM_ACTIVATE: {
if (LOWORD(wParam) != WA_INACTIVE)
{
ShowWindow(hWnd, SW_SHOWNOACTIVATE);
}
break;
}
case WM_DESTROY: {
PostQuitMessage(0);
break;
}
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
void InitWebview(HWND hWnd)
{
CreateCoreWebView2EnvironmentWithOptions(
nullptr, nullptr, nullptr,
Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
[hWnd](HRESULT result, ICoreWebView2Environment *env) -> HRESULT {
if (result != S_OK)
{
MessageBox(hWnd, L"Failed to create WebView2 environment.",
L"Error", MB_OK);
return result;
}
// Create a CoreWebView2Controller and get the associated
// CoreWebView2
env->CreateCoreWebView2Controller(
hWnd,
Callback<
ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
[hWnd](HRESULT result,
ICoreWebView2Controller *controller) -> HRESULT {
if (controller == nullptr || FAILED(result))
{
MessageBox(
hWnd,
L"Failed to create WebView2 controller.",
L"Error", MB_OK);
return E_FAIL;
}
webviewController = controller;
webviewController->get_CoreWebView2(
webview.GetAddressOf());
if (webview == nullptr)
{
MessageBox(hWnd,
L"Failed to get WebView2 instance.",
L"Error", MB_OK);
return E_FAIL;
}
// Add a few settings for the webview
ICoreWebView2Settings *settings;
webview->get_Settings(&settings);
settings->put_IsScriptEnabled(TRUE);
settings->put_AreDefaultScriptDialogsEnabled(TRUE);
settings->put_IsWebMessageEnabled(TRUE);
settings->put_AreHostObjectsAllowed(TRUE);
if (webview->QueryInterface(
IID_PPV_ARGS(&webview3)) == S_OK)
{
webview3->SetVirtualHostNameToFolderMapping(
L"appassets",
L"C:"
L"\\Users\\SonnyCalcr\\AppData\\Roaming\\Po"
L"tPla"
L"yerMini64\\Capture",
COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_DENY_CORS);
}
if (controller->QueryInterface(
IID_PPV_ARGS(&webviewController2)) == S_OK)
{
COREWEBVIEW2_COLOR backgroundColor = {0, 0, 0,
0};
webviewController2->put_DefaultBackgroundColor(
backgroundColor);
}
// Resize WebView to fit the bounds of the parent
// window
RECT bounds;
GetClientRect(hWnd, &bounds);
webviewController->put_Bounds(bounds);
// Navigate to a simple HTML string
auto hr =
webview->NavigateToString(HTMLString.c_str());
if (FAILED(hr))
{
MessageBox(hWnd,
L"Failed to navigate to string.",
L"Error", MB_OK);
}
// webview->OpenDevToolsWindow();
return S_OK;
})
.Get());
return S_OK;
})
.Get());
}
int CALLBACK WinMain(_In_ HINSTANCE hInstance, _In_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine, _In_ int nCmdShow)
{
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
WM_SHOW_MAIN_WINDOW = RegisterWindowMessage(L"WM_SHOW_MAIN_WINDOW");
WM_HIDE_MAIN_WINDOW = RegisterWindowMessage(L"WM_HIDE_MAIN_WINDOW");
WM_MOVE_CANDIDATE_WINDOW =
RegisterWindowMessage(L"WM_MOVE_CANDIDATE_WINDOW");
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);
if (!RegisterClassEx(&wcex))
{
MessageBox(NULL, _T("Call to RegisterClassEx failed!"),
_T("Windows Desktop Guided Tour"), NULL);
return 1;
}
// Store instance handle in our global variable
hInst = hInstance;
HWND hWnd = CreateWindowEx(WS_EX_LAYERED | WS_EX_TOOLWINDOW | //
WS_EX_NOACTIVATE | //
WS_EX_TOPMOST, //
szWindowClass, //
L"fanycandidatewindow", //
WS_POPUP, //
100, //
100, //
(108 + 15) * 1.5, //
(246 + 15) * 1.5, //
nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
MessageBox(NULL, _T("Call to CreateWindow failed!"),
_T("Windows Desktop Guided Tour"), NULL);
return 1;
}
MoveWindow(hWnd, -1000, 100, (108 + 15) * 1.5, (246 + 15) * 1.5, TRUE);
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
InitWebview(hWnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}