From 6ebf11e001c1e9e6be5087b045b77d16f02654fd Mon Sep 17 00:00:00 2001 From: James-ctrl-Doyle <67906228+James-ctrl-Doyle@users.noreply.github.com> Date: Sat, 12 Sep 2026 15:36:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=A4=9A=E8=80=81?= =?UTF-8?q?=E6=9D=BF=E9=94=AE=E4=B8=8E=E9=80=8F=E6=98=8E=E8=83=8C=E6=99=AF?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E6=96=B0=E5=A2=9E=E3=80=8C=E5=BC=80=E5=90=AF?= =?UTF-8?q?/=E5=85=B3=E9=97=AD=E9=80=8F=E6=98=8E=E3=80=8D=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 一、多老板键 - 支持配置 4 个老板键(默认 Alt+H,可另设 Ctrl+Alt+H / Ctrl+Shift+H / Alt+Q), 均可一键隐藏/恢复全部窗口 - 新增 KI_HIDE2~KI_HIDE4 枚举、对应控件 ID 与字符串资源 二、透明背景(【显示设置】→ 背景颜色 →「透明背景(需隐藏边框)」) - 开启后背景位图 alpha 置 0:背景透明、文字仍不透明;透明区域由 UpdateLayeredWindow 自动点击穿透,无需额外命中测试 - 新增 header.transparent_bg 字段并接入 JSON 持久化(老配置安全取默认 0) - 与边框解耦:F12 隐藏边框时生效、恢复边框时暂停,可随时来回切换 三、新增快捷键「开启/关闭透明」(默认 F9、默认禁用) 四、配置槽与数组下标解耦(兼容性关键点) - g_Keysets 的配置槽改由条目内 keyid(HIWORD(key))决定,不再依赖数组下标, 新增按键不会移动任何已有键的配置槽 - 因此老版本配置文件升级后,原有快捷键的含义与开关状态保持不变 五、默认启用的快捷键 - 默认启用:老板键1 (Alt+H)、隐藏/显示边框 (F12)、上一页/下一页 (←/→)、 上n行/下n行 (↑/↓) - 其余按键(包括新增的老板键2~4 与透明开关)默认关闭,可在【按键设置】中开启 说明:Reader_zh-cn.rc / resource.h 为 UTF-16 资源文件,上述功能均有改动且无法 按功能拆分,故合并在同一提交中;新增控件 ID 取 1180~1188,避开主线已占用的 1170~1176。 --- Reader/Cache.cpp | 1 + Reader/DisplaySet.cpp | 7 ++++ Reader/Jsondata.cpp | 5 +++ Reader/Keyset.cpp | 82 ++++++++++++++++++++++++++++++++++------- Reader/Keyset.h | 4 ++ Reader/Reader.cpp | 22 +++++++++++ Reader/Reader.h | 1 + Reader/Reader_zh-cn.rc | Bin 63822 -> 65688 bytes Reader/resource.h | Bin 28312 -> 29230 bytes Reader/types.h | 7 ++++ 10 files changed, 115 insertions(+), 14 deletions(-) diff --git a/Reader/Cache.cpp b/Reader/Cache.cpp index c2f0fda..9f06781 100644 --- a/Reader/Cache.cpp +++ b/Reader/Cache.cpp @@ -366,6 +366,7 @@ void Cache::default_header(header_t* header) // default bk color header->bg_color = 0x00ffffff; // White header->alpha = 0xff; + header->transparent_bg = 0; header->char_gap = 0; header->line_gap = 5; diff --git a/Reader/DisplaySet.cpp b/Reader/DisplaySet.cpp index 92ceafa..766fa02 100644 --- a/Reader/DisplaySet.cpp +++ b/Reader/DisplaySet.cpp @@ -23,6 +23,7 @@ typedef struct display_set_data_t int word_wrap; int line_indent; int blank_lines; + int transparent_bg; int chapter_page; int is_save; } display_set_data_t; @@ -80,6 +81,7 @@ static void _update_preview(HDC hDC, RECT *rc); (d)->word_wrap = (s)->word_wrap; \ (d)->line_indent = (s)->line_indent; \ (d)->blank_lines = (s)->blank_lines; \ + (d)->transparent_bg = (s)->transparent_bg; \ (d)->chapter_page = (s)->chapter_page; void OpenDisplaySetDlg(void) @@ -224,6 +226,10 @@ static INT_PTR CALLBACK DisplaySetDlgProc(HWND hDlg, UINT message, WPARAM wParam res = (int)SendMessage(GetDlgItem(hDlg, IDC_CHECK_BLANKLINES), BM_GETCHECK, 0, NULL); _display.blank_lines = BST_CHECKED == res ? 1 : 0; break; + case IDC_CHECK_TRANS_BG: + res = (int)SendMessage(GetDlgItem(hDlg, IDC_CHECK_TRANS_BG), BM_GETCHECK, 0, NULL); + _display.transparent_bg = BST_CHECKED == res ? 1 : 0; + break; case IDC_CHECK_CHAPTER_PAGE: res = (int)SendMessage(GetDlgItem(hDlg, IDC_CHECK_CHAPTER_PAGE), BM_GETCHECK, 0, NULL); _display.chapter_page = BST_CHECKED == res ? 1 : 0; @@ -315,6 +321,7 @@ static void _init_layout_set(HWND hDlg) SendMessage(GetDlgItem(hDlg, IDC_CHECK_WORD_WRAP), BM_SETCHECK, _display.word_wrap ? BST_CHECKED : BST_UNCHECKED, NULL); SendMessage(GetDlgItem(hDlg, IDC_CHECK_INDENT), BM_SETCHECK, _display.line_indent ? BST_CHECKED : BST_UNCHECKED, NULL); SendMessage(GetDlgItem(hDlg, IDC_CHECK_BLANKLINES), BM_SETCHECK, _display.blank_lines ? BST_CHECKED : BST_UNCHECKED, NULL); + SendMessage(GetDlgItem(hDlg, IDC_CHECK_TRANS_BG), BM_SETCHECK, _display.transparent_bg ? BST_CHECKED : BST_UNCHECKED, NULL); SendMessage(GetDlgItem(hDlg, IDC_CHECK_CHAPTER_PAGE), BM_SETCHECK, _display.chapter_page ? BST_CHECKED : BST_UNCHECKED, NULL); } diff --git a/Reader/Jsondata.cpp b/Reader/Jsondata.cpp index 2407fec..1cd8327 100644 --- a/Reader/Jsondata.cpp +++ b/Reader/Jsondata.cpp @@ -600,6 +600,7 @@ class json_header_t cJSON* line_indent; cJSON* blank_lines; cJSON* chapter_page; + cJSON* transparent_bg; cJSON* ingore_version; cJSON* checkver_time; cJSON* global_key; @@ -656,6 +657,7 @@ class json_header_t line_indent = cJSON_AddNumberToObject(parent, "line_indent", data->line_indent); blank_lines = cJSON_AddNumberToObject(parent, "blank_lines", data->blank_lines); chapter_page = cJSON_AddNumberToObject(parent, "chapter_page", data->chapter_page); + transparent_bg = cJSON_AddNumberToObject(parent, "transparent_bg", data->transparent_bg); ingore_version = cJSON_AddStringToObject(parent, "ingore_version", Utf16ToUtf8(data->ingore_version)); checkver_time = cJSON_AddULongToObject(parent, "checkver_time", data->checkver_time); @@ -779,6 +781,7 @@ class json_header_t line_indent = cJSON_GetObjectItem(parent, "line_indent"); blank_lines = cJSON_GetObjectItem(parent, "blank_lines"); chapter_page = cJSON_GetObjectItem(parent, "chapter_page"); + transparent_bg = cJSON_GetObjectItem(parent, "transparent_bg"); ingore_version = cJSON_GetObjectItem(parent, "ingore_version"); checkver_time = cJSON_GetObjectItem(parent, "checkver_time"); @@ -966,6 +969,8 @@ class json_header_t data->blank_lines = blank_lines->valueint; if (chapter_page) data->chapter_page = chapter_page->valueint; + if (transparent_bg) + data->transparent_bg = transparent_bg->valueint; if (ingore_version) wcscpy(data->ingore_version, Utf8ToUtf16(ingore_version->valuestring)); if (checkver_time) diff --git a/Reader/Keyset.cpp b/Reader/Keyset.cpp index 3c5a861..70e6a8c 100644 --- a/Reader/Keyset.cpp +++ b/Reader/Keyset.cpp @@ -19,6 +19,7 @@ extern LRESULT OnChapterUp(HWND, UINT, WPARAM, LPARAM); extern LRESULT OnChapterDown(HWND, UINT, WPARAM, LPARAM); extern LRESULT OnFontZoomIn(HWND, UINT, WPARAM, LPARAM); extern LRESULT OnFontZoomOut(HWND, UINT, WPARAM, LPARAM); +extern LRESULT OnTransBG(HWND, UINT, WPARAM, LPARAM); extern int MessageBoxFmt_(HWND hWnd, UINT captionId, UINT uType, UINT formatId, ...); extern HINSTANCE hInst; @@ -34,6 +35,9 @@ static BOOL _unregister_hotkey(HWND hWnd, DWORD kid); keydata_t g_Keysets[KI_MAXCOUNT] = { { MAKELONG(KT_HOTKEY, KI_HIDE), IDHK_SHWIN, 0, 0, MAKEWORD('H', HOTKEYF_ALT), IDC_HK_HIDE, IDC_CHECK_HIDE, OnHideWin, IDS_HIDE_SHOW_WINDOW }, + { MAKELONG(KT_HOTKEY, KI_HIDE2), ID_HOTKEY_SHOW_HIDE_WINDOW2, 0, 0, MAKEWORD('H', HOTKEYF_CONTROL|HOTKEYF_ALT), IDC_HK_HIDE2, IDC_CHECK_HIDE2, OnHideWin, IDS_HIDE_SHOW_WINDOW }, + { MAKELONG(KT_HOTKEY, KI_HIDE3), ID_HOTKEY_SHOW_HIDE_WINDOW3, 0, 0, MAKEWORD('H', HOTKEYF_CONTROL|HOTKEYF_SHIFT), IDC_HK_HIDE3, IDC_CHECK_HIDE3, OnHideWin, IDS_HIDE_SHOW_WINDOW }, + { MAKELONG(KT_HOTKEY, KI_HIDE4), ID_HOTKEY_SHOW_HIDE_WINDOW4, 0, 0, MAKEWORD('Q', HOTKEYF_ALT), IDC_HK_HIDE4, IDC_CHECK_HIDE4, OnHideWin, IDS_HIDE_SHOW_WINDOW }, { MAKELONG(KT_SHORTCUTKEY, KI_BORDER), 0, 0, 0, MAKEWORD(VK_F12, 0), IDC_HK_BORDER, IDC_CHECK_BORDER, OnHideBorder, IDS_HIDE_SHOW_BORDER }, { MAKELONG(KT_SHORTCUTKEY, KI_FULLSCREEN), 0, 0, 0, MAKEWORD(VK_F11, 0), IDC_HK_FULLSCREEN, IDC_CHECK_FULLSCREEN, OnFullScreen, IDS_FULLSRCEEN }, { MAKELONG(KT_SHORTCUTKEY, KI_TOP), 0, 0, 0, MAKEWORD('T', HOTKEYF_CONTROL), IDC_HK_TOP, IDC_CHECK_TOP, OnTopmost, IDS_TOPMOST }, @@ -50,7 +54,8 @@ keydata_t g_Keysets[KI_MAXCOUNT] = { MAKELONG(KT_SHORTCUTKEY, KI_CHAPTERUP), 0, 0, 0, MAKEWORD(VK_LEFT, HOTKEYF_EXT|HOTKEYF_CONTROL), IDC_HK_CHAPTERUP, IDC_CHECK_CHAPTERUP, OnChapterUp, IDS_CHAPTER_UP }, { MAKELONG(KT_SHORTCUTKEY, KI_CHAPTERDOWN), 0, 0, 0, MAKEWORD(VK_RIGHT, HOTKEYF_EXT|HOTKEYF_CONTROL), IDC_HK_CHAPTERDOWN, IDC_CHECK_CHAPTERDOWN, OnChapterDown, IDS_CHAPTER_DOWN }, { MAKELONG(KT_SHORTCUTKEY, KI_FONTZOOMIN), 0, 0, 0, MAKEWORD(VK_OEM_PLUS, HOTKEYF_CONTROL), IDC_HK_FONTZOOMIN, IDC_CHECK_FONTZOOMIN, OnFontZoomIn, IDS_FONT_ZOOMIN }, - { MAKELONG(KT_SHORTCUTKEY, KI_FONTZOOMOUT), 0, 0, 0, MAKEWORD(VK_OEM_MINUS, HOTKEYF_CONTROL), IDC_HK_FONTZOOMOUT, IDC_CHECK_FONTZOOMOUT, OnFontZoomOut, IDS_FONT_ZOOMOUT } + { MAKELONG(KT_SHORTCUTKEY, KI_FONTZOOMOUT), 0, 0, 0, MAKEWORD(VK_OEM_MINUS, HOTKEYF_CONTROL), IDC_HK_FONTZOOMOUT, IDC_CHECK_FONTZOOMOUT, OnFontZoomOut, IDS_FONT_ZOOMOUT }, + { MAKELONG(KT_SHORTCUTKEY, KI_TRANS), 0, 0, 0, MAKEWORD(VK_F9, 0), IDC_HK_TRANS, IDC_CHECK_TRANS, OnTransBG, IDS_TRANS_TOGGLE } }; @@ -65,31 +70,69 @@ void KS_Init(HWND hWnd, keyset_t *keyset) SetGlobalKey(hWnd); } +// The config slot of an entry is the keyid stored in key (HIWORD), NOT its array +// index in g_Keysets. This decouples the table layout from the persisted config: +// a new key can be inserted anywhere in g_Keysets without moving the slot of any +// existing key, so configs written by older versions keep their meaning +// (keyset[] is serialized positionally into JSON). +static int _keyset_slot(const keydata_t *kd) +{ + int kid = (int)HIWORD(kd->key); + + return (kid >= 0 && kid < MAX_KEYSET_COUNT) ? kid : -1; +} + +// Which keys are enabled out of the box. Everything else starts disabled and +// is opt-in from the key settings dialog. +static BOOL _default_enable(int kid) +{ + switch (kid) + { + case KI_HIDE: // boss key 1 (Alt+H) + case KI_BORDER: // hide/show border (F12) + case KI_PAGEUP: // previous page (Left) + case KI_PAGEDOWN: // next page (Right) + case KI_LINEUP: // line up (Up) + case KI_LINEDOWN: // line down (Down) + return TRUE; + default: + return FALSE; + } +} + void KS_UpdateKeyset(keyset_t *keyset) { - int i; + int i, kid; if (!keyset) return; - for (i=KI_HIDE; iexstyle & WS_EX_TOPMOST) == WS_EX_TOPMOST; _WndInfo.status = (_header->style & WS_MINIMIZEBOX) == 0 ? ds_fullscreen : (((_header->style & WS_CAPTION) == 0) ? ds_borderless : ds_normal); + // Borderless mode removes the menu bar, so the "hide/show border" key is the only + // way back to it. Never run without that escape hatch (e.g. after hand-editing the + // config), otherwise the menu would be unreachable for good. + if (_WndInfo.status == ds_borderless && _header->keyset[KI_BORDER].is_disable) + _header->keyset[KI_BORDER].is_disable = 0; + _WndInfo.hMenu = GetMenu(hWnd); // create status bar _WndInfo.hStatusBar = CreateStatusWindow(WS_CHILD | WS_VISIBLE, _T("Please open a text."), hWnd, IDC_STATUSBAR); @@ -2191,6 +2197,13 @@ VOID OnDraw(HWND hWnd) { alpha = _header->alpha < MIN_ALPHA_VALUE ? MIN_ALPHA_VALUE : _header->alpha; } + else if (_header->transparent_bg) + { + // Transparent background: alpha 0 makes the desktop show through and + // UpdateLayeredWindow turns those pixels click-through. The text is + // composited separately with _textAlpha, so it stays opaque. + alpha = 0; + } // load bg image image = LoadBGImage(w, h, alpha); @@ -2505,6 +2518,15 @@ LRESULT OnTopmost(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) return 0; } +// Toggle the transparent-background flag. The visual effect only shows in +// borderless mode (F12), matching the display-settings checkbox behaviour. +LRESULT OnTransBG(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + _header->transparent_bg = !_header->transparent_bg; + Invalidate(hWnd, TRUE, FALSE); + return 0; +} + LRESULT OnOpenFile(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { if (IsWindow(_hFindDlg)) diff --git a/Reader/Reader.h b/Reader/Reader.h index 7bbcb74..f1f2be7 100644 --- a/Reader/Reader.h +++ b/Reader/Reader.h @@ -78,6 +78,7 @@ LRESULT OnSize(HWND, UINT, WPARAM, LPARAM); // WM_KEYWORD begin LRESULT OnHideWin(HWND, UINT, WPARAM, LPARAM); LRESULT OnHideBorder(HWND, UINT, WPARAM, LPARAM); +LRESULT OnTransBG(HWND, UINT, WPARAM, LPARAM); LRESULT OnFullScreen(HWND, UINT, WPARAM, LPARAM); LRESULT OnTopmost(HWND, UINT, WPARAM, LPARAM); LRESULT OnOpenFile(HWND, UINT, WPARAM, LPARAM); diff --git a/Reader/Reader_zh-cn.rc b/Reader/Reader_zh-cn.rc index 2b3ec6da9e726efa76366bd77378fc02831cb9d7..327591ad034103d5266da3665f15e20c627ca005 100644 GIT binary patch delta 1903 zcmZuyU2KbC6n>|*_UoA1n2m($g%FMH`|0@iKYkKRdf`8~ArTo8T+Sqm8zkn!g$U03eb&DH>Zvorw_R0vl~>PRK2Le1HF zpGhPNhR#te&k$gj$Cn@yRXRP9m`Wt7L{2r$B$1dxHeOX!_TXG%}S831r17a?=h5 zL3^2s*uS|C0=1cp#>PrNgK(y4OjVvRDNH4?sv}IioTf0jZVB{MA2PXAXSC{VHAfkY z_RiQYPSu1t`bbj!ny6*{a;GV^*M$(JJ+Y+AYwDBr&|AG{E2k{*w*C+zMV;Qj78WU_ z>6L5^nHin1J7(2&egz^-9`Cou>notj^rT z>M3b@6HWav>0|rb-@GT`J*`Qv^6-NhN?Jl3bt9=2_SzuOVg_4lQvyvR^!43s?MI|G z;98bpZ-_zgX?h!GEYRrdLzFhn$=X)7C0{$e)oV?*oi=HM%VMnXyUhY!9yU)&ubNFd z3%!NpaP!ymiz3-wbXRd+(n36>k+jk=$8@w&PEa%VxPq9H6?ZK?X^TjPU>o0)V zh#jmQ4o*Q{2d)q|d~7d-BOPmVwdE_|8NCsRoABTXtcbtph`QiOjxp|!zBd}MQR-s; zaq(7$0=+x8!_NVW`fWwrc4@|d`-N^wnMlyQWf?^5Mc^IGGl)obVDL`XoCDZXIM>5N z;BvbG&)~u3y+|#?qnHBL{Q{gG%AZeD=hc(xp*91ZOM2TAt5?|`#XhUa46Z7MPs33) zT7+X)9%G~n%CA1hC=ox4VR}42?!P8wppc|iYn?U1vl07zy6hzQJhD#>z1_JCf};V0 zf-C>cXfG6v8xi4v!2$)sV-?s5{DlqVVYonqUt==j-T&@`<1!}WhOzNe=q#S%fIDFI zIT3=_qj|KoFT)4B%zwzm$c0xY@13pg=Kj!TFG@6>$^O#eaei#p6Ofi7kx_T03=9j5LTZCSLRRX* z5Q8FhWoz1GABrHF(YIire?g!6AZFIrLVDi)xO(^H!hPQJocEmbyw8*`GxkRauUF(s zOk@UD%1q46Ot`;)86PX>azy$wP)%YI292$`B2zS#N@UrOXVWR6WE`B_i?FK~{r)i5NWNK7o z*6VahHM1&CN#pHq8pc3F3g>{q#T_c~ysT-Qt1d4#o!bseTXYDXZCAN%Y240cKjp`# zo64`WCFPL6EbRniEq=X8lgErZ<&VE5ZQ9P(5lU|-_o23JZhEbniLbW#5o=Dk>^--T zR4h@ymUZig4n6H&xZEjhE2=wEGnbfU2k%nHryPmdG>W60eJZzg2lrBk4MXQg^hS;1 zXy+bucXcK^xOc5;Z}&hxPl<1v9N$ElDYQEU*b0`x>@j2;NfB)#eg#ELd5#n$65+eV z>s+sbrP1QZhG)Wxr&sgf{j7^jPen5Y3p2;}x#W2$Lx%6V9U)^8`ANKLN``91d~YND zFTMx@NkjSUB|&5sOwPU+NEDjp#%W+&9Iw8duEnuH5fMvR30yFfgFA!!