Skip to main content

truce_gui_utils/
lib.rs

1//! Shared host-side platform helpers for truce GUI backends that
2//! embed a wgpu-backed (or CALayer-backed) child view into a
3//! DAW-provided parent window.
4//!
5//! Currently macOS-only: the helpers pin an embedded `NSView`'s top
6//! edge to its superview's top edge across host-driven resizes.
7//! Linux/Windows hosts manage child-window positioning natively, so
8//! these helpers are no-ops there.
9
10#![allow(clippy::module_name_repetitions)]
11
12#[cfg(target_os = "macos")]
13#[repr(C)]
14#[derive(Clone, Copy)]
15struct NsPoint {
16    x: f64,
17    y: f64,
18}
19
20#[cfg(target_os = "macos")]
21#[repr(C)]
22#[derive(Clone, Copy)]
23struct NsSize {
24    width: f64,
25    height: f64,
26}
27
28#[cfg(target_os = "macos")]
29#[repr(C)]
30#[derive(Clone, Copy)]
31struct NsRect {
32    origin: NsPoint,
33    size: NsSize,
34}
35
36/// Re-anchor the editor's `NSView` to the **top** of its superview
37/// in unflipped Cocoa coordinates.
38///
39/// The CLAP / LV2 / AU shims set the child's autoresize mask to
40/// `NSViewMinYMargin | NSViewMaxXMargin` so the parent-resize
41/// cascade keeps the child pinned. `AppKit`'s autoresize math only
42/// runs when the *parent* resizes, though - resizing the *child*
43/// (via `baseview::Window::resize` / `setFrameSize:`) leaves the
44/// origin alone, which silently drifts the child off-anchor: a
45/// taller child grows *down* from its existing origin instead of
46/// staying anchored to the parent's top. Visually that looks like
47/// the editor's header / first row disappearing above the visible
48/// plug-in area.
49///
50/// Call this on macOS each frame (e.g. from `WindowHandler::on_frame`)
51/// so the child's origin tracks its size. No-op on non-macOS.
52#[cfg(target_os = "macos")]
53pub fn reanchor_to_superview_top(handle: raw_window_handle::RawWindowHandle) {
54    use objc::{msg_send, sel, sel_impl};
55
56    let view_ptr = match handle {
57        raw_window_handle::RawWindowHandle::AppKit(h) => h.ns_view,
58        _ => return,
59    };
60    if view_ptr.is_null() {
61        return;
62    }
63
64    unsafe {
65        let view = view_ptr.cast::<objc::runtime::Object>();
66        let superview: *mut objc::runtime::Object = msg_send![view, superview];
67        if superview.is_null() {
68            return;
69        }
70        let parent_frame: NsRect = msg_send![superview, frame];
71        let child_frame: NsRect = msg_send![view, frame];
72        let new_y = parent_frame.size.height - child_frame.size.height;
73        if (new_y - child_frame.origin.y).abs() < f64::EPSILON {
74            return;
75        }
76        let new_origin = NsPoint {
77            x: child_frame.origin.x,
78            y: new_y,
79        };
80        let _: () = msg_send![view, setFrameOrigin: new_origin];
81    }
82}
83
84#[cfg(not(target_os = "macos"))]
85pub fn reanchor_to_superview_top(_handle: raw_window_handle::RawWindowHandle) {}
86
87/// Walk every direct subview of the host-provided parent `NSView`
88/// and pin its top edge to the parent's top in unflipped Cocoa
89/// coordinates. Used by GUI backends that don't expose their own
90/// child `Window` per-frame (vizia) - they hand us the parent
91/// handle they got at `Editor::open` time and we walk the subview
92/// tree the host installed our backend's view into.
93#[cfg(target_os = "macos")]
94pub fn reanchor_all_children_to_top(parent: *mut std::ffi::c_void) {
95    use objc::runtime::Object;
96    use objc::{msg_send, sel, sel_impl};
97
98    if parent.is_null() {
99        return;
100    }
101    unsafe {
102        let parent_obj = parent.cast::<Object>();
103        let parent_frame: NsRect = msg_send![parent_obj, frame];
104        let subviews: *mut Object = msg_send![parent_obj, subviews];
105        if subviews.is_null() {
106            return;
107        }
108        let count: usize = msg_send![subviews, count];
109        for i in 0..count {
110            let child: *mut Object = msg_send![subviews, objectAtIndex: i];
111            if child.is_null() {
112                continue;
113            }
114            let child_frame: NsRect = msg_send![child, frame];
115            let new_y = parent_frame.size.height - child_frame.size.height;
116            if (new_y - child_frame.origin.y).abs() < f64::EPSILON {
117                continue;
118            }
119            let new_origin = NsPoint {
120                x: child_frame.origin.x,
121                y: new_y,
122            };
123            let _: () = msg_send![child, setFrameOrigin: new_origin];
124        }
125    }
126}
127
128#[cfg(not(target_os = "macos"))]
129pub fn reanchor_all_children_to_top(_parent: *mut std::ffi::c_void) {}