feat(rust): commands module with click-through toggle helpers
This commit is contained in:
1
src-tauri/src/commands/mod.rs
Normal file
1
src-tauri/src/commands/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod overlay;
|
||||||
117
src-tauri/src/commands/overlay.rs
Normal file
117
src-tauri/src/commands/overlay.rs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
use tauri::{AppHandle, Emitter, Manager, Runtime};
|
||||||
|
|
||||||
|
pub const OVERLAY_LABEL: &str = "overlay";
|
||||||
|
pub const EVT_CLICK_THROUGH_TOGGLED: &str = "click-through-toggled";
|
||||||
|
pub const EVT_CLICK_THROUGH_NO_OVERLAY: &str = "click-through-no-overlay";
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize)]
|
||||||
|
pub struct ClickThroughToggledPayload {
|
||||||
|
#[serde(rename = "clickThrough")]
|
||||||
|
pub click_through: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies a specific click-through state to the overlay (sets ignore_cursor_events,
|
||||||
|
/// performs the opacity pulse, emits the toggled event). Returns the applied state.
|
||||||
|
///
|
||||||
|
/// Tauri 2's WebviewWindow doesn't expose getters for `ignore_cursor_events` or
|
||||||
|
/// `opacity`, so callers must compute the new state themselves: the JS-side
|
||||||
|
/// `toggle_click_through` command receives `current` from the front end and
|
||||||
|
/// passes `!current`; the global shortcut handler maintains its own cache via
|
||||||
|
/// `AppState.click_through`.
|
||||||
|
pub async fn apply_state<R: Runtime>(
|
||||||
|
app: &AppHandle<R>,
|
||||||
|
new_state: bool,
|
||||||
|
) -> Result<Option<bool>, String> {
|
||||||
|
let Some(overlay) = app.get_webview_window(OVERLAY_LABEL) else {
|
||||||
|
let _ = app.emit(EVT_CLICK_THROUGH_NO_OVERLAY, ());
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
overlay
|
||||||
|
.set_ignore_cursor_events(new_state)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// Opacity dip half of the visual pulse. The JS click-through-toggled
|
||||||
|
// handler is responsible for restoring opacity to the user's actual
|
||||||
|
// target — Rust deliberately does NOT call set_opacity again, so there
|
||||||
|
// is no possibility of the global-shortcut path "snapping back" to a
|
||||||
|
// wrong value (we don't have a current-opacity getter).
|
||||||
|
let _ = overlay.set_opacity(0.4);
|
||||||
|
tokio::time::sleep(Duration::from_millis(180)).await;
|
||||||
|
|
||||||
|
app.emit(
|
||||||
|
EVT_CLICK_THROUGH_TOGGLED,
|
||||||
|
ClickThroughToggledPayload {
|
||||||
|
click_through: new_state,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(Some(new_state))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tauri command — invoked from JS. The JS side passes the *current* state so
|
||||||
|
/// we can compute the inverse without needing a getter on the Tauri side.
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn toggle_click_through<R: Runtime>(
|
||||||
|
app: AppHandle<R>,
|
||||||
|
current: bool,
|
||||||
|
) -> Result<Option<bool>, String> {
|
||||||
|
apply_state(&app, !current).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stateless setter — used by the global shortcut handler, which doesn't have
|
||||||
|
/// access to the JS-side current value. Reads the *intended* state from a
|
||||||
|
/// shared `Mutex<bool>` kept in app state.
|
||||||
|
pub async fn toggle_via_global_shortcut<R: Runtime>(
|
||||||
|
app: &AppHandle<R>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let state = app.state::<crate::AppState>();
|
||||||
|
let new_value = {
|
||||||
|
let mut guard = state.click_through.lock().expect("poisoned");
|
||||||
|
*guard = !*guard;
|
||||||
|
*guard
|
||||||
|
};
|
||||||
|
apply_state(app, new_value).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sync the Rust-side click-through cache with what the JS side just applied.
|
||||||
|
/// Called on overlay open/close and whenever the JS toggle changes user-side.
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn sync_click_through_cache(
|
||||||
|
app: AppHandle,
|
||||||
|
value: bool,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let state = app.state::<crate::AppState>();
|
||||||
|
let mut guard = state.click_through.lock().map_err(|_| "poisoned".to_string())?;
|
||||||
|
*guard = value;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pull the hotkey-registration outcome that was decided during setup.
|
||||||
|
/// Pull-style avoids the listener-not-yet-registered race that an event-based
|
||||||
|
/// approach would have.
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_hotkey_status(app: AppHandle) -> Result<crate::HotkeyStatus, String> {
|
||||||
|
let state = app.state::<crate::AppState>();
|
||||||
|
let guard = state.hotkey_status.lock().map_err(|_| "poisoned".to_string())?;
|
||||||
|
Ok(guard.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set window opacity. Tauri 2's JS WebviewWindow does NOT expose setOpacity
|
||||||
|
/// (only the Rust side does), so the JS layer routes through this command.
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn set_window_opacity<R: Runtime>(
|
||||||
|
app: AppHandle<R>,
|
||||||
|
label: String,
|
||||||
|
opacity: f32,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let Some(window) = app.get_webview_window(&label) else {
|
||||||
|
return Err(format!("window not found: {label}"));
|
||||||
|
};
|
||||||
|
window.set_opacity(opacity).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user