Get rid of private API on macos
This commit is contained in:
Generated
+67
-10
@@ -668,6 +668,22 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cocoa"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"block",
|
||||
"cocoa-foundation 0.1.2",
|
||||
"core-foundation 0.9.4",
|
||||
"core-graphics 0.23.2",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cocoa"
|
||||
version = "0.26.0"
|
||||
@@ -676,14 +692,28 @@ checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"block",
|
||||
"cocoa-foundation",
|
||||
"cocoa-foundation 0.2.0",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics",
|
||||
"core-graphics 0.24.0",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cocoa-foundation"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"block",
|
||||
"core-foundation 0.9.4",
|
||||
"core-graphics-types 0.1.3",
|
||||
"libc",
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cocoa-foundation"
|
||||
version = "0.2.0"
|
||||
@@ -693,7 +723,7 @@ dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"block",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics-types",
|
||||
"core-graphics-types 0.2.0",
|
||||
"libc",
|
||||
"objc",
|
||||
]
|
||||
@@ -787,6 +817,19 @@ version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation 0.9.4",
|
||||
"core-graphics-types 0.1.3",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics"
|
||||
version = "0.24.0"
|
||||
@@ -795,11 +838,22 @@ checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics-types",
|
||||
"core-graphics-types 0.2.0",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics-types"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation 0.9.4",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics-types"
|
||||
version = "0.2.0"
|
||||
@@ -3511,7 +3565,10 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||
name = "readest"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cocoa 0.25.0",
|
||||
"log",
|
||||
"objc",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
@@ -4153,7 +4210,7 @@ checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cfg_aliases 0.2.1",
|
||||
"core-graphics",
|
||||
"core-graphics 0.24.0",
|
||||
"foreign-types",
|
||||
"js-sys",
|
||||
"log",
|
||||
@@ -4359,9 +4416,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63f1f6b2017cc33d7f6fc9c6186a2c0f5dfc985899a7b4fe9e64985c17533db3"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cocoa",
|
||||
"cocoa 0.26.0",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics",
|
||||
"core-graphics 0.24.0",
|
||||
"crossbeam-channel",
|
||||
"dispatch",
|
||||
"dlopen2",
|
||||
@@ -4568,7 +4625,7 @@ checksum = "8e5cd17faa36a826e5686bd0fda5bc3f4c903682263f00cd50f2f778fc4bb866"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"bytes",
|
||||
"cocoa",
|
||||
"cocoa 0.26.0",
|
||||
"colored",
|
||||
"devtools-core",
|
||||
"futures",
|
||||
@@ -4656,7 +4713,7 @@ checksum = "9a49f2c05d15e6375ab7f7e528b3049150ba4dfafdf61f85e5178d0aef18e3f5"
|
||||
dependencies = [
|
||||
"android_logger",
|
||||
"byte-unit",
|
||||
"cocoa",
|
||||
"cocoa 0.26.0",
|
||||
"fern",
|
||||
"log",
|
||||
"objc",
|
||||
@@ -5228,7 +5285,7 @@ version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c92af36a182b46206723bdf8a7942e20838cde1cf062e5b97854d57eb01763b"
|
||||
dependencies = [
|
||||
"core-graphics",
|
||||
"core-graphics 0.24.0",
|
||||
"crossbeam-channel",
|
||||
"dirs",
|
||||
"libappindicator",
|
||||
|
||||
@@ -22,7 +22,7 @@ serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
log = "0.4"
|
||||
# FIXME: remove the devtools feature in production
|
||||
tauri = { version = "2.0.2", features = [ "macos-private-api", "protocol-asset", "devtools"] }
|
||||
tauri = { version = "2.0.2", features = [ "protocol-asset", "devtools"] }
|
||||
tauri-plugin-log = "2.0.1"
|
||||
tauri-plugin-fs = "2.0.1"
|
||||
tauri-plugin-dialog = "2.0.1"
|
||||
@@ -33,3 +33,8 @@ tauri-plugin-devtools = "2.0.0"
|
||||
[patch.crates-io]
|
||||
tauri = { path = "../../../packages/tauri/crates/tauri" }
|
||||
wry = { path = "../../../packages/wry" }
|
||||
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
cocoa = "0.25"
|
||||
objc = "0.2.7"
|
||||
rand = "0.8"
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
#[macro_use]
|
||||
extern crate cocoa;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[macro_use]
|
||||
extern crate objc;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod tauri_traffic_light_positioner_plugin;
|
||||
|
||||
use tauri::{TitleBarStyle, WebviewUrl, WebviewWindowBuilder};
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
@@ -5,6 +18,7 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_traffic_light_positioner_plugin::init())
|
||||
.setup(|app| {
|
||||
if cfg!(debug_assertions) {
|
||||
app.handle().plugin(
|
||||
@@ -13,6 +27,17 @@ pub fn run() {
|
||||
.build(),
|
||||
)?;
|
||||
}
|
||||
let win_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
|
||||
.title("")
|
||||
.inner_size(800.0, 600.0)
|
||||
.resizable(true)
|
||||
.maximized(true);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let win_builder = win_builder.title_bar_style(TitleBarStyle::Overlay);
|
||||
|
||||
let _ = win_builder.build().unwrap();
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use tauri::Emitter;
|
||||
use tauri::{
|
||||
plugin::{Builder, TauriPlugin},
|
||||
Runtime, Window,
|
||||
}; // 0.8
|
||||
|
||||
const WINDOW_CONTROL_PAD_X: f64 = 10.0;
|
||||
const WINDOW_CONTROL_PAD_Y: f64 = 21.0;
|
||||
|
||||
struct UnsafeWindowHandle(*mut std::ffi::c_void);
|
||||
unsafe impl Send for UnsafeWindowHandle {}
|
||||
unsafe impl Sync for UnsafeWindowHandle {}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("traffic_light_positioner")
|
||||
.on_window_ready(|window| {
|
||||
#[cfg(target_os = "macos")]
|
||||
setup_traffic_light_positioner(window.clone());
|
||||
let window_clone = window.clone();
|
||||
window.on_window_event(move |event| {
|
||||
let window = window_clone.clone();
|
||||
if let tauri::WindowEvent::ThemeChanged(_theme) = event {
|
||||
setup_traffic_light_positioner(window);
|
||||
}
|
||||
});
|
||||
return;
|
||||
})
|
||||
.build()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn position_traffic_lights(ns_window_handle: UnsafeWindowHandle, x: f64, y: f64) {
|
||||
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
|
||||
use cocoa::foundation::NSRect;
|
||||
let ns_window = ns_window_handle.0 as cocoa::base::id;
|
||||
unsafe {
|
||||
let close = ns_window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
|
||||
let miniaturize =
|
||||
ns_window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
|
||||
let zoom = ns_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 + 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(ns_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 = x + (i as f64 * space_between);
|
||||
button.setFrameOrigin(rect.origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[derive(Debug)]
|
||||
struct WindowState<R: Runtime> {
|
||||
window: Window<R>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
|
||||
use cocoa::appkit::NSWindow;
|
||||
use cocoa::base::{id, BOOL};
|
||||
use cocoa::foundation::NSUInteger;
|
||||
use objc::runtime::{Object, Sel};
|
||||
use std::ffi::c_void;
|
||||
|
||||
// Do the initial positioning
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(window.ns_window().expect("Failed to create window handle")),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
);
|
||||
|
||||
// Ensure they stay in place while resizing the window.
|
||||
fn with_window_state<R: Runtime, F: FnOnce(&mut WindowState<R>) -> T, T>(
|
||||
this: &Object,
|
||||
func: F,
|
||||
) {
|
||||
let ptr = unsafe {
|
||||
let x: *mut c_void = *this.get_ivar("app_box");
|
||||
&mut *(x as *mut WindowState<R>)
|
||||
};
|
||||
func(ptr);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ns_win = window
|
||||
.ns_window()
|
||||
.expect("NS Window should exist to mount traffic light delegate.")
|
||||
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<R: Runtime>(this: &Object, _cmd: Sel, notification: id) {
|
||||
unsafe {
|
||||
with_window_state(&*this, |state: &mut WindowState<R>| {
|
||||
let id = state
|
||||
.window
|
||||
.ns_window()
|
||||
.expect("NS window should exist on state to handle resize")
|
||||
as id;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(id as *mut std::ffi::c_void),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
);
|
||||
});
|
||||
|
||||
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<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(&*this, |state: &mut WindowState<R>| {
|
||||
state
|
||||
.window
|
||||
.emit("did-enter-fullscreen", ())
|
||||
.expect("Failed to emit event");
|
||||
});
|
||||
|
||||
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<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(&*this, |state: &mut WindowState<R>| {
|
||||
state
|
||||
.window
|
||||
.emit("will-enter-fullscreen", ())
|
||||
.expect("Failed to emit event");
|
||||
});
|
||||
|
||||
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<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(&*this, |state: &mut WindowState<R>| {
|
||||
state
|
||||
.window
|
||||
.emit("did-exit-fullscreen", ())
|
||||
.expect("Failed to emit event");
|
||||
|
||||
let id = state.window.ns_window().expect("Failed to emit event") as id;
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(id as *mut std::ffi::c_void),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
);
|
||||
});
|
||||
|
||||
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<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(&*this, |state: &mut WindowState<R>| {
|
||||
state
|
||||
.window
|
||||
.emit("will-exit-fullscreen", ())
|
||||
.expect("Failed to emit event");
|
||||
});
|
||||
|
||||
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
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Are we deallocing this properly ? (I miss safe Rust :( )
|
||||
let window_label = window.label().to_string();
|
||||
|
||||
let app_state = WindowState { window };
|
||||
let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void;
|
||||
let random_str: String = rand::thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(20)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
|
||||
// We need to ensure we have a unique delegate name, otherwise we will panic while trying to create a duplicate
|
||||
// delegate with the same name.
|
||||
let delegate_name = format!("windowDelegate_{}_{}", window_label, random_str);
|
||||
|
||||
ns_win.setDelegate_(delegate!(&delegate_name, {
|
||||
window: id = ns_win,
|
||||
app_box: *mut c_void = app_box,
|
||||
toolbar: id = cocoa::base::nil,
|
||||
super_delegate: id = current_delegate,
|
||||
(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::<R> 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::<R> as extern fn(&Object, Sel, id),
|
||||
(windowWillEnterFullScreen:) => on_window_will_enter_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||
(windowDidExitFullScreen:) => on_window_did_exit_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||
(windowWillExitFullScreen:) => on_window_will_exit_full_screen::<R> 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)
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -10,20 +10,7 @@
|
||||
"beforeBuildCommand": "pnpm build"
|
||||
},
|
||||
"app": {
|
||||
"macOSPrivateApi": true,
|
||||
"windows": [
|
||||
{
|
||||
"title": "Readest",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"resizable": true,
|
||||
"fullscreen": false,
|
||||
"maximized": true,
|
||||
"decorations": false,
|
||||
"transparent": true,
|
||||
"shadow": false
|
||||
}
|
||||
],
|
||||
"windows": [],
|
||||
"security": {
|
||||
"csp": {
|
||||
"default-src": "'self' 'unsafe-inline' blob: customprotocol: asset: http://asset.localhost ipc: http://ipc.localhost https://*.sentry.io https://*.posthog.com https://fonts.gstatic.com",
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { FaSearch } from 'react-icons/fa';
|
||||
import { PiPlus } from 'react-icons/pi';
|
||||
import { PiSelectionAllDuotone } from 'react-icons/pi';
|
||||
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import useFullScreen from '@/hooks/useFullScreen';
|
||||
import WindowButtons from '@/components/WindowButtons';
|
||||
|
||||
interface LibraryHeaderProps {
|
||||
@@ -16,7 +19,10 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
onImportBooks,
|
||||
onToggleSelectMode,
|
||||
}) => {
|
||||
const { appService } = useEnv();
|
||||
const { isFullScreen } = useFullScreen();
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.ctrlKey || e.shiftKey) {
|
||||
@@ -45,10 +51,16 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={headerRef} className='titlebar fixed top-2 z-10 w-full bg-gray-100 px-6 py-4'>
|
||||
<div
|
||||
ref={headerRef}
|
||||
className={clsx(
|
||||
'titlebar fixed z-10 w-full bg-gray-100 py-2 pr-6',
|
||||
isFullScreen ? 'pl-2' : 'pl-16',
|
||||
)}
|
||||
>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='sm:w relative flex w-full items-center'>
|
||||
<span className='absolute left-4 text-gray-500'>
|
||||
<div className='sm:w relative flex w-full items-center pl-4'>
|
||||
<span className='absolute left-8 text-gray-500'>
|
||||
<FaSearch className='h-4 w-4' />
|
||||
</span>
|
||||
<input
|
||||
@@ -81,12 +93,14 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<WindowButtons
|
||||
headerRef={headerRef}
|
||||
onMinimize={handleMinimize}
|
||||
onToggleMaximize={handleToggleMaximize}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
{!appService?.isNativeWindow && (
|
||||
<WindowButtons
|
||||
headerRef={headerRef}
|
||||
onMinimize={handleMinimize}
|
||||
onToggleMaximize={handleToggleMaximize}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -72,6 +72,10 @@ const LibraryPage = () => {
|
||||
setIsSelectMode(!isSelectMode);
|
||||
};
|
||||
|
||||
if (!appService) {
|
||||
return <Spinner loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='rounded-window min-h-screen select-none bg-gray-100'>
|
||||
<LibraryHeader
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { GiBookshelf } from 'react-icons/gi';
|
||||
import { FiSearch } from 'react-icons/fi';
|
||||
|
||||
import { MdOutlineMenu, MdOutlinePushPin, MdPushPin } from 'react-icons/md';
|
||||
|
||||
import useFullScreen from '@/hooks/useFullScreen';
|
||||
import Dropdown from '@/components/Dropdown';
|
||||
import BookMenu from './BookMenu';
|
||||
|
||||
@@ -11,32 +13,40 @@ const SidebarHeader: React.FC<{
|
||||
onGoToLibrary: () => void;
|
||||
onOpenSplitView: () => void;
|
||||
handleTogglePin: () => void;
|
||||
}> = ({ isPinned, onGoToLibrary, onOpenSplitView, handleTogglePin }) => (
|
||||
<div className='sidebar-header flex h-11 items-center justify-between pl-1.5 pr-3'>
|
||||
<div className='flex items-center'>
|
||||
<button className='btn btn-ghost h-8 min-h-8 w-8 p-0' onClick={onGoToLibrary}>
|
||||
<GiBookshelf size={20} className='fill-base-content' />
|
||||
</button>
|
||||
}> = ({ isPinned, onGoToLibrary, onOpenSplitView, handleTogglePin }) => {
|
||||
const { isFullScreen } = useFullScreen();
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'sidebar-header flex h-11 items-center justify-between pr-3',
|
||||
isFullScreen ? 'pl-1.5' : 'pl-20',
|
||||
)}
|
||||
>
|
||||
<div className='flex items-center'>
|
||||
<button className='btn btn-ghost h-8 min-h-8 w-8 p-0' onClick={onGoToLibrary}>
|
||||
<GiBookshelf size={20} className='fill-base-content' />
|
||||
</button>
|
||||
</div>
|
||||
<div className='flex size-[50%] min-w-20 items-center justify-between'>
|
||||
<button className='btn btn-ghost left-0 h-8 min-h-8 w-8 p-0'>
|
||||
<FiSearch size={18} />
|
||||
</button>
|
||||
<Dropdown
|
||||
className='dropdown-bottom flex justify-center'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
|
||||
toggleButton={<MdOutlineMenu size={20} />}
|
||||
>
|
||||
<BookMenu openSplitView={onOpenSplitView} />
|
||||
</Dropdown>
|
||||
<button
|
||||
onClick={handleTogglePin}
|
||||
className={`${isPinned ? 'bg-gray-300' : 'bg-base-300'} btn btn-ghost btn-circle right-0 h-6 min-h-6 w-6`}
|
||||
>
|
||||
{isPinned ? <MdPushPin size={14} /> : <MdOutlinePushPin size={14} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex size-[50%] min-w-20 items-center justify-between'>
|
||||
<button className='btn btn-ghost left-0 h-8 min-h-8 w-8 p-0'>
|
||||
<FiSearch size={18} />
|
||||
</button>
|
||||
<Dropdown
|
||||
className='dropdown-bottom flex justify-center'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
|
||||
toggleButton={<MdOutlineMenu size={20} />}
|
||||
>
|
||||
<BookMenu openSplitView={onOpenSplitView} />
|
||||
</Dropdown>
|
||||
<button
|
||||
onClick={handleTogglePin}
|
||||
className={`${isPinned ? 'bg-gray-300' : 'bg-base-300'} btn btn-ghost btn-circle right-0 h-6 min-h-6 w-6`}
|
||||
>
|
||||
{isPinned ? <MdPushPin size={14} /> : <MdOutlinePushPin size={14} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarHeader;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const useFullScreen = () => {
|
||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||
|
||||
const handleSwitchFullScreen = async () => {
|
||||
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
||||
const currentWindow = getCurrentWindow();
|
||||
currentWindow.listen('will-enter-fullscreen', () => {
|
||||
console.log('Window entered fullscreen');
|
||||
setIsFullScreen(true);
|
||||
});
|
||||
|
||||
currentWindow.listen('will-exit-fullscreen', () => {
|
||||
console.log('Window exited fullscreen');
|
||||
setIsFullScreen(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
handleSwitchFullScreen();
|
||||
}, []);
|
||||
|
||||
return { isFullScreen };
|
||||
};
|
||||
|
||||
export default useFullScreen;
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
export abstract class BaseAppService implements AppService {
|
||||
localBooksDir: string = '';
|
||||
abstract isAppDataSandbox: boolean;
|
||||
abstract isNativeWindow: boolean;
|
||||
abstract fs: FileSystem;
|
||||
|
||||
abstract resolvePath(fp: string, base: BaseDir): { baseDir: number; base: BaseDir; fp: string };
|
||||
|
||||
@@ -115,6 +115,7 @@ export const nativeFileSystem: FileSystem = {
|
||||
export class NativeAppService extends BaseAppService {
|
||||
fs = nativeFileSystem;
|
||||
isAppDataSandbox = isMobile;
|
||||
isNativeWindow = osType() === 'macos';
|
||||
|
||||
override resolvePath(fp: string, base: BaseDir): { baseDir: number; base: BaseDir; fp: string } {
|
||||
return resolvePath(fp, base);
|
||||
|
||||
@@ -18,6 +18,8 @@ export interface FileSystem {
|
||||
|
||||
export interface AppService {
|
||||
fs: FileSystem;
|
||||
isNativeWindow: boolean;
|
||||
isAppDataSandbox: boolean;
|
||||
|
||||
selectDirectory(title: string): Promise<string>;
|
||||
selectFiles(name: string, extensions: string[]): Promise<string[]>;
|
||||
|
||||
Reference in New Issue
Block a user