diff --git a/CMakeLists.txt b/CMakeLists.txt index 34e4964..51e901f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,8 @@ if(LINUX) elseif(APPLE) find_library(COCOA_LIB Cocoa REQUIRED) find_library(WEBKIT_LIB WebKit REQUIRED) - set(LIBS PkgConfig::NlohmannJson ${COCOA_LIB} ${WEBKIT_LIB} CURL::libcurl) + find_library(QUARTZCORE_LIB QuartzCore REQUIRED) + set(LIBS PkgConfig::NlohmannJson ${COCOA_LIB} ${WEBKIT_LIB} ${QUARTZCORE_LIB} CURL::libcurl) elseif(EMSCRIPTEN) set(LIBS PkgConfig::NlohmannJson) endif() diff --git a/include/candidate_window.hpp b/include/candidate_window.hpp index c3d0080..2d986e9 100644 --- a/include/candidate_window.hpp +++ b/include/candidate_window.hpp @@ -78,6 +78,8 @@ class CandidateWindow { virtual void set_style(const void *style) = 0; virtual void show(double x, double y) = 0; virtual void hide() = 0; + virtual void set_native_blur(bool enabled) = 0; + virtual void set_native_shadow(bool enabled) = 0; void set_init_callback(std::function callback) { init_callback = callback; diff --git a/include/webview_candidate_window.hpp b/include/webview_candidate_window.hpp index edfb770..c1babf0 100644 --- a/include/webview_candidate_window.hpp +++ b/include/webview_candidate_window.hpp @@ -38,6 +38,8 @@ class WebviewCandidateWindow : public CandidateWindow { void set_theme(theme_t theme) override; void set_writing_mode(writing_mode_t mode) override; void set_style(const void *style) override; + void set_native_blur(bool enabled) override; + void set_native_shadow(bool enabled) override; void show(double x, double y) override; void hide() override; @@ -71,8 +73,10 @@ class WebviewCandidateWindow : public CandidateWindow { void *create_window(); void set_transparent_background(); void resize(double dx, double dy, double anchor_top, double anchor_right, - double anchor_bottom, double anchor_left, double width, - double height, bool dragging); + double anchor_bottom, double anchor_left, double panel_top, + double panel_right, double panel_bottom, double panel_left, + double panel_radius, double width, double height, + bool dragging); void write_clipboard(const std::string &html); void *platform_data = nullptr; diff --git a/page/customize.ts b/page/customize.ts index d5121cf..35a3eae 100644 --- a/page/customize.ts +++ b/page/customize.ts @@ -406,15 +406,15 @@ export function setStyle(style: string) { rules[HOVERABLES]['background-size'] = 'cover' } - if (j.Background.Blur === 'True') { - setBlur(true) - const blur = `blur(${px(j.Background.BlurRadius)})` - rules['.fcitx-blur'] = { '-webkit-backdrop-filter': blur, 'backdrop-filter': blur } - } - else { - setBlur(false) - document.querySelector('fcitx-.panel-blur-outer')?.classList.remove('fcitx-blur') - document.querySelector('.fcitx-panel-blur-outer')?.classList.remove('fcitx-blur') + if (window.fcitx.distribution === 'fcitx5-js') { + if (j.Background.Blur === 'True') { + setBlur(true) + const blur = `blur(${px(j.Background.BlurRadius)})` + rules['.fcitx-blur'] = { '-webkit-backdrop-filter': blur, 'backdrop-filter': blur } + } + else { + setBlur(false) + } } if (j.Background.Shadow === 'False') { diff --git a/page/global.d.ts b/page/global.d.ts index 5ca23b5..75fa0bd 100644 --- a/page/global.d.ts +++ b/page/global.d.ts @@ -34,7 +34,7 @@ declare global { _scroll: (start: number, length: number) => void _askActions: (index: number) => void _action: (index: number, id: number) => void - _resize: (dx: number, dy: number, anchorTop: number, anchorRight: number, anchorBottom: number, anchorLeft: number, fullWidth: number, fullHeight: number, dragging: boolean) => void + _resize: (dx: number, dy: number, anchorTop: number, anchorRight: number, anchorBottom: number, anchorLeft: number, panelTop: number, panelRight: number, panelBottom: number, panelLeft: number, panelRadius: number, fullWidth: number, fullHeight: number, dragging: boolean) => void // JavaScript APIs that webview_candidate_window.mm calls setCandidates: (cands: Candidate[], highlighted: number, markText: string, pageable: boolean, hasPrev: boolean, hasNext: boolean, scrollState: SCROLL_STATE, scrollStart: boolean, scrollEnd: boolean) => void diff --git a/page/index.html b/page/index.html index 04e317b..de2d6d2 100644 --- a/page/index.html +++ b/page/index.html @@ -23,15 +23,13 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/page/ux.ts b/page/ux.ts index dabe6d9..9139ce5 100644 --- a/page/ux.ts +++ b/page/ux.ts @@ -36,7 +36,12 @@ interface ShadowBox { bottom: number } -export function resize(dx: number, dy: number, dragging: boolean, hasContextmenu: boolean) { +export function resize( + dx: number, + dy: number, + dragging: boolean, + hasContextmenu: boolean, +) { function adaptWindowSize(reserveSpaceForContextmenu: boolean) { let { anchorTop, @@ -76,7 +81,9 @@ export function resize(dx: number, dy: number, dragging: boolean, hasContextmenu bottom = Math.max(bottom, b) } - window.fcitx._resize(dx, dy, anchorTop, anchorRight, anchorBottom, anchorLeft, right, bottom, dragging) + const pRect = panel.getBoundingClientRect() + const pRadius = Math.max(...getComputedStyle(panel).borderRadius.split(' ').map(Number.parseFloat)) + window.fcitx._resize(dx, dy, anchorTop, anchorRight, anchorBottom, anchorLeft, pRect.top, pRect.right, pRect.bottom, pRect.left, pRadius, right, bottom, dragging) } adaptWindowSize(hasContextmenu) if (!dragging) { @@ -275,41 +282,15 @@ receiver.addEventListener('contextmenu', (e) => { } }) -const panelBlurOuter = document.querySelector('.fcitx-panel-blur-outer')! -const panelBlurInner = document.querySelector('.fcitx-panel-blur-inner')! +const panelBlur = document.querySelector('.fcitx-panel-blur')! -let blurEnabled = true export function setBlur(enabled: boolean) { - blurEnabled = enabled - if (window.fcitx.distribution === 'fcitx5-js') { - if (enabled) { - panelBlurInner.classList.add('fcitx-blur') - } - else { - panelBlurInner.classList.remove('fcitx-blur') - } - } -} - -// HACK: force redraw blur every 40ms so that window background change counts -let blurSwitch = false -function redrawBlur() { - if (!blurEnabled || !theme.classList.contains('fcitx-macos')) { - return - } - if (blurSwitch) { - panelBlurOuter.classList.add('fcitx-blur') - panelBlurInner.classList.remove('fcitx-blur') + if (enabled) { + panelBlur.classList.add('fcitx-blur') } else { - panelBlurInner.classList.add('fcitx-blur') - panelBlurOuter.classList.remove('fcitx-blur') + panelBlur.classList.remove('fcitx-blur') } - blurSwitch = !blurSwitch -} - -if (window.fcitx.distribution !== 'fcitx5-js') { // macOS <= 14 - setInterval(redrawBlur, 40) } export function showCursor(show: boolean) { diff --git a/src/platform/js.cpp b/src/platform/js.cpp index f624f41..d2e56d0 100644 --- a/src/platform/js.cpp +++ b/src/platform/js.cpp @@ -24,9 +24,20 @@ void WebviewCandidateWindow::write_clipboard(const std::string &html) {} void WebviewCandidateWindow::resize(double dx, double dy, double anchor_top, double anchor_right, double anchor_bottom, - double anchor_left, double width, - double height, bool dragging) { + double anchor_left, double panel_top, + double panel_right, double panel_bottom, + double panel_left, double panel_radius, + double width, double height, + bool dragging) { EM_ASM(fcitx.placePanel($0, $1, $2, $3, $4), dx, dy, anchor_top, anchor_left, dragging); } + +void WebviewCandidateWindow::set_native_blur(bool enabled) { + // Not supported. +} + +void WebviewCandidateWindow::set_native_shadow(bool enabled) { + // Not supported. +} } // namespace candidate_window diff --git a/src/platform/linux.cpp b/src/platform/linux.cpp index aee7a1c..c4dd360 100644 --- a/src/platform/linux.cpp +++ b/src/platform/linux.cpp @@ -43,8 +43,15 @@ void WebviewCandidateWindow::write_clipboard(const std::string &html) {} void WebviewCandidateWindow::resize(double dx, double dy, double anchor_top, double anchor_right, double anchor_bottom, - double anchor_left, double width, - double height, bool dragging) { + double anchor_left, double panel_top, + double panel_right, double panel_bottom, + double panel_left, double panel_radius, + double width, double height, + bool dragging) { gtk_widget_show_all(static_cast(w_->window())); } + +void WebviewCandidateWindow::set_native_blur(bool enabled) {} + +void WebviewCandidateWindow::set_native_shadow(bool enabled) {} } // namespace candidate_window diff --git a/src/platform/macos.mm b/src/platform/macos.mm index c3979c3..465076a 100644 --- a/src/platform/macos.mm +++ b/src/platform/macos.mm @@ -23,6 +23,9 @@ - (void)accentColorChanged:(NSNotification *)notification { @interface HoverableWindow : NSWindow +@property(nonatomic) NSRect blurViewRect; +@property(nonatomic, strong) NSVisualEffectView *blurView; + @end @implementation HoverableWindow @@ -135,9 +138,9 @@ NSRect getNearestScreenFrame(double x, double y) { void *WebviewCandidateWindow::create_window() { auto window = [[HoverableWindow alloc] initWithContentRect:NSMakeRect(0, 0, 400, 300) - styleMask:NSWindowStyleMaskBorderless + styleMask:0 backing:NSBackingStoreBuffered - defer:NO]; + defer:YES]; [window setLevel:NSPopUpMenuWindowLevel]; return window; } @@ -149,13 +152,53 @@ NSRect getNearestScreenFrame(double x, double y) { } void WebviewCandidateWindow::set_transparent_background() { + HoverableWindow *win = static_cast(w_->window()); + // Transparent NSWindow - [static_cast(w_->window()) - setBackgroundColor:[NSColor colorWithRed:0 green:0 blue:0 alpha:0]]; + win.opaque = NO; + [win setBackgroundColor:[NSColor clearColor]]; + // Transparent WKWebView WKWebView *webView = static_cast(w_->widget()); [webView setValue:@NO forKey:@"drawsBackground"]; [webView setUnderPageBackgroundColor:[NSColor clearColor]]; + + // From now on, replace the old contentView of the window (which + // happens to be exactly webView) with a new content view. Our view + // hierarchy is: NSWindow + // +--- NSView (contentView) + // +--- NSVisualEffectView (blurView) + // +--- WKWebView (webView) + // both blurView and webView fill the entire contentView, + // but blurView is at the bottom. + [webView removeFromSuperview]; + + auto contentView = [NSView new]; + contentView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + + auto blurView = [NSVisualEffectView new]; + blurView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + blurView.material = NSVisualEffectMaterialHUDWindow; + blurView.blendingMode = NSVisualEffectBlendingModeBehindWindow; + blurView.state = NSVisualEffectStateActive; + blurView.wantsLayer = YES; + blurView.translatesAutoresizingMaskIntoConstraints = NO; + blurView.hidden = YES; + win.blurView = blurView; + + [contentView addSubview:webView]; + win.contentView = contentView; + + // Fix the layout for webView; make sure it fills the entire container. + webView.translatesAutoresizingMaskIntoConstraints = NO; + [NSLayoutConstraint activateConstraints:@[ + [webView.leadingAnchor + constraintEqualToAnchor:contentView.leadingAnchor], + [webView.trailingAnchor + constraintEqualToAnchor:contentView.trailingAnchor], + [webView.topAnchor constraintEqualToAnchor:contentView.topAnchor], + [webView.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor] + ]]; } void WebviewCandidateWindow::update_accent_color() { @@ -185,8 +228,11 @@ NSRect getNearestScreenFrame(double x, double y) { void WebviewCandidateWindow::resize(double dx, double dy, double anchor_top, double anchor_right, double anchor_bottom, - double anchor_left, double width, - double height, bool dragging) { + double anchor_left, double panel_top, + double panel_right, double panel_bottom, + double panel_left, double panel_radius, + double width, double height, + bool dragging) { const int gap = 4; const int preedit_height = 24; NSRect frame = getNearestScreenFrame(cursor_x_, cursor_y_); @@ -227,9 +273,27 @@ NSRect getNearestScreenFrame(double x, double y) { } } hidden_ = false; - NSWindow *window = static_cast(w_->window()); + HoverableWindow *window = static_cast(w_->window()); [window setFrame:NSMakeRect(x_, y_, width, height) display:YES animate:NO]; [window orderFront:nil]; + + // Update the blur view + panel_right -= 1; // Shrink the blur view a bit + panel_left += 1; // to avoid the border being too thick. + panel_top += 1; + panel_bottom -= 1; + auto blurView = window.blurView; + NSRect blurViewRect = + NSMakeRect(panel_left, height - panel_bottom, + std::min(panel_right - panel_left, width), + std::min(panel_bottom - panel_top, height)); + if (blurView.hidden) { + window.blurViewRect = blurViewRect; + } else { + [window.blurView setFrame:blurViewRect]; + window.blurView.layer.cornerRadius = panel_radius; + } + // A User reported Bob.app called out by shortcut is above candidate // window on M1. While I can't reproduce it on Intel, he tested this and // belived it's fixed. This trick is learned from vChewing. @@ -240,4 +304,33 @@ NSRect getNearestScreenFrame(double x, double y) { 1]; [window setIsVisible:YES]; } + +void WebviewCandidateWindow::set_native_blur(bool enabled) { + HoverableWindow *window = static_cast(w_->window()); + dispatch_async(dispatch_get_main_queue(), ^{ + if (enabled) { + WKWebView *webView = static_cast(w_->widget()); + NSView *contentView = window.contentView; + [contentView addSubview:window.blurView + positioned:NSWindowBelow + relativeTo:webView]; + [window.blurView setFrame:window.blurViewRect]; + window.blurView.hidden = NO; + } else { + window.blurView.hidden = YES; + [window.blurView removeFromSuperview]; + } + }); +} + +void WebviewCandidateWindow::set_native_shadow(bool enabled) { + HoverableWindow *window = static_cast(w_->window()); + dispatch_async(dispatch_get_main_queue(), ^{ + if (enabled) { + [window setHasShadow:YES]; + } else { + [window setHasShadow:NO]; + } + }); +} } // namespace candidate_window diff --git a/src/webview_candidate_window.cpp b/src/webview_candidate_window.cpp index 1150ee6..2544a13 100644 --- a/src/webview_candidate_window.cpp +++ b/src/webview_candidate_window.cpp @@ -45,10 +45,13 @@ WebviewCandidateWindow::WebviewCandidateWindow() bind("_resize", [this](double dx, double dy, double anchor_top, double anchor_right, - double anchor_bottom, double anchor_left, double width, - double height, bool dragging) { + double anchor_bottom, double anchor_left, double panel_top, + double panel_right, double panel_bottom, double panel_left, + double panel_radius, double width, double height, + bool dragging) { resize(dx, dy, anchor_top, anchor_right, anchor_bottom, - anchor_left, width, height, dragging); + anchor_left, panel_top, panel_right, panel_bottom, + panel_left, panel_radius, width, height, dragging); }); bind("_select", [this](int i) { select_callback(i); }); diff --git a/tests/global.d.ts b/tests/global.d.ts index 318380b..35039ec 100644 --- a/tests/global.d.ts +++ b/tests/global.d.ts @@ -1,6 +1,6 @@ declare global { type CppCall = { - resize: [number, number, number, number, number, number, number, number, boolean] + resize: [number, number, number, number, number, number, number, number, number, number, number, number, number, boolean] } | { select: number } | { diff --git a/tests/test-generic.spec.ts b/tests/test-generic.spec.ts index 49ae72e..eb681e3 100644 --- a/tests/test-generic.spec.ts +++ b/tests/test-generic.spec.ts @@ -17,9 +17,6 @@ test('HTML structure', async ({ page }) => { await init(page) await page.evaluate(() => { document.querySelector('#fcitx-theme')?.classList.remove('fcitx-macos') - for (const klass of ['.fcitx-panel-blur-outer', '.fcitx-panel-blur-inner']) { - document.querySelector(klass)?.classList.remove('fcitx-blur') - } }) await setCandidates(page, [ { text: '页面结构', label: '1', comment: 'c', actions: [] }, @@ -36,32 +33,30 @@ test('HTML structure', async ({ page }) => {
-
-
-
-
-
-
-
-
-
-
-
-
1
-
页面结构
-
c
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
1
+
页面结构
+
c
-
-
-
2
-
测试
-
+
+
+
+
+
+
+
+
+
2
+
测试
diff --git a/tests/util.ts b/tests/util.ts index a10a3c8..bc26f5c 100644 --- a/tests/util.ts +++ b/tests/util.ts @@ -10,9 +10,9 @@ export async function init(page: Page) { await page.evaluate(() => { window.fcitx.setTheme(2) window.cppCalls = [] - window.fcitx._resize = (dx: number, dy: number, anchorTop: number, anchorRight: number, anchorBottom: number, anchorLeft: number, fullWidth: number, fullHeight: number, dragging: boolean) => { + window.fcitx._resize = (dx: number, dy: number, anchorTop: number, anchorRight: number, anchorBottom: number, anchorLeft: number, panelTop: number, panelRight: number, panelBottom: number, panelLeft: number, panelRadius: number, fullWidth: number, fullHeight: number, dragging: boolean) => { window.cppCalls.push({ - resize: [dx, dy, anchorTop, anchorRight, anchorBottom, anchorLeft, fullWidth, fullHeight, dragging], + resize: [dx, dy, anchorTop, anchorRight, anchorBottom, anchorLeft, panelTop, panelRight, panelBottom, panelLeft, panelRadius, fullWidth, fullHeight, dragging], }) } window.fcitx._select = (index: number) => {