Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use macOS native blur #72

Merged
merged 12 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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