fix(quick-pane): refresh models and window lifecycle
This commit is contained in:
+208
-28
@@ -2,14 +2,45 @@ mod bridge;
|
||||
mod ui_state;
|
||||
|
||||
use serde_json::{json, Value};
|
||||
use std::{env, path::Path, sync::{Arc, Mutex}, time::Duration};
|
||||
use std::{
|
||||
env,
|
||||
path::Path,
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
use tauri::{
|
||||
async_runtime::JoinHandle, AppHandle, Emitter, Manager, State, WebviewWindow,
|
||||
WebviewWindowBuilder, WindowEvent,
|
||||
};
|
||||
|
||||
struct LegacySubscription(Mutex<Option<JoinHandle<()>>>);
|
||||
struct QuickPaneVisibility(Mutex<bool>);
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct ManagedWindowVisibility {
|
||||
main: bool,
|
||||
quick_pane: bool,
|
||||
}
|
||||
|
||||
impl ManagedWindowVisibility {
|
||||
fn main_shown(&mut self) {
|
||||
self.main = true;
|
||||
self.quick_pane = false;
|
||||
}
|
||||
|
||||
fn quick_pane_shown(&mut self) {
|
||||
self.main = false;
|
||||
self.quick_pane = true;
|
||||
}
|
||||
|
||||
fn hidden(&mut self, label: &str) {
|
||||
if label == "main" {
|
||||
self.main = false;
|
||||
} else if label == "quick-pane" {
|
||||
self.quick_pane = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// One mutex serializes cross-window transitions, avoiding nested visibility-lock ordering.
|
||||
struct WindowVisibility(Mutex<ManagedWindowVisibility>);
|
||||
struct WorkspaceSubscription {
|
||||
generation: Arc<bridge::SubscriptionGeneration>,
|
||||
task: Mutex<Option<JoinHandle<()>>>,
|
||||
@@ -62,7 +93,67 @@ fn quick_pane_action(is_visible: bool) -> WindowAction {
|
||||
}
|
||||
|
||||
fn should_hide_close_request(label: &str) -> bool {
|
||||
label == "quick-pane"
|
||||
label == "main" || label == "quick-pane"
|
||||
}
|
||||
|
||||
fn get_or_create_main(app: &AppHandle) -> Result<(WebviewWindow, bool), String> {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
return Ok((window, false));
|
||||
}
|
||||
let config = app
|
||||
.config()
|
||||
.app
|
||||
.windows
|
||||
.iter()
|
||||
.find(|window| window.label == "main")
|
||||
.ok_or_else(|| "Main window configuration is missing".to_owned())?;
|
||||
let window = WebviewWindowBuilder::from_config(app, config)
|
||||
.map_err(|error| error.to_string())?
|
||||
.build()
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok((window, true))
|
||||
}
|
||||
|
||||
fn apply_main_window_action(app: &AppHandle, args: &[String]) {
|
||||
let (window, created) = match get_or_create_main(app) {
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
eprintln!("Could not create main window: {error}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let Some(state) = app.try_state::<WindowVisibility>() else {
|
||||
eprintln!("Window visibility state is unavailable");
|
||||
return;
|
||||
};
|
||||
let mut visibility = state
|
||||
.0
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
if created {
|
||||
visibility.main = false;
|
||||
}
|
||||
match window_action(args, visibility.main) {
|
||||
WindowAction::Hide => {
|
||||
if window.hide().is_ok() {
|
||||
visibility.hidden("main");
|
||||
}
|
||||
}
|
||||
WindowAction::ShowAndFocus => {
|
||||
if let Some(quick_pane) = app.get_webview_window("quick-pane") {
|
||||
if let Err(error) = quick_pane.hide() {
|
||||
eprintln!("Could not hide Quick Pane before showing main window: {error}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
visibility.hidden("quick-pane");
|
||||
if window.show().is_ok() {
|
||||
visibility.main_shown();
|
||||
let _ = window.unminimize();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_or_create_quick_pane(app: &AppHandle) -> Result<(WebviewWindow, bool), String> {
|
||||
@@ -91,29 +182,33 @@ fn toggle_quick_pane(app: &AppHandle) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
let Some(state) = app.try_state::<QuickPaneVisibility>() else {
|
||||
eprintln!("Quick Pane visibility state is unavailable");
|
||||
let Some(state) = app.try_state::<WindowVisibility>() else {
|
||||
eprintln!("Window visibility state is unavailable");
|
||||
return;
|
||||
};
|
||||
let mut current = state
|
||||
let mut visibility = state
|
||||
.0
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
if created {
|
||||
*current = false;
|
||||
visibility.quick_pane = false;
|
||||
}
|
||||
match quick_pane_action(*current) {
|
||||
match quick_pane_action(visibility.quick_pane) {
|
||||
WindowAction::Hide => {
|
||||
if window.hide().is_ok() {
|
||||
*current = false;
|
||||
visibility.hidden("quick-pane");
|
||||
}
|
||||
}
|
||||
WindowAction::ShowAndFocus => {
|
||||
if let Some(main) = app.get_webview_window("main") {
|
||||
let _ = main.hide();
|
||||
if let Err(error) = main.hide() {
|
||||
eprintln!("Could not hide main window before showing Quick Pane: {error}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
visibility.hidden("main");
|
||||
if window.show().is_ok() {
|
||||
*current = true;
|
||||
visibility.quick_pane_shown();
|
||||
let _ = window.unminimize();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
@@ -188,6 +283,23 @@ async fn get_model_catalog() -> Result<Value, String> {
|
||||
bridge::get_model_catalog(&socket_path()?).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn dismiss_quick_pane(
|
||||
app: AppHandle,
|
||||
visibility: State<'_, WindowVisibility>,
|
||||
) -> Result<(), String> {
|
||||
let quick_pane = app
|
||||
.get_webview_window("quick-pane")
|
||||
.ok_or_else(|| "Quick Pane window is unavailable".to_owned())?;
|
||||
let mut visibility = visibility
|
||||
.0
|
||||
.lock()
|
||||
.map_err(|_| "Could not update window visibility".to_owned())?;
|
||||
quick_pane.hide().map_err(|error| error.to_string())?;
|
||||
visibility.hidden("quick-pane");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn create_session_runtime(worktree_path: String) -> Result<bridge::RuntimeResult, String> {
|
||||
bridge::create_session_runtime(&socket_path()?, &worktree_path).await
|
||||
@@ -443,7 +555,10 @@ pub fn run() {
|
||||
let builder = tauri::Builder::default()
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.manage(LegacySubscription(Mutex::new(None)))
|
||||
.manage(QuickPaneVisibility(Mutex::new(false)))
|
||||
.manage(WindowVisibility(Mutex::new(ManagedWindowVisibility {
|
||||
main: true,
|
||||
quick_pane: false,
|
||||
})))
|
||||
.manage(WorkspaceSubscription {
|
||||
generation: Arc::new(bridge::SubscriptionGeneration::default()),
|
||||
task: Mutex::new(None),
|
||||
@@ -454,29 +569,19 @@ pub fn run() {
|
||||
return;
|
||||
}
|
||||
launch_new_session(app.clone(), &args);
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
match window_action(&args, window.is_visible().unwrap_or(false)) {
|
||||
WindowAction::Hide => {
|
||||
let _ = window.hide();
|
||||
}
|
||||
WindowAction::ShowAndFocus => {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
apply_main_window_action(app, &args);
|
||||
}))
|
||||
.on_window_event(|window, event| {
|
||||
if should_hide_close_request(window.label()) {
|
||||
if let WindowEvent::CloseRequested { api, .. } = event {
|
||||
api.prevent_close();
|
||||
if let Some(state) = window.app_handle().try_state::<QuickPaneVisibility>() {
|
||||
let mut current = state
|
||||
if let Some(state) = window.app_handle().try_state::<WindowVisibility>() {
|
||||
let mut visibility = state
|
||||
.0
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
if window.hide().is_ok() {
|
||||
*current = false;
|
||||
visibility.hidden(window.label());
|
||||
}
|
||||
} else {
|
||||
let _ = window.hide();
|
||||
@@ -496,6 +601,7 @@ pub fn run() {
|
||||
get_workspace,
|
||||
get_workspace_summary,
|
||||
get_model_catalog,
|
||||
dismiss_quick_pane,
|
||||
create_session_runtime,
|
||||
create_quick_runtime,
|
||||
open_session_runtime,
|
||||
@@ -570,9 +676,83 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_quick_pane_native_close_requests_are_hidden() {
|
||||
fn native_close_requests_hide_managed_windows() {
|
||||
assert!(should_hide_close_request("main"));
|
||||
assert!(should_hide_close_request("quick-pane"));
|
||||
assert!(!should_hide_close_request("main"));
|
||||
assert!(!should_hide_close_request("other"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn showing_main_clears_quick_pane_visibility() {
|
||||
let mut visibility = ManagedWindowVisibility {
|
||||
main: false,
|
||||
quick_pane: true,
|
||||
};
|
||||
|
||||
visibility.main_shown();
|
||||
|
||||
assert_eq!(
|
||||
visibility,
|
||||
ManagedWindowVisibility {
|
||||
main: true,
|
||||
quick_pane: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn showing_quick_pane_clears_main_visibility() {
|
||||
let mut visibility = ManagedWindowVisibility {
|
||||
main: true,
|
||||
quick_pane: false,
|
||||
};
|
||||
|
||||
visibility.quick_pane_shown();
|
||||
|
||||
assert_eq!(
|
||||
visibility,
|
||||
ManagedWindowVisibility {
|
||||
main: false,
|
||||
quick_pane: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dismissed_quick_pane_reopens_with_one_toggle() {
|
||||
let mut visibility = ManagedWindowVisibility {
|
||||
main: false,
|
||||
quick_pane: true,
|
||||
};
|
||||
|
||||
visibility.hidden("quick-pane");
|
||||
assert_eq!(
|
||||
quick_pane_action(visibility.quick_pane),
|
||||
WindowAction::ShowAndFocus
|
||||
);
|
||||
visibility.quick_pane_shown();
|
||||
assert!(visibility.quick_pane);
|
||||
assert!(!visibility.main);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main_recreation_uses_the_complete_configured_window() {
|
||||
let context: tauri::Context<tauri::Wry> = tauri::generate_context!();
|
||||
let main = context
|
||||
.config()
|
||||
.app
|
||||
.windows
|
||||
.iter()
|
||||
.find(|window| window.label == "main")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(main.width, 1100.0);
|
||||
assert_eq!(main.height, 840.0);
|
||||
assert_eq!(main.url.to_string(), "index.html");
|
||||
assert!(main.visible);
|
||||
assert!(main.transparent);
|
||||
assert!(main.always_on_top);
|
||||
assert!(main.skip_taskbar);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user