asdf98 commited on
Commit
07eb768
·
verified ·
1 Parent(s): 42d26b9

fix: layout.rs - use offscreen parking pattern instead of unreliable hide/show on Windows WebView2

Browse files
Files changed (1) hide show
  1. src-tauri/src/browser/layout.rs +75 -49
src-tauri/src/browser/layout.rs CHANGED
@@ -1,49 +1,75 @@
1
- use tauri::{AppHandle, LogicalPosition, LogicalSize, Manager};
2
-
3
- use super::tab_manager::{tab_label, ViewportLayout};
4
- use crate::state::AppState;
5
-
6
- const HIDDEN_X: f64 = -32000.0;
7
- const HIDDEN_Y: f64 = -32000.0;
8
-
9
- /// Calculate child WebView bounds from measured React host rect.
10
- /// React sends getBoundingClientRect() values in logical/CSS pixels.
11
- pub fn bounds(layout: &ViewportLayout) -> (f64, f64, f64, f64) {
12
- let x = layout.x.max(0.0);
13
- let y = layout.y.max(0.0);
14
- let w = layout.width.max(1.0);
15
- let h = layout.height.max(1.0);
16
- (x, y, w, h)
17
- }
18
-
19
- pub fn show_tab(app: &AppHandle, tab_id: &str, layout: &ViewportLayout) -> Result<(), String> {
20
- let label = tab_label(app, tab_id)?;
21
- let (x, y, w, h) = bounds(layout);
22
- if let Some(webview) = app.get_webview(&label) {
23
- webview.set_position(LogicalPosition::new(x, y)).map_err(|e| e.to_string())?;
24
- webview.set_size(LogicalSize::new(w, h)).map_err(|e| e.to_string())?;
25
- webview.show().map_err(|e| e.to_string())?;
26
- webview.set_focus().map_err(|e| e.to_string())?;
27
- }
28
- Ok(())
29
- }
30
-
31
- pub fn hide_tab(app: &AppHandle, tab_id: &str) -> Result<(), String> {
32
- let label = tab_label(app, tab_id)?;
33
- if let Some(webview) = app.get_webview(&label) {
34
- let _ = webview.hide();
35
- webview.set_position(LogicalPosition::new(HIDDEN_X, HIDDEN_Y)).map_err(|e| e.to_string())?;
36
- webview.set_size(LogicalSize::new(1.0, 1.0)).map_err(|e| e.to_string())?;
37
- }
38
- Ok(())
39
- }
40
-
41
- pub fn resize_active(app: &AppHandle, layout: &ViewportLayout) -> Result<(), String> {
42
- let active = {
43
- let state = app.state::<AppState>();
44
- let tabs = state.tabs.lock().map_err(|_| "lock")?;
45
- tabs.active.clone()
46
- };
47
- if let Some(active_id) = active { show_tab(app, &active_id, layout)?; }
48
- Ok(())
49
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use tauri::{AppHandle, LogicalPosition, LogicalSize, Manager, PhysicalPosition, PhysicalSize};
2
+
3
+ use super::tab_manager::{tab_label, ViewportLayout};
4
+ use crate::state::AppState;
5
+
6
+ /// Hidden position far offscreen. WebView2 stays "visible" (avoids deferred init bug)
7
+ /// but is not renderable. This is the correct pattern for WebView2 on Windows.
8
+ const OFFSCREEN_X: i32 = -32000;
9
+ const OFFSCREEN_Y: i32 = -32000;
10
+
11
+ /// Calculate child WebView bounds from measured React host rect.
12
+ /// React sends getBoundingClientRect() values in CSS/logical pixels.
13
+ pub fn bounds(layout: &ViewportLayout) -> (f64, f64, f64, f64) {
14
+ let x = layout.x.max(0.0);
15
+ let y = layout.y.max(0.0);
16
+ let w = layout.width.max(1.0);
17
+ let h = layout.height.max(1.0);
18
+ (x, y, w, h)
19
+ }
20
+
21
+ /// Show a tab webview at the specified layout position.
22
+ /// Uses PhysicalPosition with scale factor for correct HiDPI positioning.
23
+ pub fn show_tab(app: &AppHandle, tab_id: &str, layout: &ViewportLayout) -> Result<(), String> {
24
+ let label = tab_label(app, tab_id)?;
25
+ let (x, y, w, h) = bounds(layout);
26
+
27
+ if let Some(webview) = app.get_webview(&label) {
28
+ // Get scale factor for coordinate conversion
29
+ let scale = app.get_window("main")
30
+ .and_then(|win| win.scale_factor().ok())
31
+ .unwrap_or(1.0);
32
+
33
+ // Convert CSS logical pixels to physical pixels
34
+ let px = (x * scale) as i32;
35
+ let py = (y * scale) as i32;
36
+ let pw = (w * scale).max(1.0) as u32;
37
+ let ph = (h * scale).max(1.0) as u32;
38
+
39
+ // CRITICAL ORDER: position + size FIRST, then show
40
+ // This avoids the WebView2 "bounds before visible" requirement
41
+ webview.set_position(PhysicalPosition::new(px, py)).map_err(|e| e.to_string())?;
42
+ webview.set_size(PhysicalSize::new(pw, ph)).map_err(|e| e.to_string())?;
43
+
44
+ // show() is needed on first display after add_child
45
+ // Subsequent calls are safe (idempotent)
46
+ let _ = webview.show();
47
+ let _ = webview.set_focus();
48
+ }
49
+ Ok(())
50
+ }
51
+
52
+ /// Hide a tab webview by parking it offscreen.
53
+ /// We do NOT call webview.hide() because WebView2's hide/show is unreliable.
54
+ /// Instead we move it far offscreen and shrink to 1x1. The webview stays "alive"
55
+ /// and continues processing JS (beneficial for maintaining session state).
56
+ pub fn hide_tab(app: &AppHandle, tab_id: &str) -> Result<(), String> {
57
+ let label = tab_label(app, tab_id)?;
58
+ if let Some(webview) = app.get_webview(&label) {
59
+ // Park offscreen — don't call hide()
60
+ webview.set_position(PhysicalPosition::new(OFFSCREEN_X, OFFSCREEN_Y)).map_err(|e| e.to_string())?;
61
+ webview.set_size(PhysicalSize::new(1u32, 1u32)).map_err(|e| e.to_string())?;
62
+ }
63
+ Ok(())
64
+ }
65
+
66
+ /// Resize the active tab to match current layout.
67
+ pub fn resize_active(app: &AppHandle, layout: &ViewportLayout) -> Result<(), String> {
68
+ let active = {
69
+ let state = app.state::<AppState>();
70
+ let tabs = state.tabs.lock().map_err(|_| "lock")?;
71
+ tabs.active.clone()
72
+ };
73
+ if let Some(active_id) = active { show_tab(app, &active_id, layout)?; }
74
+ Ok(())
75
+ }