diff --git a/backend/build.zig b/backend/build.zig index c0401a86..2cac7edd 100644 --- a/backend/build.zig +++ b/backend/build.zig @@ -156,6 +156,16 @@ pub fn objcModule(dep: *std.Build.Dependency, opts: Options) ?*std.Build.Module return objc.module("objc"); } +/// `viewports` for a backend without OS windows besides the main one (`src/viewports_none.zig`): +/// dvui's own, its testing backend, the web. An app that builds on either kind imports it as +/// `viewports_none` and takes the backend's own `viewports` where it has one. Needs only dvui, +/// so nothing of SDL is fetched for it. +pub fn viewportsNoneModule(dep: *std.Build.Dependency, dvui_mod: *std.Build.Module) *std.Build.Module { + const m = dep.builder.createModule(.{ .root_source_file = dep.builder.path("src/viewports_none.zig") }); + m.addImport("dvui", dvui_mod); + return m; +} + /// zigwin32, from this package's pin, for an app's own Windows code. Null on the configure pass /// that fetches it. pub fn win32Module(dep: *std.Build.Dependency) ?*std.Build.Module { diff --git a/backend/src/SDLBackend.zig b/backend/src/SDLBackend.zig index e0387f0d..690bf88a 100644 --- a/backend/src/SDLBackend.zig +++ b/backend/src/SDLBackend.zig @@ -16,6 +16,8 @@ const dvui = @import("dvui"); const GpuRenderer = @import("GpuRenderer.zig"); pub const viewport_map = @import("viewport_map.zig"); +/// The OS windows besides the main one an app draws with (`Viewport`). +pub const viewports = @import("viewports.zig"); /// What this backend keeps count of as it runs (`health`). pub const Health = @import("Health.zig"); @@ -94,7 +96,7 @@ window_geometry: WindowGeometry = .{}, // Set by `initWindow` and `initWindowSecondary` for use by eventual child window. init_opts_save: ?InitOptions = null, /// The OS windows besides this one that show part of its frame (`Viewport`). -viewports: [max_viewports]?Viewport = @splat(null), +viewport_slots: [max_viewports]?Viewport = @splat(null), /// Where a held pointer is read while one is held (`PointerPin`, `heldPoint`). pointer_pin: PointerPin = .none, /// macOS: the main window may have come in front of a viewport's window — it took focus, was @@ -915,7 +917,7 @@ fn noteMainForward(self: *SDLBackend, target: ?*c.SDL_Window, event_type: u32) v }; if (event_type == c.SDL_EVENT_WINDOW_FOCUS_GAINED) self.main_focused_once = true; if (!over) return; - for (&self.viewports) |*slot| if (slot.*) |*v| { + for (&self.viewport_slots) |*slot| if (slot.*) |*v| { v.over_main = true; }; } @@ -1172,7 +1174,7 @@ fn openViewport(self: *SDLBackend, at: viewport_map.Rect, title_text: [:0]const const carry = role != .window; const menu = role == .menu; if (!viewportsAvailable()) return null; - const slot = for (self.viewports, 0..) |v, i| { + const slot = for (self.viewport_slots, 0..) |v, i| { if (v == null) break i; } else return null; const b = viewport_map.band(slot); @@ -1220,8 +1222,8 @@ fn openViewport(self: *SDLBackend, at: viewport_map.Rect, title_text: [:0]const // where SDL makes it a child window that moves with its parent, and the windows that left // the main one stay where they are when it moves (`plans/POPOUT_WINDOWS_PLAN.md`, decision 2). if (comptime builtin.os.tag != .macos) _ = c.SDL_SetWindowParent(window, self.window); - self.viewports[slot] = .{ .window = window, .band = b, .anchor = anchor, .density = d, .screen = placed.screen, .frame = placed.frame, .passive = carry and !menu, .overlay = role == .overlay, .menu = menu }; - const vp = &self.viewports[slot].?; + self.viewport_slots[slot] = .{ .window = window, .band = b, .anchor = anchor, .density = d, .screen = placed.screen, .frame = placed.frame, .passive = carry and !menu, .overlay = role == .overlay, .menu = menu }; + const vp = &self.viewport_slots[slot].?; // Dressed by `viewportOpenMenu`, and no hit test: nothing in it moves or resizes it. if (menu) return vp; if (role == .overlay) { @@ -1325,13 +1327,13 @@ pub fn viewportClose(self: *SDLBackend, vp: *Viewport) void { // What rides on it lets go first (`Viewport.ride`): SDL destroys a window's children with it, // and the slot of each still holds its window. The app moves a dialog left behind back over // the main window (`Popout`). - for (&self.viewports) |*slot| { + for (&self.viewport_slots) |*slot| { if (slot.*) |*other| if (other != vp and other.ride != null and other.ride == vp.window) { _ = c.SDL_SetWindowParent(other.window, null); other.ride = null; }; } - for (&self.viewports) |*slot| { + for (&self.viewport_slots) |*slot| { if (slot.*) |*v| if (v == vp) { // Holding the keyboard as it goes — a float that merged back into the main window, or // came back on a command, while its window was the one in front — it hands it back to @@ -1352,7 +1354,7 @@ pub fn viewportClose(self: *SDLBackend, vp: *Viewport) void { break; }; } - for (self.viewports) |v| if (v != null) return; + for (self.viewport_slots) |v| if (v != null) return; _ = c.SDL_ResetHint(c.SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH); } @@ -1623,7 +1625,7 @@ pub fn viewportPresent(_: *SDLBackend, vp: *Viewport, target: ?dvui.TextureTarge } fn viewportOf(self: *SDLBackend, window: *c.SDL_Window) ?*Viewport { - for (&self.viewports) |*slot| { + for (&self.viewport_slots) |*slot| { if (slot.*) |*v| if (v.window == window) return v; } return null; @@ -1666,7 +1668,7 @@ fn heldAcrossWindows(self: *SDLBackend, event: c.SDL_Event) bool { c.SDL_EVENT_MOUSE_BUTTON_DOWN, c.SDL_EVENT_MOUSE_BUTTON_UP => if (event.button.which == c.SDL_TOUCH_MOUSEID) return false, else => return false, } - const any = for (self.viewports) |v| { + const any = for (self.viewport_slots) |v| { if (v != null) break true; } else false; if (!any) return false; @@ -1700,7 +1702,7 @@ fn heldPoint(self: *SDLBackend) dvui.Point.Physical { var mh: c_int = 0; _ = c.SDL_GetWindowSize(self.window, &mw, &mh); const over_main = gx >= origin.x and gy >= origin.y and gx < origin.x + @as(f32, @floatFromInt(mw)) and gy < origin.y + @as(f32, @floatFromInt(mh)); - for (&self.viewports) |*slot| { + for (&self.viewport_slots) |*slot| { const vp = if (slot.*) |*v| v else continue; if (vp.passive or vp.see_through) continue; // In a fullscreen Space of its own, a window is where the OS put it, and under nothing: @@ -1964,7 +1966,7 @@ pub fn deinit(self: *SDLBackend) void { WindowGeometry.save(self); } // Viewports are claimed on this window's device: given back before it goes. - for (&self.viewports) |*slot| { + for (&self.viewport_slots) |*slot| { if (slot.*) |*v| self.viewportClose(v); } self.gpu.destroy(); @@ -1988,7 +1990,7 @@ pub fn renderPresent(self: *SDLBackend) void { var transacted: [max_viewports]bool = @splat(false); var any = false; if (comptime builtin.os.tag == .macos) { - for (&self.viewports, 0..) |*slot, i| { + for (&self.viewport_slots, 0..) |*slot, i| { const vp = if (slot.*) |*v| v else continue; const first = !vp.shown and vp.pending != null; const reshaped = vp.passive and !vp.overlay and (vp.carry_radius != vp.carry_radius_shown or vp.carry_alpha != vp.carry_alpha_shown or vp.carry_size_shown[0] != vp.screen.w or vp.carry_size_shown[1] != vp.screen.h); @@ -2018,7 +2020,7 @@ pub fn renderPresent(self: *SDLBackend) void { } } defer if (comptime builtin.os.tag == .macos) if (any) { - for (&self.viewports, transacted) |*slot, t| { + for (&self.viewport_slots, transacted) |*slot, t| { if (!t) continue; const vp = if (slot.*) |*v| v else continue; fizzy_native_viewport_presented(cocoaWindow(vp.window)); @@ -2033,7 +2035,7 @@ pub fn renderPresent(self: *SDLBackend) void { // drawables back slowly while the main window goes through a Space transition, and waiting on // them every frame halved the move's rate with one open. const main_moving = platform.window.windowMovingItself(self.window); - for (&self.viewports, 0..) |*slot, i| { + for (&self.viewport_slots, 0..) |*slot, i| { const vp = if (slot.*) |*v| v else continue; const target = vp.pending orelse continue; vp.pending = null; @@ -2058,7 +2060,7 @@ pub fn renderPresent(self: *SDLBackend) void { self.gpu.present(self.clear_window_on_begin, builtin.os.tag == .windows and self.inLiveResize()); // Shown once there is a frame in it (or empty, above). Not made key: the window it came out of // keeps the keyboard until the viewport is clicked. - for (&self.viewports, first_frame, map_empty) |*slot, show, empty| { + for (&self.viewport_slots, first_frame, map_empty) |*slot, show, empty| { if (!show and !empty) continue; const vp = if (slot.*) |*v| v else continue; if (!vp.mapped) { @@ -2081,11 +2083,11 @@ pub fn renderPresent(self: *SDLBackend) void { // activates, which brings the main window in front of them (`fizzy_macos_watch_first_activation`). if (comptime builtin.os.tag == .macos) { fizzy_macos_watch_first_activation(); - if (fizzy_macos_took_first_activation() != 0) for (&self.viewports) |*slot| if (slot.*) |*v| { + if (fizzy_macos_took_first_activation() != 0) for (&self.viewport_slots) |*slot| if (slot.*) |*v| { v.over_main = true; }; const main_ns = cocoaWindow(self.window); - for (&self.viewports) |*slot| { + for (&self.viewport_slots) |*slot| { const vp = if (slot.*) |*v| v else continue; if (!vp.over_main) continue; vp.over_main = false; @@ -2098,7 +2100,7 @@ pub fn renderPresent(self: *SDLBackend) void { // when the stacking may have changed. if (self.stack_stale) { self.stack_stale = false; - for (&self.viewports) |*slot| { + for (&self.viewport_slots) |*slot| { const vp = if (slot.*) |*v| v else continue; vp.under_main = vp.mapped and !vp.passive and !vp.menu and fizzy_macos_viewport_under_main(cocoaWindow(vp.window), main_ns) != 0; } @@ -2176,7 +2178,7 @@ pub fn begin(self: *SDLBackend, arena: std.mem.Allocator) !void { // A float's window standing still on its way into or out of full screen: its picture has // taken this frame's step (`begin_hook`), and the float follows it there — as it did the // window's moves — and back to the window as the way ends. - if (comptime builtin.os.tag == .macos) for (&self.viewports) |*slot| { + if (comptime builtin.os.tag == .macos) for (&self.viewport_slots) |*slot| { const vp = if (slot.*) |*v| v else continue; if (vp.picture_at[0] != 0 or vp.picture_at[1] != 0 or platform.window.windowSpacePicture(vp.window) != null) viewportFollowWindow(vp); @@ -2227,7 +2229,7 @@ fn mainHeld(self: *SDLBackend) bool { // Its own way into or out of full screen is drawn whatever AppKit says of it on the way. if (platform.window.windowMovingItself(self.window)) return false; if (c.SDL_GetWindowFlags(self.window) & c.SDL_WINDOW_OCCLUDED != 0) return true; - for (&self.viewports) |*slot| { + for (&self.viewport_slots) |*slot| { const vp = if (slot.*) |*v| v else continue; if (vp.shown and platform.window.windowMovingItself(vp.window)) return true; } diff --git a/backend/src/platform/window.zig b/backend/src/platform/window.zig index a2e84503..9ea461e7 100644 --- a/backend/src/platform/window.zig +++ b/backend/src/platform/window.zig @@ -53,6 +53,46 @@ pub const WindowGlass = extern struct { feather: f64, }; +/// A window's Liquid Glass this frame (`liquidGlassLook`, `viewports.windowGlass`), as an app +/// says it: what `core.glass_look.window` says at the window's opacity, the window's colour, and the clearing +/// bevel `core.glass_look.band` gives its size. +pub const WindowGlassLook = struct { + under_variant: i32, + under_style: i32, + over_variant: i32, + over_style: i32, + frost: f32, + blur: f32, + glass: f32, + fill: dvui.Color, + /// The colour's opacity in the body… + fill_opacity: f32, + /// …and over all of it, the bevel too. + top_fill: f32, + /// Points, each. + radius: f32, + clear: f32, + feather: f32, + + /// As `fizzy_macos_window_liquid_glass_look` reads it. + pub fn native(self: WindowGlassLook) WindowGlass { + return .{ + .under_variant = self.under_variant, + .under_style = self.under_style, + .over_variant = self.over_variant, + .over_style = self.over_style, + .frost = self.frost, + .blur = self.blur, + .glass = self.glass, + .fill = .{ @as(f64, @floatFromInt(self.fill.r)) / 255, @as(f64, @floatFromInt(self.fill.g)) / 255, @as(f64, @floatFromInt(self.fill.b)) / 255, self.fill_opacity }, + .top_fill = self.top_fill, + .radius = self.radius, + .clear = self.clear, + .feather = self.feather, + }; + } +}; + /// Make one of fizzy's titled windows (`raw_ptr`, its `NSWindow`) a window of Liquid Glass — over /// the clear lens a body of frost, the plain blur (the vibrancy it wore before, /// `ns_visual_effect_material`) and the window's colour, fading out across a clearing bevel along @@ -259,7 +299,7 @@ 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.viewports) |*slot| { + 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; @@ -383,7 +423,7 @@ pub fn setBackground(win: *dvui.Window, color: dvui.Color) void { // 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.viewports) |*slot| { + 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; diff --git a/backend/src/viewports.zig b/backend/src/viewports.zig new file mode 100644 index 00000000..2a89da63 --- /dev/null +++ b/backend/src/viewports.zig @@ -0,0 +1,378 @@ +//! OS windows besides the main one, each showing a part of the one frame: a float, a menu or a +//! dialog popped out into a window of its own (`plans/POPOUT_WINDOWS_PLAN.md`, `Viewport`). The +//! API an app draws them with, over this backend's own (`SDLBackend.viewport*`); `viewport_map` +//! is where each one's part of the frame lies. +//! +//! Every call acts on the current dvui window's backend, so it is made between `dvui.Window.begin` +//! and `end`. On Wayland, where a window cannot be placed, there are none (`available`). +const builtin = @import("builtin"); +const dvui = @import("dvui"); +const SDLBackend = @import("SDLBackend.zig"); + +/// A window's Liquid Glass this frame, as the main window's (`platform.window.WindowGlassLook`). +pub const WindowGlassLook = SDLBackend.platform.window.WindowGlassLook; + +/// Always, on this backend: an app that also builds on one without OS windows (dvui's own, the +/// web) keeps a stand-in with this name false. +pub const supported = true; +pub const Viewport = SDLBackend.Viewport; +/// Physical pixels of the frame. +pub const Rect = SDLBackend.viewport_map.Rect; + +/// Whether this run can open viewports: not on Wayland, where a window cannot be placed. +pub fn available() bool { + return SDLBackend.viewportsAvailable(); +} + +/// A viewport over `at` (the frame as the main window shows it), its window opening over that +/// place on the desktop, hidden until a frame is presented into it. Its own part of the frame +/// is `frameOf`. +pub fn open(at: Rect, title: [:0]const u8) ?*Viewport { + return dvui.currentWindow().backend.impl.viewportOpen(at, title); +} + +/// A window to carry a view past every window of the app's, over the desktop: clear, the pointer +/// passing through it, above every window, placed and drawn as any viewport (`placeMain`, +/// `present`). Where it can be made (`carries`). +pub fn openCarry(at: Rect) ?*Viewport { + return dvui.currentWindow().backend.impl.viewportOpenCarry(at); +} + +/// Whether one of the app's windows has the keyboard: the app is the active one. +pub fn appActive() bool { + return SDLBackend.appActive(); +} + +/// Whether menus can be windows of their own (`openMenu`): macOS, and Windows (Acrylic popups), +/// where viewports are. +pub const menus = builtin.os.tag == .macos or builtin.os.tag == .windows; + +/// A window for a menu (`Popout`'s menus): borderless, above every window, never focused, the +/// OS's material in it — rounded by `radius` points — and the pointer over it read in the main +/// window's frame (`mainOffset`). Placed with `placeRiding`, drawn as any viewport. `ride`: the +/// window the OS moves it with — smoothly, through a drag. `dialog`: a dialog's window, the same +/// but in its window's stacking, not above every window. +pub fn openMenu(at: Rect, radius: f32, ride: Ride, dialog: bool) ?*Viewport { + return dvui.currentWindow().backend.impl.viewportOpenMenu(at, radius, ride, dialog); +} + +pub const Ride = SDLBackend.Ride; + +/// `placeMain` for a window riding on another (`openMenu`'s `ride`), put somewhere new only when +/// `key` — where it lies over that window — changes. +pub fn placeRiding(vp: *Viewport, frame: Rect, key: Rect) Rect { + return dvui.currentWindow().backend.impl.viewportPlaceRiding(vp, frame, key); +} + +/// Where menu `vp` is drawn in the frame from where its window lies over the main window, +/// physical pixels: none for a menu of the main window's. +pub fn mainOffset(vp: *Viewport, offset: dvui.Point.Physical) void { + dvui.currentWindow().backend.impl.viewportMainOffset(vp, .{ .x = offset.x, .y = offset.y }); +} + +/// A piece of the OS's glass in an overlay (`overlayGlass`): a rounded rect, points from the +/// overlay window's top left. +pub const GlassShape = SDLBackend.GlassShape; + +/// Whether the OS has Liquid Glass to draw a view drag's glass with (`openOverlay`): macOS 26. +pub fn liquidGlass() bool { + return SDLBackend.liquidGlassAvailable(); +} + +/// A window over the display the main window is on (`displayInMain`) holding the OS's glass +/// (`overlayGlass`) under the picture presented into it, the pointer passing through it. Where +/// `liquidGlass`. +pub fn openOverlay(at: Rect) ?*Viewport { + return dvui.currentWindow().backend.impl.viewportOpenOverlay(at); +} + +/// One of Liquid Glass's materials: its variant and style. +pub const GlassMaterial = struct { variant: i32, style: i32 }; +/// What an overlay's glass is: two layers of the same pieces, `under` and `over`, crossfaded by +/// `over_share` — each layer's pieces alike, for the OS to run them together. +pub const GlassLook = struct { + under: GlassMaterial, + over: GlassMaterial, + over_share: f32, + /// How much of the glass there is. + glass: f32 = 1, + /// The window's colour under the glass, each piece's shape, `fill_opacity` opaque; a lit + /// piece's lit by `lit_amount` toward `lit_toward`. + fill: dvui.Color = .black, + fill_opacity: f32 = 0, + lit_toward: dvui.Color = .white, + lit_amount: f32 = 0, + /// Each piece's clearing bevel (`core.glass_look.band`): a share of its shorter half, at + /// most `bevel_cap` points, clear for `bevel_clear` of it before the frost and the colour + /// come in. + bevel: f32 = 0, + bevel_cap: f32 = 0, + bevel_clear: f32 = 0, + /// Points of plain blur over the lens in each piece's body, in place of the `over` frost, + /// where the OS can blur what is behind a window without its materials' tint (macOS: Core + /// Animation's backdrop blur): 0, or none to draw it with, and the frost it is. + blur: f32 = 0, +}; + +/// Overlay `vp`'s glass this frame (`openOverlay`), run together within `spacing` points, as +/// `look`. +pub fn overlayGlass(vp: *Viewport, shapes: []const GlassShape, spacing: f32, look: GlassLook) void { + const f = look.fill; + const t = look.lit_toward; + dvui.currentWindow().backend.impl.viewportOverlayGlass(vp, shapes, spacing, .{ + .under_variant = look.under.variant, + .under_style = look.under.style, + .over_variant = look.over.variant, + .over_style = look.over.style, + .over_share = look.over_share, + .glass = look.glass, + .fill = .{ @as(f64, @floatFromInt(f.r)) / 255, @as(f64, @floatFromInt(f.g)) / 255, @as(f64, @floatFromInt(f.b)) / 255, look.fill_opacity }, + .lit_toward = .{ @as(f64, @floatFromInt(t.r)) / 255, @as(f64, @floatFromInt(t.g)) / 255, @as(f64, @floatFromInt(t.b)) / 255, look.lit_amount }, + .bevel = look.bevel, + .bevel_cap = look.bevel_cap, + .bevel_clear = look.bevel_clear, + .blur = look.blur, + }); +} + +/// The carried view's picture under an overlay's glass (`overlayPhoto`). +pub const OverlayPhoto = struct { + /// The rounded rect it shows in, points from the overlay window's top left. + rect: Rect, + radius: f32, + /// Where its image lies, points from `rect`'s top left: it may reach past it. + image: Rect, + fill: dvui.Color, + alpha: f32 = 1, + /// Points of plain blur on it. + blur: f32 = 0, +}; + +/// Overlay `vp`'s image of the carried view's picture: premultiplied RGBA rows, `w` by `h`, +/// copied; null, none. Once a drag (`overlayPhoto` places it). +pub fn overlayPhotoImage(vp: *Viewport, rgba: ?[]const u8, w: u32, h: u32) void { + dvui.currentWindow().backend.impl.viewportOverlayPhotoImage(vp, rgba, w, h); +} + +/// Where overlay `vp` shows the carried view's picture this frame, under its glass, so the +/// glass over it bends it; null, nowhere. Applied with the glass. +pub fn overlayPhoto(vp: *Viewport, photo: ?OverlayPhoto) void { + const p = photo orelse return dvui.currentWindow().backend.impl.viewportOverlayPhoto(vp, null); + const f = p.fill; + dvui.currentWindow().backend.impl.viewportOverlayPhoto(vp, .{ + .x = p.rect.x, + .y = p.rect.y, + .w = p.rect.w, + .h = p.rect.h, + .radius = p.radius, + .image = .{ p.image.x, p.image.y, p.image.w, p.image.h }, + .fill = .{ @as(f64, @floatFromInt(f.r)) / 255, @as(f64, @floatFromInt(f.g)) / 255, @as(f64, @floatFromInt(f.b)) / 255, @as(f64, @floatFromInt(f.a)) / 255 }, + .alpha = p.alpha, + .blur = p.blur, + }); +} + +/// The display the main window is on, in its frame (physical pixels from its top left). +pub fn displayInMain() Rect { + return dvui.currentWindow().backend.impl.viewportDisplayInMain(); +} + +/// A carry window's shape this frame (`openCarry`): what it carries fills it, rounded by +/// `radius` physical pixels — the OS's material and shadow in that shape — `alpha` opaque. +/// Null: hidden. +pub fn carryShape(vp: *Viewport, radius: ?f32, alpha: f32) void { + dvui.currentWindow().backend.impl.viewportCarryShape(vp, radius, alpha); +} + +/// `vp`'s window at `other`'s level and just above it — a float born of a drop, over the drag's +/// glass while that goes — until `settle` puts it back at its own. macOS. +pub fn lift(vp: *Viewport, other: *Viewport) void { + dvui.currentWindow().backend.impl.viewportLift(vp, other); +} + +pub fn settle(vp: *Viewport) void { + dvui.currentWindow().backend.impl.viewportSettle(vp); +} + +/// `vp`'s window put just above `other`'s in the stacking (a growing float's picture over the +/// glass it grows out of). macOS. +pub fn orderAbove(vp: *Viewport, other: *Viewport) void { + dvui.currentWindow().backend.impl.viewportOrderAbove(vp, other); +} + +/// A carry window's glass as the lens — the carried view, a drop of water — or as frost, as a +/// window's material is (a float's window growing out of it). Liquid Glass only. +pub fn carryLens(vp: *Viewport, lens: bool) void { + dvui.currentWindow().backend.impl.viewportCarryLens(vp, lens); +} + +/// Whether `vp`'s window lies under the main window in the OS's stacking — a float's, clicked +/// behind it — as last read (macOS; never elsewhere, where floats stay over it). +pub fn underMain(vp: *Viewport) bool { + return dvui.currentWindow().backend.impl.viewportUnderMain(vp); +} + +/// Points from `vp`'s window's left edge past the OS's own buttons in its title bar (macOS's +/// traffic lights, `os_buttons`): 0 without them. +pub fn buttonsWidth(vp: *Viewport) f32 { + return dvui.currentWindow().backend.impl.viewportButtonsWidth(vp); +} + +/// `vp`'s window's Liquid Glass this frame, as the main window's (`windowGlass`). Whether it +/// has any: its float then draws no base of its own. +pub fn windowGlass(vp: *Viewport, look: WindowGlassLook) bool { + return dvui.currentWindow().backend.impl.viewportLiquidGlass(vp, look.native()); +} + +/// A float's window's corner radius, points: the OS's for a titled window (`os_frame`). +pub fn windowRadius() f32 { + return dvui.currentWindow().backend.impl.windowCornerRadius(); +} + +/// A held pointer over `vp`'s window is read as over the main window beneath it while `on`: its +/// float gone to its ghost while a view is carried out of it, aimed at the places under it. +pub fn seeThrough(vp: *Viewport, on: bool) void { + dvui.currentWindow().backend.impl.viewportSeeThrough(vp, on); +} + +/// Whether `vp`'s window is maximized — zoomed, or in a fullscreen Space of its own — as the +/// main window's is asked: nothing of the desktop behind it shows. +pub fn maximized(vp: *const Viewport) bool { + return dvui.currentWindow().backend.impl.viewportMaximized(vp); +} + +/// Whether `vp`'s window covers the desktop, or will once the transition it is in ends, as the +/// main window's is asked (`coversDesktop`). +pub fn coversDesktop(vp: *const Viewport) bool { + return dvui.currentWindow().backend.impl.viewportCovers(vp); +} + +/// Whether `vp`'s window is on its way into a fullscreen Space (`enteringSpace`). +pub fn enteringSpace(vp: *const Viewport) bool { + return dvui.currentWindow().backend.impl.viewportEnteringSpace(vp); +} + +/// How far `vp`'s window is into full screen as it moves itself there or back (`spaceFullness`). +pub fn spaceFullness(vp: *const Viewport) ?f32 { + return dvui.currentWindow().backend.impl.viewportSpaceFullness(vp); +} + +/// `vp`'s window `alpha` opaque, all of it — its material too. +pub fn fade(vp: *Viewport, alpha: f32) void { + dvui.currentWindow().backend.impl.viewportFade(vp, alpha); +} + +/// Whether a carried view can be shown past every window (`openCarry`): macOS. +pub const carries = builtin.os.tag == .macos; + +pub fn close(vp: *Viewport) void { + dvui.currentWindow().backend.impl.viewportClose(vp); +} + +/// The part of the frame `vp` shows, physical pixels: in its band, past the main window. +pub fn frameOf(vp: *const Viewport) Rect { + return vp.frame; +} + +/// Put `vp`'s window where it shows `frame`; the part of the frame it then shows. +pub fn place(vp: *Viewport, frame: Rect) Rect { + return dvui.currentWindow().backend.impl.viewportPlace(vp, frame); +} + +/// Hand `vp` this frame's picture of its part of the frame, drawn into `target`, or nothing. +pub fn present(vp: *Viewport, target: ?dvui.TextureTarget) void { + dvui.currentWindow().backend.impl.viewportPresent(vp, target); +} + +/// Where `vp`'s window is now, in the main window's part of the frame (physical pixels from +/// its top left): where its float goes when it comes back. +pub fn inMain(vp: *const Viewport) Rect { + return dvui.currentWindow().backend.impl.viewportInMain(vp); +} + +/// Put `vp`'s window where it shows `frame` of the main window's frame (past its edge, for a +/// float split out under a drag); the part of the frame it then shows. +pub fn placeMain(vp: *Viewport, frame: Rect) Rect { + return dvui.currentWindow().backend.impl.viewportPlaceMain(vp, frame); +} + +/// `frame` of the main window's frame as the same desktop place in `vp`'s band. +pub fn bandFromMain(vp: *const Viewport, frame: Rect) Rect { + return dvui.currentWindow().backend.impl.viewportBandFromMain(vp, frame); +} + +/// Whether `vp`'s window has shown a frame yet. +pub fn shown(vp: *const Viewport) bool { + return dvui.currentWindow().backend.impl.viewportShown(vp); +} + +/// Where a held pointer is read: by the window it is over, or pinned to the main window's +/// frame or a viewport's band while a window is moved or resized. +pub const Pin = SDLBackend.PointerPin; +pub fn pinPointer(pin: Pin) void { + dvui.currentWindow().backend.impl.viewportPinPointer(pin); +} + +/// Whether the OS frames a viewport's window itself — its corners, its shadow, resizing from its +/// edges (Windows: DWM; macOS: a titled window, as the main window is) — so the window is +/// exactly the float's glass. Otherwise it is the glass with a clear +/// margin round it, which the float draws its own shadow in. +pub const os_frame = builtin.os.tag == .windows or builtin.os.tag == .macos; + +/// Whether a viewport's window has the OS's own buttons to close, minimize and zoom it — macOS's +/// traffic lights, a titled window's — so its float draws no close button of its own. +pub const os_buttons = builtin.os.tag == .macos; + +/// A material behind the float's glass in `vp`'s window — its rounded rect `inset` physical +/// pixels in from the window's edge, `radius` its corners — for the float's frost to read the +/// desktop through, in the app's light or dark (`dark`). False where the platform has none +/// (yet). +pub fn glass(vp: *Viewport, inset: f32, radius: f32, dark: bool) bool { + return dvui.currentWindow().backend.impl.viewportGlass(vp, inset, radius, dark); +} + + +/// The OS asked to close `vp`'s window. +pub fn closeRequested(vp: *const Viewport) bool { + return vp.close_requested; +} + +/// Where a press on `vp`'s window is the OS's, from its float this frame (physical pixels of +/// the frame): `drag` its header, less `keep` (its close button), moves the window; `edge` in +/// from `glass`'s sides resizes it. Null: all of it is the app's. +/// `app_side` / `app_corner`: the float's own resize zones, the app's over its header where +/// the OS resizes from no edge (macOS). +pub const Hints = struct { drag: Rect, keep: Rect, glass: Rect, edge: f32, app_side: f32 = 0, app_corner: f32 = 0 }; +pub fn hints(vp: *Viewport, h: ?Hints) void { + dvui.currentWindow().backend.impl.viewportHints(vp, if (h) |x| .{ .drag = x.drag, .keep = x.keep, .glass = x.glass, .edge = x.edge, .app_side = x.app_side, .app_corner = x.app_corner } else null); +} + +/// Where `vp`'s window shows in the frame now, when the OS has moved or resized it since the +/// last ask — where its float goes. +pub fn osPlaced(vp: *Viewport) ?Rect { + return dvui.currentWindow().backend.impl.viewportOsPlaced(vp); +} + +/// The press the OS took to move or resize `vp`'s window was let go; `resized` unless the +/// window was only moved. +pub const MoveEnd = struct { resized: bool }; +pub fn osMoveEnded(vp: *Viewport) ?MoveEnd { + const e = dvui.currentWindow().backend.impl.viewportOsMoveEnded(vp) orelse return null; + return .{ .resized = e.resized }; +} + +/// Hand the drag under way to the OS, which moves `vp`'s window from then on. False where it +/// is not done: the app goes on moving it. +pub fn dragMove(vp: *Viewport) bool { + return dvui.currentWindow().backend.impl.viewportDragMove(vp); +} + +/// What `vp`'s window is called: in the taskbar, the Window menu, the window switcher. +pub fn setTitle(vp: *Viewport, text: []const u8) void { + dvui.currentWindow().backend.impl.viewportTitle(vp, text); +} + +/// The least the OS may resize `vp`'s window to, physical pixels. +pub fn minSize(vp: *Viewport, w: f32, h: f32) void { + dvui.currentWindow().backend.impl.viewportMinSize(vp, w, h); +} diff --git a/backend/src/viewports_none.zig b/backend/src/viewports_none.zig new file mode 100644 index 00000000..e7dc2061 --- /dev/null +++ b/backend/src/viewports_none.zig @@ -0,0 +1,161 @@ +//! `viewports` for a backend without OS windows besides the main one — dvui's own SDL3 backend, +//! its testing backend, the web (one canvas): the same names as the backend package's own +//! (`viewports.zig`), every call saying there are none. `available` is false and `open` gives +//! null, so an app's floats, menus and dialogs stay in the main window. +//! +//! An app that builds on either kind of backend imports this beside it and takes the backend's +//! own where it has one: +//! +//! const viewports = if (@hasDecl(backend, "viewports")) backend.viewports else @import("viewports_none"); +const dvui = @import("dvui"); + +/// A window's Liquid Glass this frame, as the backend package's (`platform.window.WindowGlassLook`): +/// there is none here, but an app still says what it would be. +pub const WindowGlassLook = struct { + under_variant: i32, + under_style: i32, + over_variant: i32, + over_style: i32, + frost: f32, + blur: f32, + glass: f32, + fill: dvui.Color, + fill_opacity: f32, + top_fill: f32, + radius: f32, + clear: f32, + feather: f32, +}; + +pub const supported = false; +pub fn available() bool { + return false; +} +pub const Viewport = struct {}; +pub const Rect = struct { x: f32 = 0, y: f32 = 0, w: f32 = 0, h: f32 = 0 }; + +pub fn open(_: Rect, _: [:0]const u8) ?*Viewport { + return null; +} +pub fn close(_: *Viewport) void {} +pub fn frameOf(_: *const Viewport) Rect { + return .{}; +} +pub fn place(_: *Viewport, frame: Rect) Rect { + return frame; +} +pub fn present(_: *Viewport, _: ?dvui.TextureTarget) void {} +pub fn inMain(_: *const Viewport) Rect { + return .{}; +} +pub const os_frame = false; +pub const os_buttons = false; +pub const carries = false; +pub fn seeThrough(_: *Viewport, _: bool) void {} +pub fn fade(_: *Viewport, _: f32) void {} +pub fn maximized(_: *const Viewport) bool { + return false; +} +pub fn coversDesktop(_: *const Viewport) bool { + return false; +} +pub fn enteringSpace(_: *const Viewport) bool { + return false; +} +pub fn spaceFullness(_: *const Viewport) ?f32 { + return null; +} +pub fn carryShape(_: *Viewport, _: ?f32, _: f32) void {} +pub fn carryLens(_: *Viewport, _: bool) void {} +/// Any look, `WindowGlassLook` or the backend package's (an app on dvui's own SDL3 backend still +/// dresses its main window with `platform`'s): there is no viewport to give it to. +pub fn windowGlass(_: *Viewport, _: anytype) bool { + return false; +} +pub fn underMain(_: *Viewport) bool { + return false; +} +pub fn buttonsWidth(_: *Viewport) f32 { + return 0; +} +pub fn windowRadius() f32 { + return 0; +} +pub fn openCarry(_: Rect) ?*Viewport { + return null; +} +pub const menus = false; +pub fn appActive() bool { + return true; +} +pub const Ride = union(enum) { none, main, viewport: *Viewport }; +pub fn orderAbove(_: *Viewport, _: *Viewport) void {} +pub fn lift(_: *Viewport, _: *Viewport) void {} +pub fn settle(_: *Viewport) void {} +pub fn placeRiding(_: *Viewport, frame: Rect, _: Rect) Rect { + return frame; +} +pub fn openMenu(_: Rect, _: f32, _: Ride, _: bool) ?*Viewport { + return null; +} +pub fn mainOffset(_: *Viewport, _: dvui.Point.Physical) void {} +pub const GlassShape = extern struct { x: f64, y: f64, w: f64, h: f64, radius: f64, lit: f64, alpha: f64, frost: f64 }; +pub fn liquidGlass() bool { + return false; +} +pub fn openOverlay(_: Rect) ?*Viewport { + return null; +} +pub const GlassMaterial = struct { variant: i32, style: i32 }; +pub const GlassLook = struct { + under: GlassMaterial, + over: GlassMaterial, + over_share: f32, + glass: f32 = 1, + fill: dvui.Color = .black, + fill_opacity: f32 = 0, + lit_toward: dvui.Color = .white, + lit_amount: f32 = 0, + bevel: f32 = 0, + bevel_cap: f32 = 0, + bevel_clear: f32 = 0, + blur: f32 = 0, +}; +pub fn overlayGlass(_: *Viewport, _: []const GlassShape, _: f32, _: GlassLook) void {} +pub const OverlayPhoto = struct { rect: Rect, radius: f32, image: Rect, fill: dvui.Color, alpha: f32 = 1, blur: f32 = 0 }; +pub fn overlayPhotoImage(_: *Viewport, _: ?[]const u8, _: u32, _: u32) void {} +pub fn overlayPhoto(_: *Viewport, _: ?OverlayPhoto) void {} +pub fn displayInMain() Rect { + return .{}; +} +pub const Hints = struct { drag: Rect, keep: Rect, glass: Rect, edge: f32, app_side: f32 = 0, app_corner: f32 = 0 }; +pub fn hints(_: *Viewport, _: ?Hints) void {} +pub fn osPlaced(_: *Viewport) ?Rect { + return null; +} +pub const MoveEnd = struct { resized: bool }; +pub fn osMoveEnded(_: *Viewport) ?MoveEnd { + return null; +} +pub fn dragMove(_: *Viewport) bool { + return false; +} +pub fn minSize(_: *Viewport, _: f32, _: f32) void {} +pub fn setTitle(_: *Viewport, _: []const u8) void {} +pub fn glass(_: *Viewport, _: f32, _: f32, _: bool) bool { + return false; +} +pub fn placeMain(_: *Viewport, frame: Rect) Rect { + return frame; +} +pub fn bandFromMain(_: *const Viewport, frame: Rect) Rect { + return frame; +} +pub fn shown(_: *const Viewport) bool { + return false; +} +pub const Pin = union(enum) { none, main, viewport: *Viewport }; +pub fn pinPointer(_: Pin) void {} +pub fn closeRequested(_: *const Viewport) bool { + return false; +} diff --git a/build/app.zig b/build/app.zig index 0a45cab6..a955a8b1 100644 --- a/build/app.zig +++ b/build/app.zig @@ -806,6 +806,8 @@ pub fn construct(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. }); fizzy_test_module.addImport("dvui", dvui_testing_dep.module("dvui_testing")); fizzy_test_module.addImport("backend", dvui_testing_dep.module("testing")); + // The testing backend has no viewports (`backend_web.viewports`). + fizzy_test_module.addImport("viewports_none", @import("fizzy_backend").viewportsNoneModule(fizzy_exe.backendDependency(b), dvui_testing_dep.module("dvui_testing"))); fizzy_test_module.addImport("assets", assets_module); fizzy_test_module.addImport("build_opts", sdk.buildOptsModule(build_opts)); diff --git a/build/exe.zig b/build/exe.zig index df90cf5a..28fed66e 100644 --- a/build/exe.zig +++ b/build/exe.zig @@ -240,6 +240,8 @@ pub fn addFizzyExecutableForTarget( exe.root_module.addImport("dvui", dvui_mod); exe.root_module.addImport("backend", native.backend); exe.root_module.addImport("platform", platformModule(b, native, resolved_target, optimize, macos_sdl_paths)); + // On dvui's own SDL3 backend (`-Dnative-backend=sdl3`), which has no viewports. + exe.root_module.addImport("viewports_none", fizzy_backend.viewportsNoneModule(backendDependency(b), dvui_mod)); // Shared `core` module (gfx/math/fs/generated atlas/platform/paths/dvui hub + // generic widgets). Import set is shared with the plugin SDK path — see sdk/core_module.zig. diff --git a/build/web.zig b/build/web.zig index 51d26965..1a2303d1 100644 --- a/build/web.zig +++ b/build/web.zig @@ -72,6 +72,8 @@ pub fn addSteps( }); web_exe.entry = .disabled; web_exe.root_module.addImport("dvui", dvui_web); + // A page has one canvas: `backend_web.viewports`. + web_exe.root_module.addImport("viewports_none", @import("fizzy_backend").viewportsNoneModule(@import("exe.zig").backendDependency(b), dvui_web)); web_exe.root_module.addImport("web-backend", web_backend); // Extra wasm exports beyond dvui's own (`dvui_init`/`dvui_update`/etc.). The wasm diff --git a/src/backend/backend_native.zig b/src/backend/backend_native.zig index 3347f99c..a4af6dd4 100644 --- a/src/backend/backend_native.zig +++ b/src/backend/backend_native.zig @@ -129,44 +129,8 @@ pub fn saveWindowGeometry(win: *dvui.Window) void { /// Called at the end of AppInit: the monitor may drive frames through window animations now. pub const macosLaunchComplete = platform.macos_monitor.launchComplete; -/// A window's Liquid Glass this frame (`windowGlass`, `viewports.windowGlass`): what -/// `core.glass_look.window` says at the window's opacity, the window's colour, and the clearing -/// bevel `core.glass_look.band` gives its size. -pub const WindowGlassLook = struct { - under_variant: i32, - under_style: i32, - over_variant: i32, - over_style: i32, - frost: f32, - blur: f32, - glass: f32, - fill: dvui.Color, - /// The colour's opacity in the body… - fill_opacity: f32, - /// …and over all of it, the bevel too. - top_fill: f32, - /// Points, each. - radius: f32, - clear: f32, - feather: f32, - - fn native(self: WindowGlassLook) platform.window.WindowGlass { - return .{ - .under_variant = self.under_variant, - .under_style = self.under_style, - .over_variant = self.over_variant, - .over_style = self.over_style, - .frost = self.frost, - .blur = self.blur, - .glass = self.glass, - .fill = .{ @as(f64, @floatFromInt(self.fill.r)) / 255, @as(f64, @floatFromInt(self.fill.g)) / 255, @as(f64, @floatFromInt(self.fill.b)) / 255, self.fill_opacity }, - .top_fill = self.top_fill, - .radius = self.radius, - .clear = self.clear, - .feather = self.feather, - }; - } -}; +/// A window's Liquid Glass this frame (`windowGlass`, `viewports.windowGlass`). +pub const WindowGlassLook = platform.window.WindowGlassLook; /// The main window's Liquid Glass this frame, where it is a window of Liquid Glass (macOS 26): /// whether it is — its colour is then under the glass, and the frame draws no base of its own. @@ -176,418 +140,9 @@ pub fn windowGlass(win: *dvui.Window, look: WindowGlassLook) bool { } /// OS windows besides the main one, each showing a part of the one frame — a float popped out -/// (`plans/POPOUT_WINDOWS_PLAN.md`, the backend's `Viewport`). Fizzy's own backend only: on dvui's +/// (`plans/POPOUT_WINDOWS_PLAN.md`): the backend package's, for any dvui app on it. On dvui's own /// SDL3 backend (`-Dnative-backend=sdl3`) there are none, as on the web, and floats stay in. -pub const viewports = struct { - const Impl = @import("backend"); - pub const supported = @hasDecl(Impl, "viewportOpen"); - pub const Viewport = if (supported) Impl.Viewport else struct {}; - /// Physical pixels of the frame. - pub const Rect = if (supported) Impl.viewport_map.Rect else struct { x: f32 = 0, y: f32 = 0, w: f32 = 0, h: f32 = 0 }; - - /// Whether this run can open viewports: not on Wayland, where a window cannot be placed. - pub fn available() bool { - if (comptime !supported) return false; - return Impl.viewportsAvailable(); - } - - /// A viewport over `at` (the frame as the main window shows it), its window opening over that - /// place on the desktop, hidden until a frame is presented into it. Its own part of the frame - /// is `frameOf`. - pub fn open(at: Rect, title: [:0]const u8) ?*Viewport { - if (comptime !supported) return null; - return dvui.currentWindow().backend.impl.viewportOpen(at, title); - } - - /// A window to carry a view past every window of the app's, over the desktop: clear, the pointer - /// passing through it, above every window, placed and drawn as any viewport (`placeMain`, - /// `present`). Where it can be made (`carries`). - pub fn openCarry(at: Rect) ?*Viewport { - if (comptime !supported) return null; - return dvui.currentWindow().backend.impl.viewportOpenCarry(at); - } - - /// Whether one of the app's windows has the keyboard: the app is the active one. - pub fn appActive() bool { - if (comptime !supported) return true; - return Impl.appActive(); - } - - /// Whether menus can be windows of their own (`openMenu`): macOS, and Windows (Acrylic popups), - /// where viewports are. - pub const menus = supported and (builtin.os.tag == .macos or builtin.os.tag == .windows); - - /// A window for a menu (`Popout`'s menus): borderless, above every window, never focused, the - /// OS's material in it — rounded by `radius` points — and the pointer over it read in the main - /// window's frame (`mainOffset`). Placed with `placeRiding`, drawn as any viewport. `ride`: the - /// window the OS moves it with — smoothly, through a drag. `dialog`: a dialog's window, the same - /// but in its window's stacking, not above every window. - pub fn openMenu(at: Rect, radius: f32, ride: Ride, dialog: bool) ?*Viewport { - if (comptime !supported) return null; - return dvui.currentWindow().backend.impl.viewportOpenMenu(at, radius, ride, dialog); - } - - pub const Ride = if (supported) Impl.Ride else union(enum) { none, main, viewport: *Viewport }; - - /// `placeMain` for a window riding on another (`openMenu`'s `ride`), put somewhere new only when - /// `key` — where it lies over that window — changes. - pub fn placeRiding(vp: *Viewport, frame: Rect, key: Rect) Rect { - if (comptime !supported) return frame; - return dvui.currentWindow().backend.impl.viewportPlaceRiding(vp, frame, key); - } - - /// Where menu `vp` is drawn in the frame from where its window lies over the main window, - /// physical pixels: none for a menu of the main window's. - pub fn mainOffset(vp: *Viewport, offset: dvui.Point.Physical) void { - if (comptime !supported) return; - dvui.currentWindow().backend.impl.viewportMainOffset(vp, .{ .x = offset.x, .y = offset.y }); - } - - /// A piece of the OS's glass in an overlay (`overlayGlass`): a rounded rect, points from the - /// overlay window's top left. - pub const GlassShape = if (supported) Impl.GlassShape else extern struct { x: f64, y: f64, w: f64, h: f64, radius: f64, lit: f64, alpha: f64 }; - - /// Whether the OS has Liquid Glass to draw a view drag's glass with (`openOverlay`): macOS 26. - pub fn liquidGlass() bool { - if (comptime !supported) return false; - return Impl.liquidGlassAvailable(); - } - - /// A window over the display the main window is on (`displayInMain`) holding the OS's glass - /// (`overlayGlass`) under the picture presented into it, the pointer passing through it. Where - /// `liquidGlass`. - pub fn openOverlay(at: Rect) ?*Viewport { - if (comptime !supported) return null; - return dvui.currentWindow().backend.impl.viewportOpenOverlay(at); - } - - /// One of Liquid Glass's materials: its variant and style. - pub const GlassMaterial = struct { variant: i32, style: i32 }; - /// What an overlay's glass is: two layers of the same pieces, `under` and `over`, crossfaded by - /// `over_share` — each layer's pieces alike, for the OS to run them together. - pub const GlassLook = struct { - under: GlassMaterial, - over: GlassMaterial, - over_share: f32, - /// How much of the glass there is. - glass: f32 = 1, - /// The window's colour under the glass, each piece's shape, `fill_opacity` opaque; a lit - /// piece's lit by `lit_amount` toward `lit_toward`. - fill: dvui.Color = .black, - fill_opacity: f32 = 0, - lit_toward: dvui.Color = .white, - lit_amount: f32 = 0, - /// Each piece's clearing bevel (`core.glass_look.band`): a share of its shorter half, at - /// most `bevel_cap` points, clear for `bevel_clear` of it before the frost and the colour - /// come in. - bevel: f32 = 0, - bevel_cap: f32 = 0, - bevel_clear: f32 = 0, - /// Points of plain blur over the lens in each piece's body, in place of the `over` frost, - /// where the OS can blur what is behind a window without its materials' tint (macOS: Core - /// Animation's backdrop blur): 0, or none to draw it with, and the frost it is. - blur: f32 = 0, - }; - - /// Overlay `vp`'s glass this frame (`openOverlay`), run together within `spacing` points, as - /// `look`. - pub fn overlayGlass(vp: *Viewport, shapes: []const GlassShape, spacing: f32, look: GlassLook) void { - if (comptime !supported) return; - const f = look.fill; - const t = look.lit_toward; - dvui.currentWindow().backend.impl.viewportOverlayGlass(vp, shapes, spacing, .{ - .under_variant = look.under.variant, - .under_style = look.under.style, - .over_variant = look.over.variant, - .over_style = look.over.style, - .over_share = look.over_share, - .glass = look.glass, - .fill = .{ @as(f64, @floatFromInt(f.r)) / 255, @as(f64, @floatFromInt(f.g)) / 255, @as(f64, @floatFromInt(f.b)) / 255, look.fill_opacity }, - .lit_toward = .{ @as(f64, @floatFromInt(t.r)) / 255, @as(f64, @floatFromInt(t.g)) / 255, @as(f64, @floatFromInt(t.b)) / 255, look.lit_amount }, - .bevel = look.bevel, - .bevel_cap = look.bevel_cap, - .bevel_clear = look.bevel_clear, - .blur = look.blur, - }); - } - - /// The carried view's picture under an overlay's glass (`overlayPhoto`). - pub const OverlayPhoto = struct { - /// The rounded rect it shows in, points from the overlay window's top left. - rect: Rect, - radius: f32, - /// Where its image lies, points from `rect`'s top left: it may reach past it. - image: Rect, - fill: dvui.Color, - alpha: f32 = 1, - /// Points of plain blur on it. - blur: f32 = 0, - }; - - /// Overlay `vp`'s image of the carried view's picture: premultiplied RGBA rows, `w` by `h`, - /// copied; null, none. Once a drag (`overlayPhoto` places it). - pub fn overlayPhotoImage(vp: *Viewport, rgba: ?[]const u8, w: u32, h: u32) void { - if (comptime !supported) return; - dvui.currentWindow().backend.impl.viewportOverlayPhotoImage(vp, rgba, w, h); - } - - /// Where overlay `vp` shows the carried view's picture this frame, under its glass, so the - /// glass over it bends it; null, nowhere. Applied with the glass. - pub fn overlayPhoto(vp: *Viewport, photo: ?OverlayPhoto) void { - if (comptime !supported) return; - const p = photo orelse return dvui.currentWindow().backend.impl.viewportOverlayPhoto(vp, null); - const f = p.fill; - dvui.currentWindow().backend.impl.viewportOverlayPhoto(vp, .{ - .x = p.rect.x, - .y = p.rect.y, - .w = p.rect.w, - .h = p.rect.h, - .radius = p.radius, - .image = .{ p.image.x, p.image.y, p.image.w, p.image.h }, - .fill = .{ @as(f64, @floatFromInt(f.r)) / 255, @as(f64, @floatFromInt(f.g)) / 255, @as(f64, @floatFromInt(f.b)) / 255, @as(f64, @floatFromInt(f.a)) / 255 }, - .alpha = p.alpha, - .blur = p.blur, - }); - } - - /// The display the main window is on, in its frame (physical pixels from its top left). - pub fn displayInMain() Rect { - if (comptime !supported) return .{}; - return dvui.currentWindow().backend.impl.viewportDisplayInMain(); - } - - /// A carry window's shape this frame (`openCarry`): what it carries fills it, rounded by - /// `radius` physical pixels — the OS's material and shadow in that shape — `alpha` opaque. - /// Null: hidden. - pub fn carryShape(vp: *Viewport, radius: ?f32, alpha: f32) void { - if (comptime !supported) return; - dvui.currentWindow().backend.impl.viewportCarryShape(vp, radius, alpha); - } - - /// `vp`'s window at `other`'s level and just above it — a float born of a drop, over the drag's - /// glass while that goes — until `settle` puts it back at its own. macOS. - pub fn lift(vp: *Viewport, other: *Viewport) void { - if (comptime !supported) return; - dvui.currentWindow().backend.impl.viewportLift(vp, other); - } - - pub fn settle(vp: *Viewport) void { - if (comptime !supported) return; - dvui.currentWindow().backend.impl.viewportSettle(vp); - } - - /// `vp`'s window put just above `other`'s in the stacking (a growing float's picture over the - /// glass it grows out of). macOS. - pub fn orderAbove(vp: *Viewport, other: *Viewport) void { - if (comptime !supported) return; - dvui.currentWindow().backend.impl.viewportOrderAbove(vp, other); - } - - /// A carry window's glass as the lens — the carried view, a drop of water — or as frost, as a - /// window's material is (a float's window growing out of it). Liquid Glass only. - pub fn carryLens(vp: *Viewport, lens: bool) void { - if (comptime !supported) return; - dvui.currentWindow().backend.impl.viewportCarryLens(vp, lens); - } - - /// Whether `vp`'s window lies under the main window in the OS's stacking — a float's, clicked - /// behind it — as last read (macOS; never elsewhere, where floats stay over it). - pub fn underMain(vp: *Viewport) bool { - if (comptime !supported) return false; - return dvui.currentWindow().backend.impl.viewportUnderMain(vp); - } - - /// Points from `vp`'s window's left edge past the OS's own buttons in its title bar (macOS's - /// traffic lights, `os_buttons`): 0 without them. - pub fn buttonsWidth(vp: *Viewport) f32 { - if (comptime !supported) return 0; - return dvui.currentWindow().backend.impl.viewportButtonsWidth(vp); - } - - /// `vp`'s window's Liquid Glass this frame, as the main window's (`windowGlass`). Whether it - /// has any: its float then draws no base of its own. - pub fn windowGlass(vp: *Viewport, look: WindowGlassLook) bool { - if (comptime !supported) return false; - return dvui.currentWindow().backend.impl.viewportLiquidGlass(vp, look.native()); - } - - /// A float's window's corner radius, points: the OS's for a titled window (`os_frame`). - pub fn windowRadius() f32 { - if (comptime !supported) return 0; - return dvui.currentWindow().backend.impl.windowCornerRadius(); - } - - /// A held pointer over `vp`'s window is read as over the main window beneath it while `on`: its - /// float gone to its ghost while a view is carried out of it, aimed at the places under it. - pub fn seeThrough(vp: *Viewport, on: bool) void { - if (comptime !supported) return; - dvui.currentWindow().backend.impl.viewportSeeThrough(vp, on); - } - - /// Whether `vp`'s window is maximized — zoomed, or in a fullscreen Space of its own — as the - /// main window's is asked: nothing of the desktop behind it shows. - pub fn maximized(vp: *const Viewport) bool { - if (comptime !supported) return false; - return dvui.currentWindow().backend.impl.viewportMaximized(vp); - } - - /// Whether `vp`'s window covers the desktop, or will once the transition it is in ends, as the - /// main window's is asked (`coversDesktop`). - pub fn coversDesktop(vp: *const Viewport) bool { - if (comptime !supported) return false; - return dvui.currentWindow().backend.impl.viewportCovers(vp); - } - - /// Whether `vp`'s window is on its way into a fullscreen Space (`enteringSpace`). - pub fn enteringSpace(vp: *const Viewport) bool { - if (comptime !supported) return false; - return dvui.currentWindow().backend.impl.viewportEnteringSpace(vp); - } - - /// How far `vp`'s window is into full screen as it moves itself there or back (`spaceFullness`). - pub fn spaceFullness(vp: *const Viewport) ?f32 { - if (comptime !supported) return null; - return dvui.currentWindow().backend.impl.viewportSpaceFullness(vp); - } - - /// `vp`'s window `alpha` opaque, all of it — its material too. - pub fn fade(vp: *Viewport, alpha: f32) void { - if (comptime !supported) return; - dvui.currentWindow().backend.impl.viewportFade(vp, alpha); - } - - /// Whether a carried view can be shown past every window (`openCarry`): macOS. - pub const carries = supported and builtin.os.tag == .macos; - - pub fn close(vp: *Viewport) void { - if (comptime !supported) return; - dvui.currentWindow().backend.impl.viewportClose(vp); - } - - /// The part of the frame `vp` shows, physical pixels: in its band, past the main window. - pub fn frameOf(vp: *const Viewport) Rect { - if (comptime !supported) return .{}; - return vp.frame; - } - - /// Put `vp`'s window where it shows `frame`; the part of the frame it then shows. - pub fn place(vp: *Viewport, frame: Rect) Rect { - if (comptime !supported) return frame; - return dvui.currentWindow().backend.impl.viewportPlace(vp, frame); - } - - /// Hand `vp` this frame's picture of its part of the frame, drawn into `target`, or nothing. - pub fn present(vp: *Viewport, target: ?dvui.TextureTarget) void { - if (comptime !supported) return; - dvui.currentWindow().backend.impl.viewportPresent(vp, target); - } - - /// Where `vp`'s window is now, in the main window's part of the frame (physical pixels from - /// its top left): where its float goes when it comes back. - pub fn inMain(vp: *const Viewport) Rect { - if (comptime !supported) return .{}; - return dvui.currentWindow().backend.impl.viewportInMain(vp); - } - - /// Put `vp`'s window where it shows `frame` of the main window's frame (past its edge, for a - /// float split out under a drag); the part of the frame it then shows. - pub fn placeMain(vp: *Viewport, frame: Rect) Rect { - if (comptime !supported) return frame; - return dvui.currentWindow().backend.impl.viewportPlaceMain(vp, frame); - } - - /// `frame` of the main window's frame as the same desktop place in `vp`'s band. - pub fn bandFromMain(vp: *const Viewport, frame: Rect) Rect { - if (comptime !supported) return frame; - return dvui.currentWindow().backend.impl.viewportBandFromMain(vp, frame); - } - - /// Whether `vp`'s window has shown a frame yet. - pub fn shown(vp: *const Viewport) bool { - if (comptime !supported) return false; - return dvui.currentWindow().backend.impl.viewportShown(vp); - } - - /// Where a held pointer is read: by the window it is over, or pinned to the main window's - /// frame or a viewport's band while a window is moved or resized. - pub const Pin = if (supported) Impl.PointerPin else union(enum) { none, main, viewport: *Viewport }; - pub fn pinPointer(pin: Pin) void { - if (comptime !supported) return; - dvui.currentWindow().backend.impl.viewportPinPointer(pin); - } - - /// Whether the OS frames a viewport's window itself — its corners, its shadow, resizing from its - /// edges (Windows: DWM; macOS: a titled window, as the main window is) — so the window is - /// exactly the float's glass. Otherwise it is the glass with a clear - /// margin round it, which the float draws its own shadow in. - pub const os_frame = supported and (builtin.os.tag == .windows or builtin.os.tag == .macos); - - /// Whether a viewport's window has the OS's own buttons to close, minimize and zoom it — macOS's - /// traffic lights, a titled window's — so its float draws no close button of its own. - pub const os_buttons = supported and builtin.os.tag == .macos; - - /// A material behind the float's glass in `vp`'s window — its rounded rect `inset` physical - /// pixels in from the window's edge, `radius` its corners — for the float's frost to read the - /// desktop through, in the app's light or dark (`dark`). False where the platform has none - /// (yet). - pub fn glass(vp: *Viewport, inset: f32, radius: f32, dark: bool) bool { - if (comptime !supported) return false; - return dvui.currentWindow().backend.impl.viewportGlass(vp, inset, radius, dark); - } - - - /// The OS asked to close `vp`'s window. - pub fn closeRequested(vp: *const Viewport) bool { - if (comptime !supported) return false; - return vp.close_requested; - } - - /// Where a press on `vp`'s window is the OS's, from its float this frame (physical pixels of - /// the frame): `drag` its header, less `keep` (its close button), moves the window; `edge` in - /// from `glass`'s sides resizes it. Null: all of it is the app's. - /// `app_side` / `app_corner`: the float's own resize zones, the app's over its header where - /// the OS resizes from no edge (macOS). - pub const Hints = struct { drag: Rect, keep: Rect, glass: Rect, edge: f32, app_side: f32 = 0, app_corner: f32 = 0 }; - pub fn hints(vp: *Viewport, h: ?Hints) void { - if (comptime !supported) return; - dvui.currentWindow().backend.impl.viewportHints(vp, if (h) |x| .{ .drag = x.drag, .keep = x.keep, .glass = x.glass, .edge = x.edge, .app_side = x.app_side, .app_corner = x.app_corner } else null); - } - - /// Where `vp`'s window shows in the frame now, when the OS has moved or resized it since the - /// last ask — where its float goes. - pub fn osPlaced(vp: *Viewport) ?Rect { - if (comptime !supported) return null; - return dvui.currentWindow().backend.impl.viewportOsPlaced(vp); - } - - /// The press the OS took to move or resize `vp`'s window was let go; `resized` unless the - /// window was only moved. - pub const MoveEnd = struct { resized: bool }; - pub fn osMoveEnded(vp: *Viewport) ?MoveEnd { - if (comptime !supported) return null; - const e = dvui.currentWindow().backend.impl.viewportOsMoveEnded(vp) orelse return null; - return .{ .resized = e.resized }; - } - - /// Hand the drag under way to the OS, which moves `vp`'s window from then on. False where it - /// is not done: the app goes on moving it. - pub fn dragMove(vp: *Viewport) bool { - if (comptime !supported) return false; - return dvui.currentWindow().backend.impl.viewportDragMove(vp); - } - - /// What `vp`'s window is called: in the taskbar, the Window menu, the window switcher. - pub fn setTitle(vp: *Viewport, text: []const u8) void { - if (comptime !supported) return; - dvui.currentWindow().backend.impl.viewportTitle(vp, text); - } - - /// The least the OS may resize `vp`'s window to, physical pixels. - pub fn minSize(vp: *Viewport, w: f32, h: f32) void { - if (comptime !supported) return; - dvui.currentWindow().backend.impl.viewportMinSize(vp, w, h); - } -}; +pub const viewports = if (@hasDecl(@import("backend"), "viewports")) @import("backend").viewports else @import("viewports_none"); /// Fizzy keeps the window's geometry in `layout.zon`, beside its regions — one file for where the /// window and everything in it were left, and the file it has always kept the frame in. diff --git a/src/backend/backend_web.zig b/src/backend/backend_web.zig index d426e819..69a5a2cb 100644 --- a/src/backend/backend_web.zig +++ b/src/backend/backend_web.zig @@ -139,154 +139,9 @@ pub const loadTree = layout_file.loadTree; pub fn macosLaunchComplete() void {} /// Symmetric with the native API: a page has one canvas, so a float never leaves it. -pub const viewports = struct { - pub const supported = false; - pub fn available() bool { - return false; - } - pub const Viewport = struct {}; - pub const Rect = struct { x: f32 = 0, y: f32 = 0, w: f32 = 0, h: f32 = 0 }; +pub const viewports = @import("viewports_none"); - pub fn open(_: Rect, _: [:0]const u8) ?*Viewport { - return null; - } - pub fn close(_: *Viewport) void {} - pub fn frameOf(_: *const Viewport) Rect { - return .{}; - } - pub fn place(_: *Viewport, frame: Rect) Rect { - return frame; - } - pub fn present(_: *Viewport, _: ?dvui.TextureTarget) void {} - pub fn inMain(_: *const Viewport) Rect { - return .{}; - } - pub const os_frame = false; - pub const os_buttons = false; - pub const carries = false; - pub fn seeThrough(_: *Viewport, _: bool) void {} - pub fn fade(_: *Viewport, _: f32) void {} - pub fn maximized(_: *const Viewport) bool { - return false; - } - pub fn coversDesktop(_: *const Viewport) bool { - return false; - } - pub fn enteringSpace(_: *const Viewport) bool { - return false; - } - pub fn spaceFullness(_: *const Viewport) ?f32 { - return null; - } - pub fn carryShape(_: *Viewport, _: ?f32, _: f32) void {} - pub fn carryLens(_: *Viewport, _: bool) void {} - pub fn windowGlass(_: *Viewport, _: WindowGlassLook) bool { - return false; - } - pub fn underMain(_: *Viewport) bool { - return false; - } - pub fn buttonsWidth(_: *Viewport) f32 { - return 0; - } - pub fn windowRadius() f32 { - return 0; - } - pub fn openCarry(_: Rect) ?*Viewport { - return null; - } - pub const menus = false; - pub fn appActive() bool { - return true; - } - pub const Ride = union(enum) { none, main, viewport: *Viewport }; - pub fn orderAbove(_: *Viewport, _: *Viewport) void {} - pub fn lift(_: *Viewport, _: *Viewport) void {} - pub fn settle(_: *Viewport) void {} - pub fn placeRiding(_: *Viewport, frame: Rect, _: Rect) Rect { - return frame; - } - pub fn openMenu(_: Rect, _: f32, _: Ride, _: bool) ?*Viewport { - return null; - } - pub fn mainOffset(_: *Viewport, _: dvui.Point.Physical) void {} - pub const GlassShape = extern struct { x: f64, y: f64, w: f64, h: f64, radius: f64, lit: f64, alpha: f64, frost: f64 }; - pub fn liquidGlass() bool { - return false; - } - pub fn openOverlay(_: Rect) ?*Viewport { - return null; - } - pub const GlassMaterial = struct { variant: i32, style: i32 }; - pub const GlassLook = struct { - under: GlassMaterial, - over: GlassMaterial, - over_share: f32, - glass: f32 = 1, - fill: dvui.Color = .black, - fill_opacity: f32 = 0, - lit_toward: dvui.Color = .white, - lit_amount: f32 = 0, - bevel: f32 = 0, - bevel_cap: f32 = 0, - bevel_clear: f32 = 0, - blur: f32 = 0, - }; - pub fn overlayGlass(_: *Viewport, _: []const GlassShape, _: f32, _: GlassLook) void {} - pub const OverlayPhoto = struct { rect: Rect, radius: f32, image: Rect, fill: dvui.Color, alpha: f32 = 1, blur: f32 = 0 }; - pub fn overlayPhotoImage(_: *Viewport, _: ?[]const u8, _: u32, _: u32) void {} - pub fn overlayPhoto(_: *Viewport, _: ?OverlayPhoto) void {} - pub fn displayInMain() Rect { - return .{}; - } - pub const Hints = struct { drag: Rect, keep: Rect, glass: Rect, edge: f32, app_side: f32 = 0, app_corner: f32 = 0 }; - pub fn hints(_: *Viewport, _: ?Hints) void {} - pub fn osPlaced(_: *Viewport) ?Rect { - return null; - } - pub const MoveEnd = struct { resized: bool }; - pub fn osMoveEnded(_: *Viewport) ?MoveEnd { - return null; - } - pub fn dragMove(_: *Viewport) bool { - return false; - } - pub fn minSize(_: *Viewport, _: f32, _: f32) void {} - pub fn setTitle(_: *Viewport, _: []const u8) void {} - pub fn glass(_: *Viewport, _: f32, _: f32, _: bool) bool { - return false; - } - pub fn placeMain(_: *Viewport, frame: Rect) Rect { - return frame; - } - pub fn bandFromMain(_: *const Viewport, frame: Rect) Rect { - return frame; - } - pub fn shown(_: *const Viewport) bool { - return false; - } - pub const Pin = union(enum) { none, main, viewport: *Viewport }; - pub fn pinPointer(_: Pin) void {} - pub fn closeRequested(_: *const Viewport) bool { - return false; - } -}; - -pub const WindowGlassLook = struct { - under_variant: i32, - under_style: i32, - over_variant: i32, - over_style: i32, - frost: f32, - blur: f32, - glass: f32, - fill: dvui.Color, - fill_opacity: f32, - top_fill: f32, - radius: f32, - clear: f32, - feather: f32, -}; +pub const WindowGlassLook = viewports.WindowGlassLook; pub fn windowGlass(_: *dvui.Window, _: WindowGlassLook) bool { return false; }