[ui]: settings window
This commit is contained in:
1 parent
d879cdf710
commit
bd5ba1f771
10 files changed
+280
-127
No files matched your search
@@ -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"
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
@@ -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 |
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define IDI_IME_ICON 101 // 注意,下面一定要有换行,这是 RC 编译器的要求
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "resource.h"
|
||||
|
||||
IDI_IME_ICON ICON "MetasequoiaIME.ico"
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
Reference in new issue
Block a user