Skip to content
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@
"e2e:test:l1": "cross-env BITFUN_E2E_APP_MODE=debug pnpm --dir tests/e2e run test:l1",
"e2e:test:smoke": "cross-env BITFUN_E2E_APP_MODE=debug pnpm --dir tests/e2e run test:smoke",
"e2e:test:chat": "cross-env BITFUN_E2E_APP_MODE=debug pnpm --dir tests/e2e run test:chat",
"e2e:test:perf:debug": "cross-env BITFUN_E2E_APP_MODE=debug pnpm --dir tests/e2e run test:perf",
"e2e:test:perf:release-fast": "cross-env BITFUN_E2E_APP_MODE=release-fast pnpm --dir tests/e2e run test:perf"
"e2e:test:perf:debug": "cross-env BITFUN_E2E_APP_MODE=debug E2E_LOG_LEVEL=warn pnpm --dir tests/e2e run test:perf",
"e2e:test:perf:release-fast": "cross-env BITFUN_E2E_APP_MODE=release-fast E2E_LOG_LEVEL=warn pnpm --dir tests/e2e run test:perf"
},
"devDependencies": {
"@tauri-apps/cli": "^2.10.0",
Expand Down
153 changes: 150 additions & 3 deletions src/apps/desktop/src/theme.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Theme System

use std::sync::{OnceLock, RwLock};
use std::time::Instant;
use std::time::{Duration, Instant};

use bitfun_core::infrastructure::try_get_path_manager_arc;
use bitfun_core::service::config::types::GlobalConfig;
Expand All @@ -18,11 +18,106 @@ const AGENT_COMPANION_WINDOW_MAX_WIDTH: f64 = 360.0;
const AGENT_COMPANION_WINDOW_MAX_HEIGHT: f64 = 240.0;
const AGENT_COMPANION_WINDOW_MARGIN: i32 = 64;
const AGENT_COMPANION_WINDOW_EDGE_MARGIN: f64 = 8.0;
#[cfg(target_os = "windows")]
const WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MAX: Duration = Duration::from_millis(150);
#[cfg(target_os = "windows")]
const WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MIN: Duration = Duration::from_millis(16);
#[cfg(target_os = "windows")]
const WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_POLL: Duration = Duration::from_millis(8);

static AGENT_COMPANION_WINDOW_OPS: OnceLock<tokio::sync::Mutex<()>> = OnceLock::new();
static AGENT_COMPANION_WINDOW_LAST_POSITION: OnceLock<RwLock<Option<tauri::LogicalPosition<f64>>>> =
OnceLock::new();

#[cfg(target_os = "windows")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum WindowsMaximizeShowWaitAction {
Ready,
Sleep(Duration),
TimedOut,
}

#[cfg(target_os = "windows")]
fn windows_maximize_show_wait_action(
is_maximized: Option<bool>,
elapsed: Duration,
) -> WindowsMaximizeShowWaitAction {
if is_maximized == Some(true) && elapsed >= WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MIN {
return WindowsMaximizeShowWaitAction::Ready;
}

if elapsed >= WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MAX {
return WindowsMaximizeShowWaitAction::TimedOut;
}

let target_wait = if is_maximized == Some(true) {
WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MIN
} else {
WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MAX
};
WindowsMaximizeShowWaitAction::Sleep(
target_wait
.saturating_sub(elapsed)
.min(WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_POLL),
)
}

#[cfg(target_os = "windows")]
fn read_windows_maximize_state(
window: &tauri::WebviewWindow,
logged_error: &mut bool,
) -> Option<bool> {
match window.is_maximized() {
Ok(is_maximized) => Some(is_maximized),
Err(error) => {
if !*logged_error {
warn!(
"Failed to read main window maximize state during startup show wait: {}",
error
);
*logged_error = true;
}
None
}
}
}

#[cfg(target_os = "windows")]
fn wait_for_windows_maximize_before_show(window: &tauri::WebviewWindow) -> &'static str {
let started_at = Instant::now();
let mut logged_error = false;

loop {
match windows_maximize_show_wait_action(
read_windows_maximize_state(window, &mut logged_error),
started_at.elapsed(),
) {
WindowsMaximizeShowWaitAction::Ready => return "ready",
WindowsMaximizeShowWaitAction::TimedOut => return "timeout",
WindowsMaximizeShowWaitAction::Sleep(duration) => std::thread::sleep(duration),
}
}
}

#[cfg(target_os = "windows")]
async fn wait_for_windows_maximize_before_show_async(
window: &tauri::WebviewWindow,
) -> &'static str {
let started_at = Instant::now();
let mut logged_error = false;

loop {
match windows_maximize_show_wait_action(
read_windows_maximize_state(window, &mut logged_error),
started_at.elapsed(),
) {
WindowsMaximizeShowWaitAction::Ready => return "ready",
WindowsMaximizeShowWaitAction::TimedOut => return "timeout",
WindowsMaximizeShowWaitAction::Sleep(duration) => tokio::time::sleep(duration).await,
}
}
}

fn agent_companion_window_ops() -> &'static tokio::sync::Mutex<()> {
AGENT_COMPANION_WINDOW_OPS.get_or_init(|| tokio::sync::Mutex::new(()))
}
Expand Down Expand Up @@ -568,12 +663,18 @@ fn show_main_window_for_startup(
);
}
let show_delay_started_at = Instant::now();
std::thread::sleep(std::time::Duration::from_millis(150));
let show_wait_outcome = wait_for_windows_maximize_before_show(window);
startup_trace.record_elapsed_step(
"native_window",
"windows_show_after_maximize_wait",
show_delay_started_at,
);
debug!(
"Main window startup show step completed: step=wait_for_maximize_state outcome={} duration_ms={} since_create_start_ms={}",
show_wait_outcome,
show_delay_started_at.elapsed().as_millis(),
total_started_at.elapsed().as_millis()
);
}

let show_started_at = Instant::now();
Expand Down Expand Up @@ -915,7 +1016,13 @@ pub async fn show_main_window(app: tauri::AppHandle) -> Result<(), String> {
step_started_at.elapsed().as_millis()
);

tokio::time::sleep(std::time::Duration::from_millis(150)).await;
let wait_started_at = Instant::now();
let show_wait_outcome = wait_for_windows_maximize_before_show_async(&main_window).await;
debug!(
"Main window show step completed: step=wait_for_maximize_state outcome={} duration_ms={}",
show_wait_outcome,
wait_started_at.elapsed().as_millis()
);
}

let step_started_at = Instant::now();
Expand Down Expand Up @@ -954,3 +1061,43 @@ pub async fn show_main_window(app: tauri::AppHandle) -> Result<(), String> {
);
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[cfg(target_os = "windows")]
#[test]
fn windows_maximize_show_wait_releases_when_maximized() {
assert_eq!(
windows_maximize_show_wait_action(Some(true), Duration::ZERO),
WindowsMaximizeShowWaitAction::Sleep(WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_POLL)
);
assert_eq!(
windows_maximize_show_wait_action(Some(true), WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MIN),
WindowsMaximizeShowWaitAction::Ready
);
}

#[cfg(target_os = "windows")]
#[test]
fn windows_maximize_show_wait_polls_until_max_wait() {
assert_eq!(
windows_maximize_show_wait_action(Some(false), Duration::from_millis(20)),
WindowsMaximizeShowWaitAction::Sleep(WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_POLL)
);
assert_eq!(
windows_maximize_show_wait_action(None, Duration::from_millis(148)),
WindowsMaximizeShowWaitAction::Sleep(Duration::from_millis(2))
);
}

#[cfg(target_os = "windows")]
#[test]
fn windows_maximize_show_wait_times_out_at_original_bound() {
assert_eq!(
windows_maximize_show_wait_action(Some(false), WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MAX),
WindowsMaximizeShowWaitAction::TimedOut
);
}
}
Loading
Loading