diff --git a/README.md b/README.md index af58ceb..6fec260 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,13 @@ prints what is on screen as text: the names a test or an automation client reads pixels. It depends only on the SDK package. `zig build run-dvui` is a plain dvui app on fizzy's backend (`dvui/main.zig`): dvui's own -widgets, and each `dvui.floatingWindow` an OS window of its own, moved, resized and closed by the -OS like any window. `fizzy.addDvui` gives it dvui, the backend and the backend's `viewports`. -`-Ddvui-backend=sdl3` builds the same app on dvui's own SDL3 backend, where there are no OS -windows besides the main one and the floating windows stay in it, as in any dvui app. Menus and -dialogs as OS windows of their own come later. +widgets and its demo, and OS windows opened with dvui's own `dvui.osWindow` — nothing but dvui in +the app's code. On fizzy's backend each is drawn from the one `dvui.Window` and looks as a dvui +floating window does (its header and close button, no OS title bar); the OS moves it by its +header, and it resizes from its edges. `fizzy.addDvui` gives the app dvui and the backend. +`-Ddvui-backend=sdl3` builds the same code on dvui's own SDL3 backend, where `dvui.osWindow` +opens a `dvui.Window` of its own, as dvui does anywhere. Menus and dialogs as OS windows of their +own come later. ## Shapes diff --git a/build.zig b/build.zig index e124512..1f9f1ff 100644 --- a/build.zig +++ b/build.zig @@ -6,9 +6,9 @@ //! `zig build run-replay` builds a plain dvui app, nothing of fizzy, with tape playback from the //! plugin SDK's `replay`: it plays a tape into its own window and prints what is on screen. //! -//! `zig build run-dvui` builds a plain dvui app on fizzy's backend: dvui's own widgets, each -//! floating window an OS window of its own. `-Ddvui-backend=sdl3` builds the same app on dvui's own -//! SDL3 backend, where the floating windows stay in the main window. +//! `zig build run-dvui` builds a plain dvui app on fizzy's backend: dvui's own widgets, and OS +//! windows opened with `dvui.osWindow`. `-Ddvui-backend=sdl3` builds the same app on dvui's own +//! SDL3 backend, where `dvui.osWindow` opens a `dvui.Window` of its own. const std = @import("std"); const fizzy = @import("fizzy"); const fizzy_sdk = @import("fizzy_sdk"); @@ -95,9 +95,9 @@ pub fn build(b: *std.Build) !void { if (b.args) |args| run_replay.addArgs(args); b.step("run-replay", "Run the replay app: a plain dvui app with tape playback").dependOn(&run_replay.step); - // A plain dvui app on fizzy's backend (`dvui/main.zig`): dvui and the backend from fizzy, the - // backend's `viewports` for its floating windows' OS windows. Not part of the default install. - const dvui_backend = b.option(fizzy.NativeBackend, "dvui-backend", "run-dvui: fizzy (OS windows) or sdl3 (dvui's own, the main window only)") orelse .fizzy; + // A plain dvui app on fizzy's backend (`dvui/main.zig`): dvui and the backend from fizzy, its + // `dvui.osWindow`s OS windows of their own there. Not part of the default install. + const dvui_backend = b.option(fizzy.NativeBackend, "dvui-backend", "run-dvui: fizzy (fizzy's backend) or sdl3 (dvui's own)") orelse .fizzy; const dvui_exe = b.addExecutable(.{ .name = "dvui-app", .root_module = b.createModule(.{ @@ -112,5 +112,5 @@ pub fn build(b: *std.Build) !void { const run_dvui = b.addRunArtifact(dvui_exe); run_dvui.step.dependOn(&install_dvui.step); if (b.args) |args| run_dvui.addArgs(args); - b.step("run-dvui", "Run a plain dvui app on fizzy's backend, its floating windows OS windows").dependOn(&run_dvui.step); + b.step("run-dvui", "Run a plain dvui app on fizzy's backend, its dvui.osWindows OS windows").dependOn(&run_dvui.step); } diff --git a/build.zig.zon b/build.zig.zon index 89e1808..91e1f87 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -8,8 +8,8 @@ // by commit; fizzy's CI builds this repo against each of its pull requests, so a change // there that breaks an outside app fails before it lands. .fizzy = .{ - .url = "https://github.com/fizzyedit/fizzy/archive/189c7d5dac1b.tar.gz", - .hash = "fizzy-0.2.0-c46aVg6TjQCmiypW-eerXKRnlDgof9ss5ErBTUtH0aHO", + .url = "https://github.com/fizzyedit/fizzy/archive/75fd7f0ed1440432893238f75664e9680a56a123.tar.gz", + .hash = "fizzy-0.2.0-c46aVj4MkABq503RNbfyqugEU7A-BkJreTBFDhNbl6uF", }, // The plugins this app bundles beyond fizzy's own: fizzyedit/example-plugin, as any // plugin from its own repo, and this app's own, which also builds alone as a dylib diff --git a/dvui/main.zig b/dvui/main.zig index 047f5a7..ba66263 100644 --- a/dvui/main.zig +++ b/dvui/main.zig @@ -1,34 +1,16 @@ -//! A plain dvui app on fizzy's backend: dvui's own widgets and its demo, nothing of fizzy the -//! editor, and each floating window an OS window of its own where the backend has them -//! (`viewports`), looking just as it does in the main window: its own header and close button, no -//! OS title bar (`.app`). The main window is an ordinary OS window. On a backend without them — -//! dvui's own SDL3 backend (`-Ddvui-backend=sdl3`), or Wayland, where a window cannot be placed — -//! the same floating windows stay in the main window, as in any dvui app. +//! A plain dvui app on fizzy's backend: dvui's own widgets, its demo, and OS windows of its own +//! through `dvui.osWindow` — nothing of fizzy the editor, and nothing but dvui in the app's code. //! -//! There stays one `dvui.Window` and one frame. A floating window that is out is drawn in its -//! viewport's band of the frame, far past the main window's edge, and its OS window shows that -//! band; the pointer over the OS window comes back to dvui as a pointer in the band. Any floating -//! window can go out, the app's own or dvui's demo: dvui keeps where each is (`_rect` in its data, -//! by its id), and the app moves that into the band and reads it back. What it takes, each frame: +//! On fizzy's backend each `dvui.osWindow` is a floating window in the one frame, shown in an OS +//! window of its own that looks just as the floating window does in the main window: dvui's header +//! and close button, no OS title bar. The OS moves it by its header; it resizes itself from its +//! edges, as a floating window does, and its window follows. The backend does all of it +//! (`backend/src/os_windows.zig` in fizzy). On dvui's own SDL3 backend (`-Ddvui-backend=sdl3`) +//! `dvui.osWindow` opens a `dvui.Window` of its own instead, as dvui does anywhere. //! -//! - before drawing (`beforeFloats`): a window the OS moved takes its floating window with it -//! (`osPlaced`), and each band is a screen of dvui's (`dvui.screensSet`), so dvui keeps the -//! floating window in it; -//! - drawing, as dvui draws anywhere, naming the floating windows to put out as they draw (`out`); -//! - after drawing (`afterFloats`): dvui's dialogs and toasts made (`Window.drawRetained`); a -//! window whose floating window was not drawn — closed — goes; each one named and not yet out -//! goes into a viewport over where it is (`viewports.open`); and each window is put where its -//! floating window is (`viewports.place`), told where its header is for the OS to move it by -//! (`hints`), and handed its picture: every subwindow in its band, taken out of the frame -//! (`viewports.Picture`), so the main window draws none of it. -//! -//! A floating window resizes itself from its edges, as dvui's does anywhere, and its OS window -//! follows (`place`); while it holds the pointer the pointer is read in its band, wherever it goes -//! (`pinPointer`). +//! dvui's demo is a floating window, and stays one: in the main window, as in dvui's own example. const std = @import("std"); const dvui = @import("dvui"); -const backend = @import("backend"); -const viewports = if (@hasDecl(backend, "viewports")) backend.viewports else @import("viewports_none"); pub const dvui_app: dvui.App = .{ .config = .{ .options = .{ @@ -41,60 +23,38 @@ pub const main = dvui.App.main; pub const panic = dvui.App.panic; pub const std_options: std.Options = .{ .logFn = dvui.App.logFn }; -/// The most floating windows out at once: as many as the backend has viewports. Past what it can -/// open, a floating window stays in the main window. -const max_outs = 8; - -/// A floating window out in an OS window of its own, by its dvui id. -const Out = struct { - id: dvui.Id, - viewport: *viewports.Viewport, - target: ?dvui.Texture.Target = null, - /// Opened this frame: drawn in the main window still, in its band from the next. - fresh: bool = true, -}; -var outs: [max_outs]?Out = @splat(null); - -/// The floating windows to put out, named as they draw this frame (`out`). -const Wanted = struct { id: dvui.Id, title: [:0]const u8 }; -var wanted: [max_outs]Wanted = undefined; -var wanted_n: usize = 0; - -/// How tall `dvui.windowHeader` is, natural: the strip the OS moves a window by. Measured on the -/// app's own floating windows; dvui's demo draws the same header. -var header_h: f32 = 32; - -/// A floating window of the app's own. dvui keeps where it is, as for any floating window. -const Float = struct { +/// An OS window of the app's own. +const Window = struct { open: bool = true, - /// Where it first opens, natural, in the main window. - at: dvui.Rect, title_buf: [32:0]u8 = @splat(0), title_len: usize = 0, count: u32 = 0, - fn title(self: *const Float) [:0]const u8 { + fn title(self: *const Window) [:0]const u8 { return self.title_buf[0..self.title_len :0]; } }; -var floats: [4]?Float = @splat(null); +var windows: [4]?Window = @splat(null); var made: u32 = 0; var started = false; fn frame() !dvui.App.Result { - // dvui's demo and two floating windows of the app's own to begin with. + // dvui's demo and two OS windows to begin with. if (!started) { started = true; dvui.Examples.show_demo_window = true; - newFloat(); - newFloat(); + newWindow(); + newWindow(); } - beforeFloats(); mainWindow(); - for (&floats, 0..) |*slot, i| if (slot.*) |*f| drawFloat(f, i); + for (&windows, 0..) |*slot, i| if (slot.*) |*w| { + if (!w.open) { + slot.* = null; + continue; + } + osWindow(w, i); + }; dvui.Examples.demo(.lite); - if (dvui.Examples.show_demo_window) if (demoWindow()) |id| out(id, "DVUI Demo"); - afterFloats(); return .ok; } @@ -102,183 +62,33 @@ fn mainWindow() void { var box = dvui.box(@src(), .{}, .{ .expand = .both, .padding = .all(16) }); defer box.deinit(); dvui.labelNoFmt(@src(), "dvui's own widgets on fizzy's backend.", .{}, .{ .font = .theme(.title) }); - const how = if (viewports.supported and viewports.available()) - "Each floating window is an OS window of its own: move it, resize it, put it on another display." - else - "This backend has no OS windows besides this one, so floating windows stay in it."; - dvui.labelNoFmt(@src(), how, .{}, .{}); - if (dvui.button(@src(), "New floating window", .{}, .{ .margin = .{ .y = 12 } })) newFloat(); + dvui.labelNoFmt(@src(), "Each window opened here is a dvui.osWindow.", .{}, .{}); + if (dvui.button(@src(), "New window", .{}, .{ .margin = .{ .y = 12 } })) newWindow(); if (!dvui.Examples.show_demo_window) { if (dvui.button(@src(), "Show the dvui demo", .{}, .{})) dvui.Examples.show_demo_window = true; } } -fn newFloat() void { - const slot = for (&floats) |*slot| { +fn newWindow() void { + const slot = for (&windows) |*slot| { if (slot.* == null) break slot; } else return; made += 1; - const step: f32 = @floatFromInt(made % 6); - slot.* = .{ .at = .{ .x = 80 + 32 * step, .y = 140 + 24 * step, .w = 280, .h = 180 } }; - const f = &slot.*.?; - const t: []const u8 = std.fmt.bufPrint(&f.title_buf, "Floating window {d}", .{made}) catch ""; - f.title_len = t.len; + slot.* = .{}; + const w = &slot.*.?; + const t: []const u8 = std.fmt.bufPrint(&w.title_buf, "Window {d}", .{made}) catch ""; + w.title_len = t.len; } -fn drawFloat(f: *Float, i: usize) void { - var fw = dvui.floatingWindow(@src(), .{ .open_flag = &f.open }, .{ .id_extra = i, .rect = f.at, .min_size_content = .{ .w = 220, .h = 120 } }); - defer fw.deinit(); - const header = dvui.windowHeader(f.title(), "", &f.open); - // Moved by its header only, as dvui's demos do: elsewhere a press is its content's. - fw.dragAreaSet(header); - header_h = header.h / dvui.windowNaturalScale(); - out(fw.data().id, f.title()); - // Closed by its X: its window goes next frame, which comes now rather than at the next event. - if (!f.open) dvui.refresh(null, @src(), null); +fn osWindow(w: *Window, i: usize) void { + var os = dvui.osWindow(@src(), .{ + .title = w.title(), + .size = .{ .w = 280, .h = 180 }, + .min_size = .{ .w = 220, .h = 120 }, + }, .{ .id_extra = i, .open_flag = &w.open }); + defer os.deinit(); var box = dvui.box(@src(), .{}, .{ .expand = .both, .padding = .all(12) }); defer box.deinit(); - dvui.label(@src(), "Clicked {d} times", .{f.count}, .{}); - if (dvui.button(@src(), "Count", .{}, .{})) f.count += 1; -} - -/// dvui's demo's floating window, by its id: the subwindow its tag's rect is. dvui gives a floating -/// window's `tag` to the box it draws in, which fills it, not to the window itself. -fn demoWindow() ?dvui.Id { - const t = dvui.tagGet(dvui.Examples.demo_window_tag) orelse return null; - if (t.rect.w < 1 or t.rect.h < 1) return null; - for (dvui.currentWindow().subwindows.stack.items) |sw| { - const r = sw.rect_pixels; - if (@abs(r.x - t.rect.x) < 1 and @abs(r.y - t.rect.y) < 1 and @abs(r.w - t.rect.w) < 1 and @abs(r.h - t.rect.h) < 1) return sw.id; - } - return null; -} - -/// Put floating window `id` out in an OS window of its own, called `title` there: named each frame -/// it draws. -fn out(id: dvui.Id, title: [:0]const u8) void { - if (wanted_n == wanted.len) return; - wanted[wanted_n] = .{ .id = id, .title = title }; - wanted_n += 1; -} - -fn outOf(id: dvui.Id) ?*Out { - for (&outs) |*slot| if (slot.*) |*o| if (o.id == id) return o; - return null; -} - -fn release(o: *Out) void { - viewports.close(o.viewport); - if (o.target) |t| t.destroyLater(); -} - -fn natural(r: viewports.Rect, s: f32) dvui.Rect { - return .{ .x = r.x / s, .y = r.y / s, .w = r.w / s, .h = r.h / s }; -} - -fn physical(r: dvui.Rect, s: f32) viewports.Rect { - return .{ .x = r.x * s, .y = r.y * s, .w = r.w * s, .h = r.h * s }; -} - -fn beforeFloats() void { - wanted_n = 0; - for (&floats) |*slot| if (slot.*) |f| if (!f.open) { - slot.* = null; - }; - if (comptime !viewports.supported) return; - const s = dvui.windowNaturalScale(); - var screens: [max_outs]dvui.Rect.Natural = undefined; - var n: usize = 0; - var pin: viewports.Pin = .none; - for (&outs) |*slot| { - const o = if (slot.*) |*o| o else continue; - // The OS moved its window: the floating window goes with it. - if (viewports.osPlaced(o.viewport)) |fr| dvui.dataSet(null, o.id, "_rect", natural(fr, s)); - _ = viewports.osMoveEnded(o.viewport); - const r = dvui.dataGet(null, o.id, "_rect", dvui.Rect) orelse continue; - screens[n] = .{ .x = r.x, .y = r.y, .w = r.w, .h = r.h }; - n += 1; - // Resized from its edges, it holds the pointer: read in its band till it lets go, even - // past its window's edge. - if (dvui.captured(o.id)) pin = .{ .viewport = o.viewport }; - } - dvui.screensSet(screens[0..n]); - viewports.pinPointer(pin); -} - -fn afterFloats() void { - if (comptime !viewports.supported) return; - const cw = dvui.currentWindow(); - // Dialogs and toasts are subwindows from here, so each lands in the window it is over. - cw.drawRetained(.{}); - const s = dvui.windowNaturalScale(); - - // One no longer named — its X cleared what showed it, as dvui's demo's does — goes next frame, - // which comes now rather than at the next event. - for (outs) |slot| if (slot) |o| { - const named = for (wanted[0..wanted_n]) |w| { - if (w.id == o.id) break true; - } else false; - if (!named) dvui.refresh(null, @src(), null); - }; - // A window whose floating window was not drawn this frame — closed — goes. - for (&outs) |*slot| { - const o = if (slot.*) |*o| o else continue; - const drawn = for (cw.subwindows.stack.items) |sw| { - if (sw.id == o.id) break sw.used; - } else false; - if (drawn) continue; - release(o); - slot.* = null; - } - - // Each floating window named and not out yet goes into a window of its own, opening over - // where it is in the main window; it is drawn in that window's band from the next frame. - for (wanted[0..wanted_n]) |w| { - if (outOf(w.id) != null) continue; - if (!viewports.available()) break; - const slot = for (&outs) |*slot| { - if (slot.* == null) break slot; - } else break; - const r = dvui.dataGet(null, w.id, "_rect", dvui.Rect) orelse continue; - // Not on its first frame, which dvui spends measuring it, hidden, with no size yet. - if (r.w < 1 or r.h < 1) continue; - const vp = viewports.open(physical(r, s), w.title, .app) orelse break; - slot.* = .{ .id = w.id, .viewport = vp }; - dvui.dataSet(null, w.id, "_rect", natural(viewports.frameOf(vp), s)); - dvui.refresh(null, @src(), null); - } - - for (&outs) |*slot| { - const o = if (slot.*) |*o| o else continue; - if (o.fresh) { - o.fresh = false; - continue; - } - const r = dvui.dataGet(null, o.id, "_rect", dvui.Rect) orelse continue; - const shown = viewports.place(o.viewport, physical(r, s)); - // The OS moves its window by its header — the top `header_h` — but for the close button, - // square at the header's height: at its right end where buttons go OK then Cancel - // (Windows), else at its left (`dvui.windowHeader`); and not from the floating window's - // own resize zones round its edge (`app_side`). - const hh = header_h * s; - const keep_x = if (cw.button_order == .ok_cancel) shown.x + shown.w - hh else shown.x; - viewports.hints(o.viewport, .{ - .drag = .{ .x = shown.x, .y = shown.y, .w = shown.w, .h = hh }, - .keep = .{ .x = keep_x, .y = shown.y, .w = hh, .h = hh }, - .glass = shown, - .edge = 0, - .app_side = 6 * s, - .app_corner = 15 * s, - }); - const w: u32 = @intFromFloat(@max(1, @round(shown.w))); - const h: u32 = @intFromFloat(@max(1, @round(shown.h))); - const target = viewports.sizedTarget(&o.target, w, h) orelse continue; - const area: dvui.Rect.Physical = .{ .x = shown.x, .y = shown.y, .w = shown.w, .h = shown.h }; - const picture: viewports.Picture = .begin(target, shown); - for (cw.subwindows.stack.items) |*sw| { - if (area.contains(sw.rect_pixels.center())) picture.subwindow(sw, true); - } - picture.end(); - viewports.present(o.viewport, target); - } + dvui.label(@src(), "Clicked {d} times", .{w.count}, .{}); + if (dvui.button(@src(), "Count", .{}, .{})) w.count += 1; }