DVUI

Parameters

#
self:*FloatingWindowWidget
*FloatingWindowWidget
src:std.builtin.SourceLocation
std.builtin.SourceLocation
init_opts:InitOptions
InitOptions
opts:Options
Options

Source

Implementation

#
pub fn init(self: *FloatingWindowWidget, src: std.builtin.SourceLocation, init_opts: InitOptions, opts: Options) void {
    const options = defaults.override(opts);
    var box_options = options;
    box_options.role = null;
    box_options.label = null;
    box_options.id_extra = null;
    box_options.rect = null; // if the user passes in a rect, don't pass it to the BoxWidget

    self.* = .{
        // options is really for our embedded BoxWidget, so save them for the
        // end of drawBackground()
        .options = box_options,

        // the floating window itself doesn't have any styling, it comes from
        // the embedded BoxWidget
        .wd = WidgetData.init(src, .{ .subwindow = true }, .{
            .id_extra = opts.id_extra,
            // passing options.rect will stop WidgetData.init from calling rectFor
            // which is important because we are outside normal layout
            .rect = .{},
            .role = options.role,
            .label = options.label,
            .name = options.name,
        }),
        .init_options = init_opts,
    };

    // by default we store the rect (only while the window is open)
    self.wd.rect = dvui.dataGet(null, self.wd.id, "_rect", Rect) orelse Rect{};

    if (self.init_options.rect) |ior| {
        // user is storing the rect for us across open/close
        self.wd.rect = ior.*;
    }

    if (dvui.firstFrame(self.wd.id)) {
        // Options.rect only affects initial position/size
        if (opts.rect) |r| {
            self.wd.rect = r;
        }
    }

    if (dvui.dataGet(null, self.wd.id, "_auto_size", @TypeOf(self.auto_size))) |as| {
        self.auto_size = as;
    } else {
        if (self.data().rect.w == 0 and self.wd.rect.h == 0) {
            self.autoSize();
        }
    }

    if (dvui.dataGet(null, self.wd.id, "_auto_pos", @TypeOf(self.auto_pos))) |ap| {
        self.auto_pos = ap;
    } else {
        self.auto_pos = (self.wd.rect.x == 0 and self.wd.rect.y == 0);
    }

    if (dvui.minSizeGet(self.wd.id)) |min_size| {
        if (self.auto_size) {
            // Track if any of our children called refresh(), and in deinit we
            // will turn off auto_size if none of them did.
            self.auto_size_refresh_prev_value = dvui.currentWindow().extra_frames_needed;
            dvui.currentWindow().extra_frames_needed = 0;

            const ms = Size.min(Size.max(min_size, self.options.min_sizeGet()), .cast(dvui.windowRect().size()));
            self.wd.rect.w = ms.w;
            self.wd.rect.h = ms.h;
        }

        if (self.auto_pos) {
            // only position ourselves once by default
            self.auto_pos = false;

            const centering: Rect.Natural = self.init_options.center_on orelse dvui.currentWindow().subwindows.current_rect;
            self.wd.rect.x = centering.x + (centering.w - self.wd.rect.w) / 2;
            self.wd.rect.y = centering.y + (centering.h - self.wd.rect.h) / 2;

            if (dvui.snapToPixels()) {
                const s = dvui.windowNaturalScale();
                self.wd.rect.x = @round(self.wd.rect.x * s) / s;
                self.wd.rect.y = @round(self.wd.rect.y * s) / s;
            }

            if (self.init_options.window_avoid != .none) {
                if (self.wd.rect.topLeft().equals(.cast(centering.topLeft()))) {
                    // if we ended up directly on top, nudge downright a bit
                    self.wd.rect.x += 24;
                    self.wd.rect.y += 24;
                }
            }

            if (self.init_options.window_avoid == .nudge) {
                const cw = dvui.currentWindow();

                // we might nudge onto another window, so have to keep checking until we don't
                var nudge = true;
                while (nudge) {
                    nudge = false;
                    // don't check against subwindows[0] - that's that main window
                    for (cw.subwindows.stack.items[1..]) |subw| {
                        if (subw.id != self.wd.id and subw.rect.topLeft().equals(self.data().rect.topLeft())) {
                            self.wd.rect.x += 24;
                            self.wd.rect.y += 24;
                            nudge = true;
                        }
                    }
                }
            }

            //std.debug.print("autopos to {}\n", .{self.data().rect});
        }

        // always make sure we are on the screen
        var screen = dvui.windowRect();
        // okay if we are off the left or right but still see some
        const offleft = self.wd.rect.w - 48;
        screen.x -= offleft;
        screen.w += offleft + offleft;
        // okay if we are off the bottom but still see the top
        screen.h += self.wd.rect.h - 24;
        self.wd.rect = .cast(dvui.placeOnScreen(screen, .{}, .none, .cast(self.data().rect)));
    }

    self.data().register();

    if (dvui.firstFrame(self.data().id)) {
        dvui.focusSubwindow(self.data().id, null);

        // write back before we hide ourselves for the first frame
        dvui.dataSet(null, self.data().id, "_rect", self.data().rect);
        if (self.init_options.rect) |ior| {
            // send rect back to user
            ior.* = self.data().rect;
        }

        // need a second frame to fit contents
        dvui.refresh(null, @src(), self.data().id);

        // hide our first frame so the user doesn't see an empty window or
        // jump when we autopos/autosize
        self.data().rect.w = 0;
        self.data().rect.h = 0;
    }

    if (dvui.captured(self.data().id)) {
        if (dvui.dataGet(null, self.data().id, "_drag_part", DragPart)) |dp| {
            self.drag_part = dp;
        }
    }

    dvui.parentSet(self.widget());

    // standard subwindow stuff
    {
        const rs = self.data().rectScale();
        self.render_ftb.initReset();
        self.prev_windowInfo = dvui.subwindowCurrentSet(self.data().id, .cast(self.data().rect));
        dvui.subwindowAdd(self.data().id, self.data().rect, rs.r, self.init_options.modal, if (self.init_options.stay_above_parent_window) self.prev_windowInfo.id else null, true);
        dvui.captureMouseMaintain(.{ .id = self.data().id, .rect = rs.r, .subwindow_id = self.data().id });
        self.prevClip = dvui.clipGet();
        dvui.clipSet(dvui.windowRectPixels()); // break out of whatever clipping we were in
        self.prev_scroll = dvui.ScrollContainerWidget.scrollSet(null);
    }

    // prevents parents from processing key events if focus is inside the floating window
    self.prev_last_focus = dvui.lastFocusedIdInFrame();

    self.drag_area = self.data().rectScale().r;

    if (self.data().accesskit_node()) |ak_node| {
        if (self.init_options.modal)
            AccessKit.nodeSetModal(ak_node)
        else
            AccessKit.nodeClearModal(ak_node);
        AccessKit.nodeAddAction(ak_node, AccessKit.Action.focus);
    }

    dvui.toastsShow(self.data().id, .cast(self.data().rect));
}