[ui]: settings window

This commit is contained in:
fanlumaster committed 2025-11-03 13:12:51 +08:00
1 parent d879cdf710
commit bd5ba1f771
10 files changed
+280 -127

No files matched your search

+2
View File
@@ -49,6 +49,8 @@ set(SRC_FILES
"./src/ipc/ipc.h"
"./src/log/fanylog.cpp"
"./src/log/fanylog.h"
"./src/resource/resource.h"
"./src/resource/resource.rc"
"./src/utils/common_utils.cpp"
"./src/utils/common_utils.h"
"./src/utils/ime_utils.cpp"
+1 -1
View File
@@ -94,7 +94,7 @@
<span>🖲️</span>
悬浮工具栏
</div>
<div class="toggle" id="floatingToggle"></div>
<div class="toggle active" id="floatingToggle"></div>
</div>
<div class="menu-item">
+183 -95
View File
@@ -4,148 +4,236 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>水杉输入法设置</title>
<title>(IME)Settings</title>
<style>
:root {
--accent-color: #8E8CD8;
--bg-color: rgba(32, 32, 32, 0.65);
--sidebar-bg: rgba(25, 25, 25, 0.7);
--sidebar-hover: rgba(255, 255, 255, 0.09);
--item-active-hover: rgba(255, 255, 255, 0.06);
--text-color: #f3f3f3;
--border-color: rgba(255, 255, 255, 0.08);
--card-bg: rgba(38, 38, 38, 0.8);
--titlebar-height: 32px;
}
* {
box-sizing: border-box;
font-family: "Noto Sans SC", sans-serif;
font-family: "Segoe UI Variable", "Segoe UI", "Noto Sans SC", sans-serif;
user-select: none;
}
body {
margin: 0;
display: flex;
flex-direction: column;
height: 100vh;
background-color: transparent;
color: #fff;
width: 100vw;
color: var(--text-color);
/* background: var(--bg-color); */
background: #202020;
/* backdrop-filter: blur(20px) saturate(160%); */
-webkit-backdrop-filter: blur(20px) saturate(160%);
/* overflow: hidden; */
}
/* 主体区域 */
.main {
display: flex;
flex: 1;
overflow: hidden;
}
/* 左侧导航栏 */
.sidebar {
width: 200px;
background: linear-gradient(to bottom, #2c3e50, #34495e);
width: 240px;
display: flex;
flex-direction: column;
padding-top: 20px;
background: var(--sidebar-bg);
border-right: 1px solid var(--border-color);
padding: 12px 0;
overflow-y: auto;
}
.sidebar-header {
display: flex;
align-items: center;
padding: 0 24px 12px 24px;
gap: 10px;
margin-bottom: 8px;
}
.sidebar-header img {
width: 28px;
height: 28px;
border-radius: 6px;
}
.sidebar-header span {
font-weight: 600;
font-size: 16px;
}
.sidebar .item {
padding: 15px 20px;
padding: 10px 24px;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
}
.sidebar .item.active {
background-color: #0078D4;
/* Fluent 蓝色 */
border-radius: 4px;
}
.sidebar .item:hover {
background-color: rgba(0, 120, 212, 0.1);
/* Fluent 浅蓝悬停 */
}
/* 右侧内容区 */
.content {
flex: 1;
padding: 20px;
background-color: #121212;
}
.section {
margin-bottom: 20px;
}
.section h2 {
font-size: 16px;
margin-bottom: 10px;
position: relative;
transition: background 0.2s, color 0.2s;
border-radius: 8px;
margin: 2px 8px;
color: #ccc;
}
/* 候选字 */
.candidate-preview {
display: flex;
gap: 5px;
background-color: #222;
padding: 10px;
border-radius: 4px;
.sidebar .item:hover {
background: var(--sidebar-hover);
}
.candidate-preview span {
padding: 5px 10px;
.sidebar .item.active {
background: var(--item-active-hover);
}
.sidebar .item.active::before {
content: "";
position: absolute;
left: 8px;
width: 3px;
height: 40%;
border-radius: 3px;
cursor: pointer;
background: var(--accent-color);
}
.candidate-preview span.active {
background-color: #0078D4;
/* Fluent 蓝色 */
color: #fff;
.content {
flex: 1;
padding: 32px 48px;
overflow-y: auto;
}
/* 滑块 */
.slider-container {
display: flex;
align-items: center;
gap: 10px;
.section {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 20px 24px;
margin-bottom: 24px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(10px);
}
.section h2 {
font-size: 15px;
font-weight: 600;
margin-bottom: 12px;
color: #ccc;
}
select {
padding: 6px 10px;
border-radius: 8px;
border: 1px solid var(--border-color);
background-color: rgba(45, 45, 45, 0.8);
color: #f3f3f3;
font-size: 14px;
transition: border 0.2s, background 0.2s;
}
select:focus {
outline: none;
border: 1px solid var(--accent-color);
background-color: rgba(50, 50, 50, 0.9);
}
input[type="range"] {
flex: 1;
width: 100%;
accent-color: var(--accent-color);
}
/* 下拉选择 */
select {
padding: 5px 10px;
border-radius: 4px;
border: none;
background-color: #333;
.candidate-preview {
display: flex;
gap: 6px;
background: rgba(50, 50, 50, 0.8);
padding: 10px;
border-radius: 8px;
border: 1px solid var(--border-color);
}
.candidate-preview span {
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s, color 0.2s;
color: #ddd;
}
.candidate-preview span:hover {
background: rgba(255, 255, 255, 0.1);
color: #fff;
}
.candidate-preview span.active {
background: var(--accent-color);
color: white;
}
</style>
</head>
<body>
<div class="sidebar">
<div class="item active">外观</div>
<div class="item">输入</div>
<div class="item">快捷键</div>
<div class="item">词库管理</div>
<div class="item">皮肤</div>
<div class="item">关于&反馈</div>
</div>
<div class="content">
<div class="section">
<h2>候选字预览</h2>
<div class="candidate-preview">
<span class="active">1水杉</span>
<span>2水杉输入法</span>
<span>3连心</span>
<span>4为新</span>
<span>5唯心</span>
<span>6威</span>
<div class="main">
<div class="sidebar">
<div class="sidebar-header">
<img src="https://imesettings/assets/MetasequoiaIME.ico" alt="">
<span>水杉输入法</span>
</div>
<div class="item active">通用</div>
<div class="item">外观</div>
<div class="item">输入</div>
<div class="item">快捷键</div>
<div class="item">词库管理</div>
<div class="item">皮肤</div>
<div class="item">帮助</div>
<div class="item">关于</div>
<div class="item">反馈</div>
</div>
<div class="section">
<h2>候选字大小</h2>
<div class="slider-container">
<span>最小</span>
<input type="range" min="10" max="30" value="16">
<span>最大</span>
<div class="content">
<div class="section">
<h2>默认主题模式</h2>
<select>
<option value="dark" selected>深色</option>
<option value="light">浅色</option>
</select>
</div>
<div class="section">
<h2>候选词预览</h2>
<div class="candidate-preview">
<span>水</span>
<span>杉</span>
<span class="active">输入法</span>
</div>
</div>
</div>
<div class="section">
<h2>主题模式</h2>
<select>
<option value="dark" selected>深色</option>
<option value="light">浅色</option>
</select>
</div>
</div>
<script>
function onMinimize() {
if (window.chrome?.webview) {
window.chrome.webview.postMessage({ action: "minimize" });
}
}
function onMaximize() {
if (window.chrome?.webview) {
window.chrome.webview.postMessage({ action: "maximize" });
}
}
function onClose() {
if (window.chrome?.webview) {
window.chrome.webview.postMessage({ action: "close" });
}
}
</script>
</body>
</html>
+3 -1
View File
@@ -49,6 +49,8 @@ set(SRC_FILES
"./src/ipc/ipc.h"
"./src/log/fanylog.cpp"
"./src/log/fanylog.h"
"./src/resource/resource.h"
"./src/resource/resource.rc"
"./src/utils/common_utils.cpp"
"./src/utils/common_utils.h"
"./src/utils/ime_utils.cpp"
@@ -57,7 +59,7 @@ set(SRC_FILES
"./src/utils/webview_utils.h"
"./src/utils/window_utils.cpp"
"./src/utils/window_utils.h"
"./src/window/candidate_window.cpp"
"./src/window/ime_windows.cpp"
"./src/window/ime_windows.h"
"./src/window/window_hook.cpp"
"./src/window/window_hook.h"
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

+3
View File
@@ -0,0 +1,3 @@
#pragma once
#define IDI_IME_ICON 101 // 注意,下面一定要有换行,这是 RC 编译器的要求
+3
View File
@@ -0,0 +1,3 @@
#include "resource.h"
IDI_IME_ICON ICON "MetasequoiaIME.ico"
+30 -5
View File
@@ -4,6 +4,7 @@
#include <filesystem>
#include <string>
#include <windows.h>
#include <dwmapi.h>
#include "global/globals.h"
#include "fmt/xchar.h"
@@ -556,10 +557,15 @@ HRESULT OnControllerCreatedSettingsWnd( //
// Configure virtual host path
if (SUCCEEDED(webviewSettingsWnd->QueryInterface(IID_PPV_ARGS(&webview3SettingsWnd))))
{
const std::wstring assetPath = fmt::format( //
L"{}\\{}\\html\\webview2\\settings", //
string_to_wstring(CommonUtils::get_local_appdata_path()), //
GlobalIme::AppName //
);
// Assets mapping
webview3SettingsWnd->SetVirtualHostNameToFolderMapping( //
L"appassets", //
::LocalAssetsPath.c_str(), //
L"imesettings", //
assetPath.c_str(), //
COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_DENY_CORS //
); //
}
@@ -574,17 +580,36 @@ HRESULT OnControllerCreatedSettingsWnd( //
// Adjust to window size
RECT bounds;
GetClientRect(hwnd, &bounds);
bounds.right += boundRightExtra;
bounds.bottom += boundBottomExtra;
webviewControllerSettingsWnd->put_Bounds(bounds);
// Navigate to HTML
HRESULT hr = webviewSettingsWnd->NavigateToString(::HTMLStringSettingsWnd.c_str());
// HRESULT hr = webviewSettingsWnd->NavigateToString(::HTMLStringSettingsWnd.c_str());
std::wstring url = L"https://imesettings/default.html";
HRESULT hr = webviewSettingsWnd->Navigate(url.c_str());
if (FAILED(hr))
{
OutputDebugString(fmt::format(L"Failed to navigate to string.").c_str());
}
EventRegistrationToken navCompletedToken;
webviewSettingsWnd->add_NavigationCompleted(
Callback<ICoreWebView2NavigationCompletedEventHandler>( //
[hwnd](ICoreWebView2 *sender, ICoreWebView2NavigationCompletedEventArgs *args) -> HRESULT {
BOOL success;
args->get_IsSuccess(&success);
if (success)
{
OutputDebugString(L"✅ WebView2 页面加载完成\n");
// 在这里执行你想等页面准备好的逻辑,比如执行 JS 初始化
BOOL cloak = FALSE;
DwmSetWindowAttribute(hwnd, DWMWA_CLOAK, &cloak, sizeof(cloak));
}
return S_OK;
})
.Get(),
&navCompletedToken);
/* Debug console */
// webviewSettingsWindow->OpenDevToolsWindow();
+54 -24
View File
@@ -18,6 +18,7 @@
#include "utils/ime_utils.h"
#include "window_hook.h"
#include <windowsx.h>
#include "resource/resource.h"
#pragma comment(lib, "dwmapi.lib")
@@ -46,7 +47,8 @@ LRESULT RegisterIMEWindowsClass(WNDCLASSEX &wcex, HINSTANCE hInstance)
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_IME_ICON));
wcex.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_IME_ICON));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
/* We do not need background color, otherwise it will flash when rendering */
wcex.hbrBackground = NULL;
@@ -156,34 +158,42 @@ int CreateCandidateWindow(HINSTANCE hInstance)
//
// settings 窗口
//
dwExStyle = WS_EX_LAYERED | //
WS_EX_APPWINDOW; //
DWORD styleSettingsWnd = WS_POPUP | //
WS_SYSMENU | //
WS_MINIMIZEBOX | //
WS_MAXIMIZEBOX; //
HWND hwnd_settings = CreateWindowEx( //
dwExStyle, //
szWindowClass, //
lpWindowNameSettings, //
styleSettingsWnd, //
400, //
400, //
(::SETTINGS_WINDOW_WIDTH)*scale, //
(::SETTINGS_WINDOW_HEIGHT)*scale, //
nullptr, //
nullptr, //
hInstance, //
nullptr //
); //
dwExStyle = 0; //
HWND hwnd_settings = CreateWindowEx( //
dwExStyle, //
szWindowClass, //
lpWindowNameSettings, //
WS_OVERLAPPEDWINDOW, //
400, //
400, //
(::SETTINGS_WINDOW_WIDTH)*scale, //
(::SETTINGS_WINDOW_HEIGHT)*scale, //
nullptr, //
nullptr, //
hInstance, //
nullptr //
); //
if (!hwnd_settings)
{
OutputDebugString(fmt::format(L"Call to CreateWindow for settings failed!\n").c_str());
return 1;
}
MARGINS m_settings{-1}; // 全部填满
DwmExtendFrameIntoClientArea(hwnd_settings, &m_settings);
// 使用 DWM 允许透明
DWM_BLURBEHIND bb = {0};
bb.dwFlags = DWM_BB_ENABLE;
bb.fEnable = TRUE;
bb.hRgnBlur = nullptr;
DwmEnableBlurBehindWindow(hwnd_settings, &bb);
BOOL useDarkMode = TRUE;
DwmSetWindowAttribute( //
hwnd_settings, //
DWMWA_USE_IMMERSIVE_DARK_MODE, //
&useDarkMode, //
sizeof(useDarkMode) //
);
::global_hwnd_settings = hwnd_settings;
BOOL cloak = TRUE;
DwmSetWindowAttribute(hwnd_settings, DWMWA_CLOAK, &cloak, sizeof(cloak));
//
// floating toolbar 窗口
@@ -218,7 +228,8 @@ int CreateCandidateWindow(HINSTANCE hInstance)
//
ShowWindow(hwnd, SW_SHOW);
ShowWindow(hwnd_menu, SW_SHOW);
ShowWindow(hwnd_settings, SW_SHOW);
ShowWindow(hwnd_settings, SW_SHOWMINIMIZED);
ShowWindow(hwnd_settings, SW_RESTORE);
ShowWindow(hwnd_ftb, SW_SHOW);
UpdateWindow(hwnd);
UpdateWindow(hwnd_menu);
@@ -479,6 +490,25 @@ LRESULT CALLBACK WndProcSettingsWindow(HWND hwnd, UINT message, WPARAM wParam, L
{
switch (message)
{
case WM_ERASEBKGND: { // Make the background dark
// HDC hdc = (HDC)wParam;
// RECT rc;
// GetClientRect(hwnd, &rc);
// HBRUSH darkBrush = CreateSolidBrush(RGB(32, 32, 32));
// FillRect(hdc, &rc, darkBrush);
// DeleteObject(darkBrush);
return 1;
}
case WM_SIZE: {
if (::webviewControllerSettingsWnd)
{
RECT rect;
GetClientRect(hwnd, &rect);
webviewControllerSettingsWnd->put_Bounds(rect);
}
break;
}
default: {
return DefWindowProc(hwnd, message, wParam, lParam);
}
+1 -1
View File
@@ -9,7 +9,7 @@ inline WCHAR lpWindowNameCand[] = L"metaseuqoiaimecandwnd";
/* 菜单窗口 */
inline WCHAR lpWindowNameMenu[] = L"metaseuqoiaimemenuwnd";
/* settings 窗口 */
inline WCHAR lpWindowNameSettings[] = L"metaseuqoiaimesettingswnd";
inline WCHAR lpWindowNameSettings[] = L"Settings";
/* floating toolbar 窗口 */
inline WCHAR lpWindowNameFtb[] = L"metaseuqoiaimeftbwnd";