fix(electrobun): wrap WebView in GtkScrolledWindow for resize-in
Codex review #2 found: set_size_request only controls minimum, not natural/preferred size. WebView still reports content size as natural, causing rubber-band effect during shrink. Fix: reparent WebKitWebView into GtkScrolledWindow via FFI with propagate-natural-width/height=FALSE and min-content=1x1. This decouples WebView content size from window size negotiation. Also reverted to native begin_resize_drag (WM handles everything).
This commit is contained in:
parent
d1583f8ce4
commit
300bd30ca3
2 changed files with 132 additions and 2 deletions
|
|
@ -66,6 +66,62 @@ function getGtk() {
|
||||||
args: [FFIType.ptr],
|
args: [FFIType.ptr],
|
||||||
returns: FFIType.ptr, // GList*
|
returns: FFIType.ptr, // GList*
|
||||||
},
|
},
|
||||||
|
// GtkScrolledWindow — wraps WebView to decouple natural size
|
||||||
|
gtk_scrolled_window_new: {
|
||||||
|
args: [FFIType.ptr, FFIType.ptr], // hadjustment, vadjustment (both null)
|
||||||
|
returns: FFIType.ptr,
|
||||||
|
},
|
||||||
|
gtk_scrolled_window_set_policy: {
|
||||||
|
args: [FFIType.ptr, FFIType.i32, FFIType.i32], // hscrollbar_policy, vscrollbar_policy
|
||||||
|
returns: FFIType.void,
|
||||||
|
},
|
||||||
|
gtk_scrolled_window_set_propagate_natural_width: {
|
||||||
|
args: [FFIType.ptr, FFIType.bool],
|
||||||
|
returns: FFIType.void,
|
||||||
|
},
|
||||||
|
gtk_scrolled_window_set_propagate_natural_height: {
|
||||||
|
args: [FFIType.ptr, FFIType.bool],
|
||||||
|
returns: FFIType.void,
|
||||||
|
},
|
||||||
|
gtk_scrolled_window_set_min_content_width: {
|
||||||
|
args: [FFIType.ptr, FFIType.i32],
|
||||||
|
returns: FFIType.void,
|
||||||
|
},
|
||||||
|
gtk_scrolled_window_set_min_content_height: {
|
||||||
|
args: [FFIType.ptr, FFIType.i32],
|
||||||
|
returns: FFIType.void,
|
||||||
|
},
|
||||||
|
// Reparenting
|
||||||
|
gtk_container_remove: {
|
||||||
|
args: [FFIType.ptr, FFIType.ptr], // container, widget
|
||||||
|
returns: FFIType.void,
|
||||||
|
},
|
||||||
|
gtk_container_add: {
|
||||||
|
args: [FFIType.ptr, FFIType.ptr], // container, widget
|
||||||
|
returns: FFIType.void,
|
||||||
|
},
|
||||||
|
gtk_widget_show_all: {
|
||||||
|
args: [FFIType.ptr],
|
||||||
|
returns: FFIType.void,
|
||||||
|
},
|
||||||
|
// Expand flags
|
||||||
|
gtk_widget_set_hexpand: {
|
||||||
|
args: [FFIType.ptr, FFIType.bool],
|
||||||
|
returns: FFIType.void,
|
||||||
|
},
|
||||||
|
gtk_widget_set_vexpand: {
|
||||||
|
args: [FFIType.ptr, FFIType.bool],
|
||||||
|
returns: FFIType.void,
|
||||||
|
},
|
||||||
|
// Reference counting (prevent widget destruction during reparent)
|
||||||
|
g_object_ref: {
|
||||||
|
args: [FFIType.ptr],
|
||||||
|
returns: FFIType.ptr,
|
||||||
|
},
|
||||||
|
g_object_unref: {
|
||||||
|
args: [FFIType.ptr],
|
||||||
|
returns: FFIType.void,
|
||||||
|
},
|
||||||
// GList traversal
|
// GList traversal
|
||||||
g_list_length: {
|
g_list_length: {
|
||||||
args: [FFIType.ptr],
|
args: [FFIType.ptr],
|
||||||
|
|
@ -233,6 +289,75 @@ export function gtkSetFrame(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap the WebKitWebView inside a GtkScrolledWindow to decouple its
|
||||||
|
* natural/preferred size from the window's size negotiation.
|
||||||
|
* This prevents the "rubber band" effect during resize-in.
|
||||||
|
*
|
||||||
|
* Codex review: GtkScrolledWindow has propagate-natural-width/height = FALSE
|
||||||
|
* by default, which stops the child's natural size from reaching the window.
|
||||||
|
*/
|
||||||
|
export function wrapWebViewInScrolledWindow(windowPtr: number | bigint): boolean {
|
||||||
|
const lib = getGtk();
|
||||||
|
if (!lib) return false;
|
||||||
|
try {
|
||||||
|
// Get the window's child (container holding the WebView)
|
||||||
|
const container = lib.symbols.gtk_bin_get_child(windowPtr as any);
|
||||||
|
if (!container) { console.log("[gtk-window] No child container found"); return false; }
|
||||||
|
|
||||||
|
// Get the WebView from the container
|
||||||
|
const webview = lib.symbols.gtk_bin_get_child(container as any);
|
||||||
|
if (!webview) { console.log("[gtk-window] No WebView found in container"); return false; }
|
||||||
|
|
||||||
|
console.log("[gtk-window] Wrapping WebView in GtkScrolledWindow");
|
||||||
|
|
||||||
|
// Ref the WebView so it's not destroyed when removed from container
|
||||||
|
lib.symbols.g_object_ref(webview as any);
|
||||||
|
|
||||||
|
// Remove WebView from its current parent
|
||||||
|
lib.symbols.gtk_container_remove(container as any, webview as any);
|
||||||
|
|
||||||
|
// Create a GtkScrolledWindow
|
||||||
|
const scrolled = lib.symbols.gtk_scrolled_window_new(null, null);
|
||||||
|
if (!scrolled) {
|
||||||
|
// Put WebView back if scrolled window creation failed
|
||||||
|
lib.symbols.gtk_container_add(container as any, webview as any);
|
||||||
|
lib.symbols.g_object_unref(webview as any);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure: no scrollbar policy (automatic), don't propagate natural size
|
||||||
|
const GTK_POLICY_AUTOMATIC = 1;
|
||||||
|
const GTK_POLICY_NEVER = 2;
|
||||||
|
lib.symbols.gtk_scrolled_window_set_policy(scrolled as any, GTK_POLICY_NEVER, GTK_POLICY_NEVER);
|
||||||
|
lib.symbols.gtk_scrolled_window_set_propagate_natural_width(scrolled as any, false);
|
||||||
|
lib.symbols.gtk_scrolled_window_set_propagate_natural_height(scrolled as any, false);
|
||||||
|
lib.symbols.gtk_scrolled_window_set_min_content_width(scrolled as any, 1);
|
||||||
|
lib.symbols.gtk_scrolled_window_set_min_content_height(scrolled as any, 1);
|
||||||
|
|
||||||
|
// Set expand flags
|
||||||
|
lib.symbols.gtk_widget_set_hexpand(scrolled as any, true);
|
||||||
|
lib.symbols.gtk_widget_set_vexpand(scrolled as any, true);
|
||||||
|
lib.symbols.gtk_widget_set_size_request(scrolled as any, 1, 1);
|
||||||
|
|
||||||
|
// Add WebView to ScrolledWindow
|
||||||
|
lib.symbols.gtk_container_add(scrolled as any, webview as any);
|
||||||
|
lib.symbols.g_object_unref(webview as any); // balance the ref
|
||||||
|
|
||||||
|
// Add ScrolledWindow to the original container
|
||||||
|
lib.symbols.gtk_container_add(container as any, scrolled as any);
|
||||||
|
|
||||||
|
// Show everything
|
||||||
|
lib.symbols.gtk_widget_show_all(scrolled as any);
|
||||||
|
|
||||||
|
console.log("[gtk-window] WebView successfully wrapped in GtkScrolledWindow");
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[gtk-window] wrapWebViewInScrolledWindow failed:", err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Edge string → GDK_EDGE mapping
|
// Edge string → GDK_EDGE mapping
|
||||||
const EDGE_MAP: Record<string, GdkEdge> = {
|
const EDGE_MAP: Record<string, GdkEdge> = {
|
||||||
n: GDK_EDGE.N, s: GDK_EDGE.S, e: GDK_EDGE.E, w: GDK_EDGE.W,
|
n: GDK_EDGE.N, s: GDK_EDGE.S, e: GDK_EDGE.E, w: GDK_EDGE.W,
|
||||||
|
|
|
||||||
|
|
@ -233,10 +233,15 @@ mainWindow = new BrowserWindow({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure GTK window is resizable (titleBarStyle:"hidden" may clear the flag)
|
// Ensure GTK window is resizable and wrap WebView for proper resize-in
|
||||||
{
|
{
|
||||||
const { ensureResizable } = require("./gtk-window.ts");
|
const { ensureResizable, wrapWebViewInScrolledWindow } = require("./gtk-window.ts");
|
||||||
ensureResizable((mainWindow as any).ptr);
|
ensureResizable((mainWindow as any).ptr);
|
||||||
|
// Wrap WebView in GtkScrolledWindow to decouple natural size from window size
|
||||||
|
// (prevents rubber-band effect when shrinking)
|
||||||
|
setTimeout(() => {
|
||||||
|
wrapWebViewInScrolledWindow((mainWindow as any).ptr);
|
||||||
|
}, 2000); // Delay to ensure WebView is fully initialized
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent GTK's false Ctrl+click detection from closing the window on initial load.
|
// Prevent GTK's false Ctrl+click detection from closing the window on initial load.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue