mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-22 18:47:57 +01:00
383 lines
16 KiB
Rust
383 lines
16 KiB
Rust
// Borrowed from our friends at Hoppscotch
|
|
// https://github.com/hoppscotch/hoppscotch/blob/286fcd2bb08a84f027b10308d1e18da368f95ebf/packages/hoppscotch-selfhost-desktop/src-tauri/src/mac/window.rs
|
|
|
|
use hex_color::HexColor;
|
|
use tauri::{Manager, WebviewWindow};
|
|
|
|
struct UnsafeWindowHandle(*mut std::ffi::c_void);
|
|
|
|
unsafe impl Send for UnsafeWindowHandle {}
|
|
|
|
unsafe impl Sync for UnsafeWindowHandle {}
|
|
|
|
const WINDOW_CONTROL_PAD_X: f64 = 13.0;
|
|
const WINDOW_CONTROL_PAD_Y: f64 = 18.0;
|
|
|
|
#[cfg(target_os = "macos")]
|
|
fn update_window_title(window: &WebviewWindow, title: String) {
|
|
use cocoa::{
|
|
appkit::NSWindow,
|
|
base::nil,
|
|
foundation::NSString,
|
|
};
|
|
|
|
unsafe {
|
|
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
|
|
|
|
let _ = window.run_on_main_thread(move || {
|
|
let win_title = NSString::alloc(nil).init_str(&title);
|
|
let handle = window_handle;
|
|
NSWindow::setTitle_(handle.0 as cocoa::base::id, win_title);
|
|
set_window_controls_pos(handle.0 as cocoa::base::id);
|
|
});
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
fn update_window_theme(window: &WebviewWindow, color: HexColor) {
|
|
use cocoa::{
|
|
appkit::{NSAppearance, NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight, NSWindow},
|
|
base::nil,
|
|
foundation::NSString,
|
|
};
|
|
|
|
let brightness = (color.r as u64 + color.g as u64 + color.b as u64) / 3;
|
|
|
|
unsafe {
|
|
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
|
|
|
|
let _ = window.run_on_main_thread(move || {
|
|
let handle = window_handle;
|
|
|
|
let selected_appearance = if brightness >= 128 {
|
|
NSAppearance(NSAppearanceNameVibrantLight)
|
|
} else {
|
|
NSAppearance(NSAppearanceNameVibrantDark)
|
|
};
|
|
|
|
let title = NSString::alloc(nil).init_str("My Title");
|
|
NSWindow::setTitle_(handle.0 as cocoa::base::id, title);
|
|
NSWindow::setAppearance(handle.0 as cocoa::base::id, selected_appearance);
|
|
set_window_controls_pos(handle.0 as cocoa::base::id);
|
|
});
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
fn set_window_controls_pos(window: cocoa::base::id) {
|
|
use cocoa::{
|
|
appkit::{NSView, NSWindow, NSWindowButton},
|
|
foundation::NSRect,
|
|
};
|
|
|
|
unsafe {
|
|
let close = window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
|
|
let miniaturize = window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
|
|
let zoom = window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
|
|
|
|
let title_bar_container_view = close.superview().superview();
|
|
|
|
let close_rect: NSRect = msg_send![close, frame];
|
|
let button_height = close_rect.size.height;
|
|
|
|
let title_bar_frame_height = button_height + WINDOW_CONTROL_PAD_Y;
|
|
let mut title_bar_rect = NSView::frame(title_bar_container_view);
|
|
title_bar_rect.size.height = title_bar_frame_height;
|
|
title_bar_rect.origin.y = NSView::frame(window).size.height - title_bar_frame_height;
|
|
let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect];
|
|
|
|
let window_buttons = vec![close, miniaturize, zoom];
|
|
let space_between = NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x;
|
|
|
|
for (i, button) in window_buttons.into_iter().enumerate() {
|
|
let mut rect: NSRect = NSView::frame(button);
|
|
rect.origin.x = WINDOW_CONTROL_PAD_X + (i as f64 * space_between);
|
|
button.setFrameOrigin(rect.origin);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
#[derive(Debug)]
|
|
struct AppState {
|
|
window: WebviewWindow,
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
pub fn setup_mac_window(window: &mut WebviewWindow) {
|
|
use cocoa::delegate;
|
|
use cocoa::appkit::NSWindow;
|
|
use cocoa::base::{BOOL, id};
|
|
use cocoa::foundation::NSUInteger;
|
|
use objc::runtime::{Object, Sel};
|
|
use std::ffi::c_void;
|
|
|
|
fn with_app_state<F: FnOnce(&mut AppState) -> T, T>(this: &Object, func: F) {
|
|
let ptr = unsafe {
|
|
let x: *mut c_void = *this.get_ivar("yaakApp");
|
|
&mut *(x as *mut AppState)
|
|
};
|
|
func(ptr);
|
|
}
|
|
|
|
unsafe {
|
|
let ns_win = window.ns_window().unwrap() as id;
|
|
|
|
let current_delegate: id = ns_win.delegate();
|
|
|
|
extern "C" fn on_window_should_close(this: &Object, _cmd: Sel, sender: id) -> BOOL {
|
|
unsafe {
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
msg_send![super_del, windowShouldClose: sender]
|
|
}
|
|
}
|
|
extern "C" fn on_window_will_close(this: &Object, _cmd: Sel, notification: id) {
|
|
unsafe {
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
let _: () = msg_send![super_del, windowWillClose: notification];
|
|
}
|
|
}
|
|
extern "C" fn on_window_did_resize(this: &Object, _cmd: Sel, notification: id) {
|
|
unsafe {
|
|
with_app_state(&*this, |state| {
|
|
let id = state.window.ns_window().unwrap() as id;
|
|
|
|
set_window_controls_pos(id);
|
|
});
|
|
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
let _: () = msg_send![super_del, windowDidResize: notification];
|
|
}
|
|
}
|
|
extern "C" fn on_window_did_move(this: &Object, _cmd: Sel, notification: id) {
|
|
unsafe {
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
let _: () = msg_send![super_del, windowDidMove: notification];
|
|
}
|
|
}
|
|
extern "C" fn on_window_did_change_backing_properties(
|
|
this: &Object,
|
|
_cmd: Sel,
|
|
notification: id,
|
|
) {
|
|
unsafe {
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
let _: () = msg_send![super_del, windowDidChangeBackingProperties: notification];
|
|
}
|
|
}
|
|
extern "C" fn on_window_did_become_key(this: &Object, _cmd: Sel, notification: id) {
|
|
unsafe {
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
let _: () = msg_send![super_del, windowDidBecomeKey: notification];
|
|
}
|
|
}
|
|
extern "C" fn on_window_did_resign_key(this: &Object, _cmd: Sel, notification: id) {
|
|
unsafe {
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
let _: () = msg_send![super_del, windowDidResignKey: notification];
|
|
}
|
|
}
|
|
extern "C" fn on_dragging_entered(this: &Object, _cmd: Sel, notification: id) -> BOOL {
|
|
unsafe {
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
msg_send![super_del, draggingEntered: notification]
|
|
}
|
|
}
|
|
extern "C" fn on_prepare_for_drag_operation(
|
|
this: &Object,
|
|
_cmd: Sel,
|
|
notification: id,
|
|
) -> BOOL {
|
|
unsafe {
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
msg_send![super_del, prepareForDragOperation: notification]
|
|
}
|
|
}
|
|
extern "C" fn on_perform_drag_operation(this: &Object, _cmd: Sel, sender: id) -> BOOL {
|
|
unsafe {
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
msg_send![super_del, performDragOperation: sender]
|
|
}
|
|
}
|
|
extern "C" fn on_conclude_drag_operation(this: &Object, _cmd: Sel, notification: id) {
|
|
unsafe {
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
let _: () = msg_send![super_del, concludeDragOperation: notification];
|
|
}
|
|
}
|
|
extern "C" fn on_dragging_exited(this: &Object, _cmd: Sel, notification: id) {
|
|
unsafe {
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
let _: () = msg_send![super_del, draggingExited: notification];
|
|
}
|
|
}
|
|
extern "C" fn on_window_will_use_full_screen_presentation_options(
|
|
this: &Object,
|
|
_cmd: Sel,
|
|
window: id,
|
|
proposed_options: NSUInteger,
|
|
) -> NSUInteger {
|
|
unsafe {
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
msg_send![super_del, window: window willUseFullScreenPresentationOptions: proposed_options]
|
|
}
|
|
}
|
|
extern "C" fn on_window_did_enter_full_screen(this: &Object, _cmd: Sel, notification: id) {
|
|
unsafe {
|
|
with_app_state(&*this, |state| {
|
|
state.window.emit("did-enter-fullscreen", ()).unwrap();
|
|
});
|
|
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
let _: () = msg_send![super_del, windowDidEnterFullScreen: notification];
|
|
}
|
|
}
|
|
extern "C" fn on_window_will_enter_full_screen(this: &Object, _cmd: Sel, notification: id) {
|
|
unsafe {
|
|
with_app_state(&*this, |state| {
|
|
state.window.emit("will-enter-fullscreen", ()).unwrap();
|
|
});
|
|
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
let _: () = msg_send![super_del, windowWillEnterFullScreen: notification];
|
|
}
|
|
}
|
|
extern "C" fn on_window_did_exit_full_screen(this: &Object, _cmd: Sel, notification: id) {
|
|
unsafe {
|
|
with_app_state(&*this, |state| {
|
|
state.window.emit("did-exit-fullscreen", ()).unwrap();
|
|
|
|
let id = state.window.ns_window().unwrap() as id;
|
|
set_window_controls_pos(id);
|
|
});
|
|
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
let _: () = msg_send![super_del, windowDidExitFullScreen: notification];
|
|
}
|
|
}
|
|
extern "C" fn on_window_will_exit_full_screen(this: &Object, _cmd: Sel, notification: id) {
|
|
unsafe {
|
|
with_app_state(&*this, |state| {
|
|
state.window.emit("will-exit-fullscreen", ()).unwrap();
|
|
});
|
|
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
let _: () = msg_send![super_del, windowWillExitFullScreen: notification];
|
|
}
|
|
}
|
|
extern "C" fn on_window_did_fail_to_enter_full_screen(
|
|
this: &Object,
|
|
_cmd: Sel,
|
|
window: id,
|
|
) {
|
|
unsafe {
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
let _: () = msg_send![super_del, windowDidFailToEnterFullScreen: window];
|
|
}
|
|
}
|
|
extern "C" fn on_effective_appearance_did_change(
|
|
this: &Object,
|
|
_cmd: Sel,
|
|
notification: id,
|
|
) {
|
|
unsafe {
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
let _: () = msg_send![super_del, effectiveAppearanceDidChange: notification];
|
|
}
|
|
}
|
|
extern "C" fn on_effective_appearance_did_changed_on_main_thread(
|
|
this: &Object,
|
|
_cmd: Sel,
|
|
notification: id,
|
|
) {
|
|
unsafe {
|
|
let super_del: id = *this.get_ivar("super_delegate");
|
|
let _: () = msg_send![
|
|
super_del,
|
|
effectiveAppearanceDidChangedOnMainThread: notification
|
|
];
|
|
}
|
|
}
|
|
|
|
// extern fn on_dealloc(this: &Object, cmd: Sel) {
|
|
// unsafe {
|
|
// let super_del: id = *this.get_ivar("super_delegate");
|
|
// let _: () = msg_send![super_del, dealloc];
|
|
// }
|
|
// }
|
|
|
|
// extern fn on_mark_is_checking_zoomed_in(this: &Object, cmd: Sel) {
|
|
// unsafe {
|
|
// let super_del: id = *this.get_ivar("super_delegate");
|
|
// let _: () = msg_send![super_del, markIsCheckingZoomedIn];
|
|
// }
|
|
// }
|
|
|
|
// extern fn on_clear_is_checking_zoomed_in(this: &Object, cmd: Sel) {
|
|
// unsafe {
|
|
// let super_del: id = *this.get_ivar("super_delegate");
|
|
// let _: () = msg_send![super_del, clearIsCheckingZoomedIn];
|
|
// }
|
|
// }
|
|
|
|
// Are we deallocing this properly ? (I miss safe Rust :( )
|
|
let w = window.clone();
|
|
let app_state = AppState { window: w };
|
|
let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void;
|
|
set_window_controls_pos(ns_win);
|
|
|
|
ns_win.setDelegate_(delegate!("MainWindowDelegate", {
|
|
window: id = ns_win,
|
|
yaakApp: *mut c_void = app_box,
|
|
toolbar: id = cocoa::base::nil,
|
|
super_delegate: id = current_delegate,
|
|
// (dealloc) => on_dealloc as extern fn(&Object, Sel),
|
|
// (markIsCheckingZoomedIn) => on_mark_is_checking_zoomed_in as extern fn(&Object, Sel),
|
|
// (clearIsCheckingZoomedIn) => on_clear_is_checking_zoomed_in as extern fn(&Object, Sel),
|
|
(windowShouldClose:) => on_window_should_close as extern fn(&Object, Sel, id) -> BOOL,
|
|
(windowWillClose:) => on_window_will_close as extern fn(&Object, Sel, id),
|
|
(windowDidResize:) => on_window_did_resize as extern fn(&Object, Sel, id),
|
|
(windowDidMove:) => on_window_did_move as extern fn(&Object, Sel, id),
|
|
(windowDidChangeBackingProperties:) => on_window_did_change_backing_properties as extern fn(&Object, Sel, id),
|
|
(windowDidBecomeKey:) => on_window_did_become_key as extern fn(&Object, Sel, id),
|
|
(windowDidResignKey:) => on_window_did_resign_key as extern fn(&Object, Sel, id),
|
|
(draggingEntered:) => on_dragging_entered as extern fn(&Object, Sel, id) -> BOOL,
|
|
(prepareForDragOperation:) => on_prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
|
|
(performDragOperation:) => on_perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
|
|
(concludeDragOperation:) => on_conclude_drag_operation as extern fn(&Object, Sel, id),
|
|
(draggingExited:) => on_dragging_exited as extern fn(&Object, Sel, id),
|
|
(window:willUseFullScreenPresentationOptions:) => on_window_will_use_full_screen_presentation_options as extern fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
|
|
(windowDidEnterFullScreen:) => on_window_did_enter_full_screen as extern fn(&Object, Sel, id),
|
|
(windowWillEnterFullScreen:) => on_window_will_enter_full_screen as extern fn(&Object, Sel, id),
|
|
(windowDidExitFullScreen:) => on_window_did_exit_full_screen as extern fn(&Object, Sel, id),
|
|
(windowWillExitFullScreen:) => on_window_will_exit_full_screen as extern fn(&Object, Sel, id),
|
|
(windowDidFailToEnterFullScreen:) => on_window_did_fail_to_enter_full_screen as extern fn(&Object, Sel, id),
|
|
(effectiveAppearanceDidChange:) => on_effective_appearance_did_change as extern fn(&Object, Sel, id),
|
|
(effectiveAppearanceDidChangedOnMainThread:) => on_effective_appearance_did_changed_on_main_thread as extern fn(&Object, Sel, id)
|
|
}))
|
|
}
|
|
|
|
let app = window.app_handle();
|
|
let window = window.clone();
|
|
|
|
// Control window theme based on app update_window
|
|
let window_for_theme = window.clone();
|
|
app.listen_any("yaak_bg_changed", move |ev| {
|
|
let payload = serde_json::from_str::<&str>(ev.payload())
|
|
.unwrap()
|
|
.trim();
|
|
|
|
let color = HexColor::parse_rgb(payload).unwrap();
|
|
|
|
update_window_theme(&window_for_theme, color);
|
|
});
|
|
|
|
let window_for_title = window.clone();
|
|
app.listen_any("yaak_title_changed", move |ev| {
|
|
let title = serde_json::from_str::<&str>(ev.payload())
|
|
.unwrap()
|
|
.trim();
|
|
update_window_title(&window_for_title, title.to_string());
|
|
});
|
|
}
|