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
46 changes: 34 additions & 12 deletions backend/src/SDLBackend.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,23 @@ pub fn windowAtPointer(_: *SDLBackend) ?*anyopaque {
return fizzy_macos_app_window_at_pointer();
}

/// The app's window whose content the pointer is over — below its title bar — or null: over a title
/// bar (the main window's, a window of the OS's own frame's) it is over no window's content. A
/// floating window dragged there took the window for the one to land in, and dvui held its top
/// below the title bar, away from the pointer (the user).
pub fn windowContentAtPointer(self: *SDLBackend) ?*anyopaque {
if (comptime builtin.target.os.tag != .macos) return null;
const window = fizzy_macos_app_window_at_pointer() orelse return null;
var gx: f32 = 0;
var gy: f32 = 0;
_ = c.SDL_GetGlobalMouseState(&gx, &gy);
if (window == cocoaWindow(self.window)) return if (gy < self.mainOnScreen().y) null else window;
for (&self.viewport_slots) |*slot| if (slot.*) |*vp| if (cocoaWindow(vp.window) == window and vp.native) {
return if (gy < @as(f32, @floatFromInt(vp.screen.y))) null else window;
};
return window;
}

/// `window` (`windowAtPointer`'s) in front of the app's other windows, not made key.
pub fn bringForward(_: *SDLBackend, window: *anyopaque) void {
if (comptime builtin.target.os.tag != .macos) return;
Expand Down Expand Up @@ -2026,27 +2043,32 @@ pub fn viewportLiquidGlass(_: *SDLBackend, vp: *Viewport, look: platform.window.
return platform.window.liquidGlassLook(vp.window, look);
}

/// Where `vp`'s window is now, in the main window's part of the frame: physical pixels from the
/// main window's top left, wherever either window has been moved since it opened.
/// Whether `vp`'s window — its title bar's band too — lies wholly inside the main window, with the
/// main window on the Space being shown (`viewports.mainHolds`).
pub fn viewportMainHolds(self: *SDLBackend, vp: *const Viewport) bool {
/// Whether the pointer is over the main window's content — below its title bar — on the Space being
/// shown (`os_windows`: a torn-off window let go there goes back in). By where the main window is,
/// whatever lies over it: the window being let go is.
pub fn viewportPointerOverMainContent(self: *SDLBackend) bool {
if (comptime builtin.target.os.tag == .macos) if (fizzy_macos_window_off_active_space(cocoaWindow(self.window)) != 0) return false;
var x: c_int = 0;
var y: c_int = 0;
var w: c_int = 0;
var h: c_int = 0;
_ = c.SDL_GetWindowPosition(vp.window, &x, &y);
_ = c.SDL_GetWindowSize(vp.window, &w, &h);
var gx: f32 = 0;
var gy: f32 = 0;
_ = c.SDL_GetGlobalMouseState(&gx, &gy);
var mx: c_int = 0;
var my: c_int = 0;
var mw: c_int = 0;
var mh: c_int = 0;
_ = c.SDL_GetWindowPosition(self.window, &mx, &my);
_ = c.SDL_GetWindowSize(self.window, &mw, &mh);
return x >= mx and y >= my and x + w <= mx + mw and y + h <= my + mh;
const top = self.mainOnScreen().y;
return gx >= @as(f32, @floatFromInt(mx)) and gx < @as(f32, @floatFromInt(mx + mw)) and gy >= top and gy < @as(f32, @floatFromInt(my + mh));
}

/// Whether the OS is moving `vp`'s window under a held press (`viewportOsMoveEnded` says when it
/// lets go).
pub fn viewportOsMoving(_: *SDLBackend, vp: *const Viewport) bool {
return vp.os_moving;
}

/// Where `vp`'s window is now, in the main window's part of the frame: physical pixels from the
/// main window's top left, wherever either window has been moved since it opened.
pub fn viewportInMain(self: *SDLBackend, vp: *const Viewport) viewport_map.Rect {
// Where it was last put, not SDL's idea of it: on Windows it may have been moved in one step
// behind SDL's back (`viewportPlace`), and only the app moves it.
Expand Down
142 changes: 55 additions & 87 deletions backend/src/os_windows.zig
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,15 @@ var keyed: ?dvui.Id = null;
/// OS window of its own at once — the OS's title bar and buttons under the pointer — shrunk to a
/// miniature of itself: its content laid out at its size, shown scaled, live. Back over a window of
/// the app's, the window hides and the floating window is there again. Let go over the desktop, the
/// window grows to the floating window's size (`Grow`) and is its window from then (`torn`). Moved
/// by the OS wholly back into the main window, the window shrinks into its place there, dvui's
/// header for the OS's title bar (`Merge`). One window all the way: a borderless card that grew and
/// then gave way to a window opened for it jumped as one replaced the other (the user). Only one
/// that can close (`open_flag`, `named`): a window's close clears it. dvui says nothing of a drag,
/// so it is read from what dvui shows: the floating window that has the mouse, its rect moving
/// while its size stays — moved by its header, not resized from an edge. Over another of the app's
/// windows the held pointer already takes it into that window (`SDLBackend.heldPoint`).
/// window grows to the floating window's size (`Grow`) and is its window from then (`torn`). Let go
/// by the OS's move over the main window's content, it is the floating window there again, dvui's
/// header where its title bar was, the window fading out over it (`beforeFrame`, `closing`). One
/// window all the way: a borderless card that grew and then gave way to a window opened for it
/// jumped as one replaced the other (the user). Only one that can close (`open_flag`, `named`): a
/// window's close clears it. dvui says nothing of a drag, so it is read from what dvui shows: the
/// floating window that has the mouse, its rect moving while its size stays — moved by its header,
/// not resized from an edge. Over another of the app's windows the held pointer already takes it
/// into that window (`SDLBackend.heldPoint`).
const Tear = struct {
id: dvui.Id,
/// Where on it the pointer took it, from its top left (physical, at its size).
Expand Down Expand Up @@ -141,21 +142,15 @@ const Grow = struct {
};
var grow: ?Grow = null;

/// A torn-off window moved wholly back into the main window (`beforeFrame`): its last picture
/// shrinking from the window's frame to the floating window's place, the OS's title bar's band
/// becoming dvui's header's, then the floating window there.
const Merge = struct {
id: dvui.Id,
card: *viewports.Viewport,
card_target: ?dvui.Texture.Target = null,
snap: dvui.Texture.Target,
from: dvui.Rect.Physical,
band: f32,
to: dvui.Rect.Physical,
header: f32,
start_ns: i128,
};
var merge: ?Merge = null;
/// Torn-off windows gone back into the main window (`beforeFrame`), fading out over it: the frame
/// that goes back in draws the floating window in the main window where its window was, and its
/// window, over it with its last picture, the pointer passing through it, fades out over
/// `back_ns`, its title bar into dvui's header, its content still. Closed at once, the window
/// snapped into the floating window (the user); closed before the main window had drawn it,
/// neither showed it for a frame; shrunk back through a card of its last picture, its title bar's
/// place became dvui's header's as it went and the window flashed a title.
var closing: [max_outs]?struct { viewport: *viewports.Viewport, target: ?dvui.Texture.Target, start_ns: i128 } = @splat(null);
const back_ns: i128 = 150 * std.time.ns_per_ms;

/// How long the card takes to grow into its window, or a window to shrink back into the main one,
/// and the floating window to shrink into the card as it goes past the app's windows.
Expand Down Expand Up @@ -235,17 +230,33 @@ fn physical(r: dvui.Rect, s: f32) viewports.Rect {
pub fn beforeFrame() void {
named_n = 0;
closable_n = 0;
// The windows gone back in (`closing`), fading out over the floating windows the main window
// draws under them, then closed.
for (&closing) |*c| if (c.*) |w| {
const k = ease(dvui.currentWindow().frame_time_ns - w.start_ns, back_ns);
if (k < 1) {
viewports.fade(w.viewport, 1 - k);
dvui.refresh(null, @src(), null);
continue;
}
viewports.close(w.viewport);
if (w.target) |t| t.destroyLater();
c.* = null;
};
const s = dvui.windowNaturalScale();
for (&outs) |*slot| {
const o = if (slot.*) |*o| o else continue;
// Where its window is, and its size: the OS's — moved, resized, zoomed, full screen — and
// never the other way. Not grown to fit what it holds, as dvui sizes a floating window
// while its content changes (`_auto_size`).
_ = viewports.osPlaced(o.viewport);
// Torn off, and moved by the OS wholly into the main window: back in it, where it was let
// go, as a floating window of its own again (`torn`). Wholly, so a window only overlapping
// the main one stays a window.
if (viewports.osMoveEnded(o.viewport)) |end| if (!end.resized and torn(o.id) and viewports.mainHolds(o.viewport)) {
// Moved by the OS, frames keep coming, so its let-go is seen at once: after the OS's own
// move it otherwise waited for the next event (the user: "takes more events").
if (viewports.osMoving(o.viewport)) dvui.refresh(null, @src(), null);
// Torn off, and let go by the OS's move with the pointer over the main window's content:
// back in it, where it was let go, as a floating window of its own again (`torn`). A window
// let go elsewhere — over the desktop, the main window's title bar — stays a window.
if (viewports.osMoveEnded(o.viewport)) |end| if (!end.resized and torn(o.id) and viewports.pointerOverMainContent()) {
// Its content where it was and at its size, dvui's header above it where the title bar
// was: the floating window's rect holds its header, and given only the content's, the
// content came back a header shorter (the user).
Expand All @@ -257,16 +268,18 @@ pub fn beforeFrame() void {
for (&torn_list) |*ts| if (ts.*) |tt| if (tt.id == o.id) {
ts.* = null;
};
// Shrinking into its place from its window, its last picture in a card over it
// (`Merge`); or there at once where no card can be made.
const band = viewports.titleBand();
const from: dvui.Rect.Physical = .{ .x = c.x, .y = c.y - band, .w = c.w, .h = c.h + band };
if (merge == null and o.target != null and viewports.tears()) if (viewports.openCarry(viewRect(from))) |card| {
merge = .{ .id = o.id, .card = card, .snap = o.target.?, .from = from, .band = band, .to = to, .header = header, .start_ns = dvui.currentWindow().frame_time_ns };
o.target = null;
};
viewports.close(o.viewport);
if (o.target) |t| t.destroyLater();
// Its window fading out over the floating window this frame draws (`closing`), the
// pointer through it to the floating window under it.
const free = for (&closing) |*cl| {
if (cl.* == null) break cl;
} else null;
if (free) |cl| {
viewports.held(o.viewport, true);
cl.* = .{ .viewport = o.viewport, .target = o.target, .start_ns = dvui.currentWindow().frame_time_ns };
} else {
viewports.close(o.viewport);
if (o.target) |t| t.destroyLater();
}
slot.* = null;
open_failed = false;
dvui.refresh(null, @src(), null);
Expand Down Expand Up @@ -313,13 +326,12 @@ fn viewRect(r: dvui.Rect.Physical) viewports.Rect {
}

/// The floating window being dragged this frame, if any, as a card past the app's windows (`Tear`),
/// and a card growing into its window (`Grow`) or a window shrinking back (`Merge`). After the app
/// and its window growing to its size (`Grow`). After the app
/// has drawn, so its rect and drawing are this frame's.
fn tearFrame() void {
if (!viewports.tears()) return;
const cw = dvui.currentWindow();
growFrame(cw);
mergeFrame(cw);
const held = viewports.pointerHeld();
var dragged: ?*dvui.Subwindows.Subwindow = null;
if (held) if (dvui.captureMouseGet()) |cm| for (cw.subwindows.stack.items) |*sw| {
Expand Down Expand Up @@ -367,8 +379,9 @@ fn tearFrame() void {
}
t.last = r;
// The app's window it is over in front, to show where it would land (the user): once, as it
// comes over each.
const over = viewports.windowAtPointer();
// comes over each. Over its content, not its title bar, where it stays out
// (`windowContentAtPointer`).
const over = viewports.windowContentAtPointer();
if (t.moving and over != t.over) if (over) |w| viewports.bringForward(w);
t.over = over;
// One with no `open_flag` gets no window (`named`), and its window could not close: it stays
Expand Down Expand Up @@ -462,7 +475,7 @@ fn dropContent(size: dvui.Rect.Physical, grab: dvui.Point.Physical) dvui.Rect.Ph
.w = size.w,
// The floating window's rect holds its header, which the title bar takes the place of: its
// content is that much shorter. Given the whole rect, the window came back from each trip
// out and in a header taller (`Merge` puts the header back above the content; the user).
// out and in a header taller (going back in puts the header above the content; the user).
.h = @max(1, size.h - headerHeight()) + band,
};
const u = viewports.usableAtPointer();
Expand Down Expand Up @@ -493,32 +506,6 @@ fn livePicture(slot: *?dvui.Texture.Target, sw: *dvui.Subwindows.Subwindow) bool
return true;
}

/// A card in carry window `card` at `at` (the main window's frame, physical): the window's colour,
/// a band of it `strip` high at its top — a title bar's or a header's place — and `content` under
/// it at the card's width; its corners the window's at `scale`.
fn drawCard(card: *viewports.Viewport, card_target: *?dvui.Texture.Target, content: dvui.Texture.Target, at: dvui.Rect.Physical, strip: f32, scale: f32) void {
const placed = viewports.placeMain(card, viewRect(at));
const shown: dvui.Rect.Physical = .{ .x = placed.x, .y = placed.y, .w = placed.w, .h = placed.h };
viewports.carryShape(card, viewports.windowRadius() * dvui.windowNaturalScale() * scale, 1);
const w: u32 = @intFromFloat(@max(1, @round(shown.w)));
const h: u32 = @intFromFloat(@max(1, @round(shown.h)));
const target = viewports.sizedTarget(card_target, w, h) orelse return;
const picture: viewports.Picture = .begin(target, viewRect(shown));
{
const prev_clip = dvui.clipGet();
defer dvui.clipSet(prev_clip);
dvui.clipSet(shown);
shown.fill(.all(0), .{ .color = .{ .color = dvui.themeGet().color(.window, .fill) } });
if (dvui.Texture.fromTargetTemp(content)) |tex| {
const k = at.w / @max(1, @as(f32, @floatFromInt(content.width)));
const r: dvui.Rect.Physical = .{ .x = at.x, .y = @round(at.y + strip), .w = at.w, .h = @as(f32, @floatFromInt(content.height)) * k };
dvui.renderTexture(tex, .{ .r = r, .s = 1 }, .{}) catch {};
} else |_| {}
}
picture.end();
viewports.present(card, target);
}

/// The window let go over the desktop growing to the floating window's size (`Grow`), its picture
/// scaled up with it, and the floating window's own window once it has: the floating window, drawn
/// without its header meanwhile (`frameless`), laid out in it from the next frame.
Expand Down Expand Up @@ -578,25 +565,6 @@ fn endGrow(g: *Grow) void {
dvui.refresh(null, @src(), null);
}

/// A torn-off window shrinking back into the main window (`Merge`): its last picture eased from the
/// window's frame to the floating window's place, the OS's title bar's band becoming dvui's
/// header's; the floating window drawn nowhere meanwhile, and there as the card goes.
fn mergeFrame(cw: *dvui.Window) void {
const m = if (merge) |*m| m else return;
const k = ease(cw.frame_time_ns - m.start_ns, grow_ns);
if (k >= 1) {
viewports.close(m.card);
if (m.card_target) |ct| ct.destroyLater();
m.snap.destroyLater();
merge = null;
return;
}
for (cw.subwindows.stack.items) |*sw| if (sw.id == m.id) viewports.Picture.drop(sw);
const at = whole(lerp(m.from, m.to, k));
drawCard(m.card, &m.card_target, m.snap, at, m.band + (m.header - m.band) * k, 1);
dvui.refresh(null, @src(), null);
}

fn lerp(a: dvui.Rect.Physical, b: dvui.Rect.Physical, k: f32) dvui.Rect.Physical {
return .{ .x = a.x + (b.x - a.x) * k, .y = a.y + (b.y - a.y) * k, .w = a.w + (b.w - a.w) * k, .h = a.h + (b.h - a.h) * k };
}
Expand Down
19 changes: 15 additions & 4 deletions backend/src/viewports.zig
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,15 @@ pub fn tears() bool {
return builtin.target.os.tag == .macos and SDLBackend.floating_os_windows != .none;
}

/// Whether `vp`'s window lies wholly inside the main window, on the Space being shown
/// (`os_windows`: a torn-off window let go there goes back in).
pub fn mainHolds(vp: *const Viewport) bool {
return dvui.currentWindow().backend.impl.viewportMainHolds(vp);
/// Whether the pointer is over the main window's content, on the Space being shown (`os_windows`:
/// a torn-off window let go there goes back in; `SDLBackend.viewportPointerOverMainContent`).
pub fn pointerOverMainContent() bool {
return dvui.currentWindow().backend.impl.viewportPointerOverMainContent();
}

/// Whether the OS is moving `vp`'s window under a held press (`osMoveEnded` says when it lets go).
pub fn osMoving(vp: *const Viewport) bool {
return dvui.currentWindow().backend.impl.viewportOsMoving(vp);
}

/// Physical pixels from the frame's origin to the first viewport's band, and between bands.
Expand All @@ -82,6 +87,12 @@ pub fn windowAtPointer() ?*anyopaque {
return dvui.currentWindow().backend.impl.windowAtPointer();
}

/// The app's window whose content (below its title bar) the pointer is over, or null
/// (`SDLBackend.windowContentAtPointer`).
pub fn windowContentAtPointer() ?*anyopaque {
return dvui.currentWindow().backend.impl.windowContentAtPointer();
}

/// `window` (`windowAtPointer`'s) in front of the app's other windows, not made key.
pub fn bringForward(window: *anyopaque) void {
dvui.currentWindow().backend.impl.bringForward(window);
Expand Down
Loading