Skip to content

Commit

Permalink
Use macOS native blur (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
ksqsf authored Sep 27, 2024
1 parent 780f927 commit 14689cd
Show file tree
Hide file tree
Showing 14 changed files with 193 additions and 98 deletions.
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 2 additions & 0 deletions include/candidate_window.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<void()> callback) {
init_callback = callback;
Expand Down
8 changes: 6 additions & 2 deletions include/webview_candidate_window.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down
18 changes: 9 additions & 9 deletions page/customize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
2 changes: 1 addition & 1 deletion page/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 6 additions & 8 deletions page/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,13 @@
<div class="fcitx-panel-left"></div>
<div class="fcitx-panel-center">
<div class="fcitx-panel fcitx-horizontal-tb">
<div class="fcitx-panel-blur-outer">
<div class="fcitx-panel-blur-inner">
<div class="fcitx-header">
<div class="fcitx-aux-up fcitx-hidden"></div>
<div class="fcitx-preedit fcitx-hidden"></div>
</div>
<div class="fcitx-aux-down fcitx-hidden"></div>
<div class="fcitx-hoverables fcitx-horizontal"></div>
<div class="fcitx-panel-blur">
<div class="fcitx-header">
<div class="fcitx-aux-up fcitx-hidden"></div>
<div class="fcitx-preedit fcitx-hidden"></div>
</div>
<div class="fcitx-aux-down fcitx-hidden"></div>
<div class="fcitx-hoverables fcitx-horizontal"></div>
</div>
</div>
</div>
Expand Down
45 changes: 13 additions & 32 deletions page/ux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
15 changes: 13 additions & 2 deletions src/platform/js.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
11 changes: 9 additions & 2 deletions src/platform/linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<GtkWidget *>(w_->window()));
}

void WebviewCandidateWindow::set_native_blur(bool enabled) {}

void WebviewCandidateWindow::set_native_shadow(bool enabled) {}
} // namespace candidate_window
107 changes: 100 additions & 7 deletions src/platform/macos.mm
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ - (void)accentColorChanged:(NSNotification *)notification {

@interface HoverableWindow : NSWindow

@property(nonatomic) NSRect blurViewRect;
@property(nonatomic, strong) NSVisualEffectView *blurView;

@end

@implementation HoverableWindow
Expand Down Expand Up @@ -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;
}
Expand All @@ -149,13 +152,53 @@ NSRect getNearestScreenFrame(double x, double y) {
}

void WebviewCandidateWindow::set_transparent_background() {
HoverableWindow *win = static_cast<HoverableWindow *>(w_->window());

// Transparent NSWindow
[static_cast<NSWindow *>(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<WKWebView *>(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() {
Expand Down Expand Up @@ -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_);
Expand Down Expand Up @@ -227,9 +273,27 @@ NSRect getNearestScreenFrame(double x, double y) {
}
}
hidden_ = false;
NSWindow *window = static_cast<NSWindow *>(w_->window());
HoverableWindow *window = static_cast<HoverableWindow *>(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.
Expand All @@ -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<HoverableWindow *>(w_->window());
dispatch_async(dispatch_get_main_queue(), ^{
if (enabled) {
WKWebView *webView = static_cast<WKWebView *>(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<HoverableWindow *>(w_->window());
dispatch_async(dispatch_get_main_queue(), ^{
if (enabled) {
[window setHasShadow:YES];
} else {
[window setHasShadow:NO];
}
});
}
} // namespace candidate_window
9 changes: 6 additions & 3 deletions src/webview_candidate_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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); });
Expand Down
2 changes: 1 addition & 1 deletion tests/global.d.ts
Original file line number Diff line number Diff line change
@@ -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
} | {
Expand Down
Loading

0 comments on commit 14689cd

Please sign in to comment.