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
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,17 @@ jobs:
shell: bash
run: bash scripts/ci-retry.sh zig build check-web

# Fizzy on dvui's own SDL3 backend (`-Dnative-backend=sdl3`), where any dvui app on the stock
# backend stands: the platform pieces compile without fizzy's backend and lose what needs it
# (floats' windows, the window skin). It rotted unnoticed while nothing built it. macOS
# only: the platform reaches furthest into the backend there, and that is where it broke.
- name: Fetch dependencies for dvui's SDL3 backend (with retries)
if: matrix.name == 'macOS'
run: bash scripts/ci-retry.sh zig build --fetch -Dnative-backend=sdl3
- name: Compile on dvui's SDL3 backend
if: matrix.name == 'macOS'
run: bash scripts/ci-retry.sh zig build -Dnative-backend=sdl3 -Dno-emit --summary all

# Fizzy consumed from outside: fizzyedit/example-app (fizzy as a library, in every shape)
# and fizzyedit/example-plugin (the plugin template), built against this tree fetched as a
# URL dependency would be. The only checks that catch a path resolving against the
Expand Down
4 changes: 4 additions & 0 deletions backend/src/SDLBackend.zig
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ pub const Health = @import("Health.zig");
/// What an app gets from its window beyond drawing into it: native dialogs, files the OS hands
/// over, trackpad gestures, the window's chrome and state (`platform/`).
pub const platform = @import("platform");
// The platform knows this backend by that declaration (`platform.own_backend`).
comptime {
std.debug.assert(platform.own_backend);
}

/// SDL3 only. Kept as a constant so the event code below stays a verbatim copy of dvui's
/// `sdl.zig` (its `if (sdl3)` branches are the ones compiled).
Expand Down
17 changes: 15 additions & 2 deletions backend/src/platform/macos/window_monitor.m
Original file line number Diff line number Diff line change
Expand Up @@ -1206,11 +1206,14 @@ static void install_constrain_override(NSWindow *window) {
* AppKit keeps the content rect across each change, so the window grew by its title bar and shrank
* back: kept at will-enter it came back from full screen a title bar taller each time, and at
* did-exit it jumped up a title bar and back down in a frame. A window the monitor follows keeps its
* content under the title bar through any style it is given. */
* content under the title bar through any style it is given, and so does one that asked for that
* alone (`fizzy_macos_window_keep_full_size_content`). */
static IMP g_nswindow_style_mask_imp = NULL;
static char g_keeps_full_size_content;

static void fizzy_set_style_mask(id self, SEL _cmd, NSWindowStyleMask mask) {
if (monitor_of((__bridge void *)self)) mask |= NSWindowStyleMaskFullSizeContentView;
if (monitor_of((__bridge void *)self) || objc_getAssociatedObject(self, &g_keeps_full_size_content))
mask |= NSWindowStyleMaskFullSizeContentView;
((void (*)(id, SEL, NSWindowStyleMask))g_nswindow_style_mask_imp)(self, _cmd, mask);
}

Expand All @@ -1231,6 +1234,16 @@ static void install_style_mask_override(NSWindow *window) {
}
}

/* `nswindow` keeps its content under the title bar through every style SDL gives it, as a followed
* window does, without the rest of the monitor: on a backend whose frames the monitor cannot step
* (`macos_monitor.zig`'s `install`), the window's chrome is still fizzy's. */
void fizzy_macos_window_keep_full_size_content(void *nswindow) {
if (!nswindow) return;
NSWindow *window = (__bridge NSWindow *)nswindow;
objc_setAssociatedObject(window, &g_keeps_full_size_content, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
install_style_mask_override(window);
}

/* Keep `token`, an observer `m` added, to remove when the window is forgotten. */
static void keep_observer(WindowMonitor *m, id token) {
if (!m || !token || m->observer_count >= (int)(sizeof(m->observers) / sizeof(m->observers[0]))) return;
Expand Down
10 changes: 10 additions & 0 deletions backend/src/platform/macos_monitor.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const dvui = @import("dvui");
const Backend = @import("backend");
const c = Backend.c;
const window_layout = @import("window_layout.zig");
const own_backend = @import("root.zig").own_backend;

const log = std.log.scoped(.macos_monitor);

Expand Down Expand Up @@ -45,6 +46,7 @@ extern fn fizzy_macos_window_set_frame(cocoa_window: ?*anyopaque, x: f64, y: f64
extern fn fizzy_macos_copy_screen_frames(out: [*]f64, max: c_int) c_int;
extern fn fizzy_macos_window_sync_content_views(cocoa_window: ?*anyopaque) void;
extern fn fizzy_macos_window_install_resize_observer(cocoa_window: ?*anyopaque) void;
extern fn fizzy_macos_window_keep_full_size_content(cocoa_window: ?*anyopaque) void;
extern fn fizzy_macos_window_sdl_draws_live_resize(cocoa_window: ?*anyopaque) c_int;
extern fn fizzy_macos_window_space_step(cocoa_window: ?*anyopaque) void;
extern fn fizzy_macos_window_space_moving(cocoa_window: ?*anyopaque) c_int;
Expand Down Expand Up @@ -369,10 +371,18 @@ export fn fizzy_macos_origin_nudged(cap_x: f64, cap_y: f64, cur_x: f64, cur_y: f
/// Follow `win` through Spaces, zooms and live resizes: from here on AppKit's live sizes reach SDL
/// before each frame (`Backend.SDLBackend.begin_hook`) and through every animation. Call once the
/// window's chrome is in place, before it is shown.
///
/// This package's backend only. The monitor takes full screen and zooms over from AppKit and draws
/// their steps itself, which needs that backend's frame hooks; on dvui's its steps were drawn out of
/// time with the window. There the window goes in and out as AppKit and SDL take it, every query
/// here answers from AppKit's own state, and the window only keeps its content under the title bar
/// through SDL's styles, for fizzy's chrome (`styleTitled`): without that it came back from each
/// round trip a title bar shorter.
pub fn install(win: *dvui.Window) void {
if (comptime builtin.os.tag != .macos) return;
const back = win.backend.impl;
const cocoa = cocoaWindowOf(back.window) orelse return;
if (comptime !own_backend) return fizzy_macos_window_keep_full_size_content(cocoa);
macos_monitor_window = back.window;
back.begin_hook = macosAppPreBeginSync;
back.present_hook = macosAppPresented;
Expand Down
6 changes: 6 additions & 0 deletions backend/src/platform/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ pub const macos_monitor = @import("macos_monitor.zig");
pub const window_layout = @import("window_layout.zig");
pub const menu = @import("menu.zig");

/// Whether the app is on this package's backend (`SDLBackend`, which imports these pieces) rather
/// than dvui's own SDL3 backend. What they reach into beyond SDL's window — floats' windows, the
/// window skin, the present hook — is there only; on dvui's, those parts do nothing. One flag, not
/// a `@hasField` per field: a field renamed on this backend then fails its build, not goes quiet.
pub const own_backend = @hasDecl(@import("backend"), "platform");

comptime {
// The menu bar's Objective-C (`macos/menu_target.m`) calls these back, and it is compiled into
// every app on macOS (`addPlatformObjC`), so they are kept whether or not the app ever reaches
Expand Down
19 changes: 11 additions & 8 deletions backend/src/platform/window.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const objc = @import("objc");
const titlebar = @import("titlebar.zig");
const win32_titlebar = @import("win32_titlebar.zig");
const linux_titlebar = @import("linux_titlebar.zig");
const own_backend = @import("root.zig").own_backend;

const NSPoint = extern struct { x: f64, y: f64 };
const NSSize = extern struct { width: f64, height: f64 };
Expand Down Expand Up @@ -299,12 +300,12 @@ pub fn setStyle(win: *dvui.Window) void {
// Not a menu's or a dialog's: dressed once as it opens (`fizzy_macos_viewport_menu`), it
// never goes full screen, and this made it a full-screen primary window over the
// auxiliary one it was opened as, every frame.
for (&win.backend.impl.viewport_slots) |*slot| {
if (comptime own_backend) for (&win.backend.impl.viewport_slots) |*slot| {
const vp = if (slot.*) |*v| v else continue;
if (vp.passive or vp.menu) continue;
const ns = c.SDL_GetPointerProperty(c.SDL_GetWindowProperties(vp.window), c.SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, null) orelse continue;
styleTitled(ns);
}
};
} else if (builtin.os.tag == .windows) {
win32_titlebar.applyChrome(win);
} else if (builtin.os.tag == .linux) {
Expand Down Expand Up @@ -422,12 +423,14 @@ pub fn setBackground(win: *dvui.Window, color: dvui.Color) void {
skin(raw_ptr.?, color, dvui.themeGet().dark);
// Every float's own window too, and each opened from now on (`SDLBackend.viewportGlass`):
// side by side, and each in a Space of its own, they are one app's windows.
win.backend.impl.window_skin = color;
for (&win.backend.impl.viewport_slots) |*slot| {
const vp = if (slot.*) |*v| v else continue;
if (vp.passive) continue;
const ns = c.SDL_GetPointerProperty(c.SDL_GetWindowProperties(vp.window), c.SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, null) orelse continue;
skin(ns, color, dvui.themeGet().dark);
if (comptime own_backend) {
win.backend.impl.window_skin = color;
for (&win.backend.impl.viewport_slots) |*slot| {
const vp = if (slot.*) |*v| v else continue;
if (vp.passive) continue;
const ns = c.SDL_GetPointerProperty(c.SDL_GetWindowProperties(vp.window), c.SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, null) orelse continue;
skin(ns, color, dvui.themeGet().dark);
}
}

// SDL3 currently removes the shadow when the transparency flag for the window is set. This brings it back.
Expand Down
Loading