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
25 changes: 25 additions & 0 deletions appkit/ui_macos_darwin.v
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ mut:
scroll_handler ScrollFn = ScrollFn(unsafe { nil })
drop_handler DropFn = DropFn(unsafe { nil })
window_ready_fn WindowReadyFn = WindowReadyFn(unsafe { nil })
window_resize_fn WindowResizeFn = WindowResizeFn(unsafe { nil })
window NativeView
root_view NativeView
button_handler NativeView
Expand Down Expand Up @@ -389,6 +390,29 @@ fn fire_window_ready() {
st.window_ready_fn(st.window)
}

// on_window_resize registers a handler called with the content size in
// points (AppKit logical units, which differ from pixels on Retina displays)
// whenever the window is resized, so an embedder can size its child view to
// match.
pub fn on_window_resize(handler WindowResizeFn) {
mut st := state()
st.window_resize_fn = handler
}

// fire_window_resize notifies the embedder registered through
// on_window_resize, if any.
fn fire_window_resize() {
st := state()
if voidptr(st.window_resize_fn) == unsafe { nil } {
return
}
if native_is_nil(st.root_view) {
return
}
b := native_bounds(st.root_view)
st.window_resize_fn(int(b.width), int(b.height))
}

// set_window_title updates the current AppKit window title.
pub fn set_window_title(title string) {
st := state()
Expand Down Expand Up @@ -3924,6 +3948,7 @@ fn scroll_view_move(scroll NativeView, horizontal bool, distance f64) {
@[export: 'ui2_window_did_resize']
fn ui2_window_did_resize(_self voidptr, _cmd voidptr, _notification voidptr) {
refresh()
fire_window_resize()
}

@[export: 'ui2_window_key_down']
Expand Down
80 changes: 80 additions & 0 deletions embed_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,69 @@ $if windows && !ui2_custom_rendering ? {
windows_fire_window_ready()
assert native_window_handle() == previous_window
}

fn window_resize_test_handler(width int, height int) {
// Report through the state so the test needs no globals.
mut st := windows_state()
st.scroll_positions['resize_test_w'] = width
st.scroll_positions['resize_test_h'] = height
}

// The resize hook reports the size it is handed, and is a silent no-op when
// no handler is registered.
fn test_windows_window_resize_hook_reports_client_size() {
mut st := windows_state()
previous_handler := st.window_resize_fn
defer {
st.window_resize_fn = previous_handler
st.scroll_positions.delete('resize_test_w')
st.scroll_positions.delete('resize_test_h')
}
st.scroll_positions.delete('resize_test_w')
st.scroll_positions.delete('resize_test_h')
on_window_resize(window_resize_test_handler)
windows_fire_window_resize(800, 600)
assert st.scroll_positions['resize_test_w'] or { -1 } == 800
assert st.scroll_positions['resize_test_h'] or { -1 } == 600
on_window_resize(WindowResizeFn(unsafe { nil }))
windows_fire_window_resize(1, 2)
assert st.scroll_positions['resize_test_w'] or { -1 } == 800
assert st.scroll_positions['resize_test_h'] or { -1 } == 600
}

// End-to-end wiring: a real WM_SIZE delivered to the window proc reaches
// the registered handler with the window's actual client size. Calling
// windows_fire_window_resize directly would not prove the WM_SIZE branch is
// wired up.
fn test_windows_real_wm_size_reaches_the_resize_hook() {
mut st := windows_state()
previous_root := st.root
previous_handler := st.window_resize_fn
defer {
st.root = previous_root
st.window_resize_fn = previous_handler
st.scroll_positions.delete('resize_test_w')
st.scroll_positions.delete('resize_test_h')
}
assert C.ui2_win_register_classes() != 0
title := 'resize test'.to_wide()
root := C.ui2_win_create_main_window(title, 320, 200)
unsafe {
free(title)
}
assert root != unsafe { nil }
defer {
C.ui2_win_destroy(root)
}
st.root = root
on_window_resize(window_resize_test_handler)
st.scroll_positions.delete('resize_test_w')
st.scroll_positions.delete('resize_test_h')
ui2_windows_window_proc(root, win_wm_size, 0, 0)
assert st.scroll_positions['resize_test_w'] or { -1 } == C.ui2_win_client_width(root)
assert st.scroll_positions['resize_test_h'] or { -1 } == C.ui2_win_client_height(root)
assert (st.scroll_positions['resize_test_w'] or { -1 }) > 0
}
}

$if ui2_custom_rendering ? {
Expand Down Expand Up @@ -100,3 +163,20 @@ $if ui2_custom_rendering ? {
assert native_window_handle() == previous_window
}
}

$if ui2_custom_rendering ? {
fn window_resize_custom_test_handler(_width int, _height int) {}

// The gg backends have no native child to size, so the hook is a
// documented no-op that still accepts a registration.
fn test_custom_window_resize_hook_is_a_documented_noop() {
previous_handler := g_window_resize_handler
defer {
g_window_resize_handler = previous_handler
}
on_window_resize(window_resize_custom_test_handler)
assert voidptr(g_window_resize_handler) != unsafe { nil }
on_window_resize(WindowResizeFn(unsafe { nil }))
assert voidptr(g_window_resize_handler) == unsafe { nil }
}
}
14 changes: 14 additions & 0 deletions ui/ui.v
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,20 @@ pub type WheelFn = fn (dx f64, dy f64)
// Linux.
pub type WindowReadyFn = fn (handle voidptr)

// WindowResizeFn receives the main window's client size whenever it changes,
// so an embedder can resize its child (e.g. a webview) to match. ui2 never
// manages the child itself. The hook does not fire for the initial window
// size: create and size the child in the on_window_ready callback instead.
//
// - Windows: client size in physical pixels. The hook also fires with 0x0
// while the window is minimized, then with the real size again on
// restore.
// - macOS: content size in AppKit points, not pixels; the two differ on
// Retina displays.
// - Immediate renderer (Linux, Android, and the custom renderer on every
// platform): never fires, because there is no native child to size.
pub type WindowResizeFn = fn (width int, height int)

pub struct DropEvent {
pub:
paths []string
Expand Down
9 changes: 9 additions & 0 deletions ui/ui_immediate.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ $if (android || linux || ((macos || windows) && ui2_custom_rendering ?)) && !ui2
__global g_scroll_handler = ScrollFn(unsafe { nil })
__global g_drop_handler = DropFn(unsafe { nil })
__global g_window_ready_handler = WindowReadyFn(unsafe { nil })
__global g_window_resize_handler = WindowResizeFn(unsafe { nil })
__global g_key_consumed = false
__global g_gg_app = &GgApp{}
__global g_refresh_mode = RefreshMode.on_demand
Expand Down Expand Up @@ -570,6 +571,14 @@ $if (android || linux || ((macos || windows) && ui2_custom_rendering ?)) && !ui2
g_window_ready_handler(unsafe { nil })
}

// on_window_resize only stores the handler: a documented no-op. The
// immediate renderer backends (Linux, Android, and the custom renderer on
// every platform) have no native child to resize — but accepting the
// registration keeps embedder code uniform across backends.
pub fn on_window_resize(handler WindowResizeFn) {
g_window_resize_handler = handler
}

pub fn text(id string) string {
// State maps store owned strings so that replacing their values can
// release the previous allocation. Callers, in particular QML bindings,
Expand Down
27 changes: 25 additions & 2 deletions windows/ui_windows.v
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ mut:
scroll_handler ScrollFn = ScrollFn(unsafe { nil })
drop_handler DropFn = DropFn(unsafe { nil })
window_ready_fn WindowReadyFn = WindowReadyFn(unsafe { nil })
window_resize_fn WindowResizeFn = WindowResizeFn(unsafe { nil })
root voidptr
nodes map[string]voidptr
node_kinds map[string]Kind
Expand Down Expand Up @@ -846,6 +847,25 @@ fn windows_fire_window_ready() {
st.window_ready_fn(st.root)
}

// on_window_resize registers a handler called with the client size in
// physical pixels whenever the main window is resized, so an embedder can
// size its child window (e.g. a webview) to match. A minimized window
// reports 0x0; the real size follows on restore.
pub fn on_window_resize(handler WindowResizeFn) {
mut st := windows_state()
st.window_resize_fn = handler
}

// windows_fire_window_resize notifies the embedder registered through
// on_window_resize, if any.
fn windows_fire_window_resize(width int, height int) {
st := windows_state()
if voidptr(st.window_resize_fn) == unsafe { nil } {
return
}
st.window_resize_fn(width, height)
}

// set_window_title updates the current Win32 window title.
pub fn set_window_title(title string) {
st := windows_state()
Expand Down Expand Up @@ -2309,8 +2329,11 @@ fn ui2_windows_window_proc(hwnd voidptr, message u32, wparam usize, lparam isize
return 0
}
win_wm_size {
if hwnd == st.root && !st.rendering {
refresh()
if hwnd == st.root {
if !st.rendering {
refresh()
}
windows_fire_window_resize(C.ui2_win_client_width(hwnd), C.ui2_win_client_height(hwnd))
}
return 0
}
Expand Down
Loading