DVUI

slider

returns true if fraction (0-1) was changed

Parameters

#
src:std.builtin.SourceLocation
std.builtin.SourceLocation
init_opts:SliderInitOptions
SliderInitOptions
opts:Options
Options

Source

Implementation

#
pub fn slider(src: std.builtin.SourceLocation, init_opts: SliderInitOptions, opts: Options) bool {
    std.debug.assert(init_opts.fraction.* >= 0);
    std.debug.assert(init_opts.fraction.* <= 1);

    const options = slider_defaults.override(opts);

    var b = box(src, .{ .dir = init_opts.dir }, options);
    defer b.deinit();

    if (b.data().accesskit_node()) |ak_node| {
        AccessKit.nodeAddAction(ak_node, AccessKit.Action.focus);
        AccessKit.nodeAddAction(ak_node, AccessKit.Action.set_value);
        AccessKit.nodeSetOrientation(ak_node, switch (init_opts.dir) {
            .vertical => AccessKit.Orientation.vertical,
            .horizontal => AccessKit.Orientation.horizontal,
        });
        AccessKit.nodeSetNumericValue(ak_node, init_opts.fraction.*);
        AccessKit.nodeSetMinNumericValue(ak_node, 0);
        AccessKit.nodeSetMaxNumericValue(ak_node, 1);
        AccessKit.nodeSetNumericValueStep(ak_node, 0.01);
        AccessKit.nodeSetNumericValueJump(ak_node, 0.10);
    }

    tabIndexSet(b.data().id, options.tab_index, b.data().rectScale().r);

    var hovered: bool = false;
    var ret = false;

    const br = b.data().contentRect();
    const knobsize = @min(br.w, br.h);
    const track = switch (init_opts.dir) {
        .horizontal => Rect{ .x = knobsize / 2, .y = br.h / 2 - 2, .w = br.w - knobsize, .h = 4 },
        .vertical => Rect{ .x = br.w / 2 - 2, .y = knobsize / 2, .w = 4, .h = br.h - knobsize },
    };

    const trackrs = b.widget().screenRectScale(track);

    const rs = b.data().contentRectScale();
    const evts = events();
    for (evts) |*e| {
        if (!eventMatch(e, .{ .id = b.data().id, .r = rs.r }))
            continue;

        switch (e.evt) {
            .mouse => |me| {
                var p: ?Point.Physical = null;
                if (me.action == .focus) {
                    e.handle(@src(), b.data());
                    focusWidget(b.data().id, null, e.num);
                } else if (me.action == .press and me.button.pointer()) {
                    // capture
                    captureMouse(b.data(), e.num);
                    e.handle(@src(), b.data());
                    p = me.p;
                } else if (me.action == .release and me.button.pointer()) {
                    // stop capture
                    captureMouse(null, e.num);
                    dragEnd();
                    e.handle(@src(), b.data());
                } else if (me.action == .motion and captured(b.data().id)) {
                    // handle only if we have capture
                    e.handle(@src(), b.data());
                    p = me.p;
                } else if (me.action == .position) {
                    dvui.cursorSet(.arrow);
                    hovered = true;
                }

                if (p) |pp| {
                    var min: f32 = undefined;
                    var max: f32 = undefined;
                    switch (init_opts.dir) {
                        .horizontal => {
                            min = trackrs.r.x;
                            max = trackrs.r.x + trackrs.r.w;
                        },
                        .vertical => {
                            min = 0;
                            max = trackrs.r.h;
                        },
                    }

                    if (max > min) {
                        const v = if (init_opts.dir == .horizontal) pp.x else (trackrs.r.y + trackrs.r.h - pp.y);
                        init_opts.fraction.* = (v - min) / (max - min);
                        init_opts.fraction.* = @max(0, @min(1, init_opts.fraction.*));
                        ret = true;
                    }
                }
            },
            .key => |ke| {
                if (ke.action == .down or ke.action == .repeat) {
                    switch (ke.code) {
                        .left, .down => {
                            e.handle(@src(), b.data());
                            init_opts.fraction.* = @max(0, @min(1, init_opts.fraction.* - 0.05));
                            ret = true;
                        },
                        .right, .up => {
                            e.handle(@src(), b.data());
                            init_opts.fraction.* = @max(0, @min(1, init_opts.fraction.* + 0.05));
                            ret = true;
                        },
                        else => {},
                    }
                }
            },
            .text => |te| {
                switch (te.action) {
                    .value => |set| blk: {
                        e.handle(@src(), b.data());
                        const value: f32 = std.fmt.parseFloat(f32, set.txt) catch break :blk;
                        init_opts.fraction.* = std.math.clamp(value, 0.0, 1.0);
                    },
                    else => {},
                }
            },
            else => {},
        }
    }

    const perc = @max(0, @min(1, init_opts.fraction.*));

    var part = trackrs.r;
    switch (init_opts.dir) {
        .horizontal => part.w *= perc,
        .vertical => {
            const h = part.h * (1 - perc);
            part.y += h;
            part.h = trackrs.r.h - h;
        },
    }
    if (b.data().visible()) {
        part.fill(options.cornersGet().scale(trackrs.s, CornerRect.Physical), .{ .color = init_opts.color_bar orelse dvui.themeGet().color(.highlight, .fill), .fade = 1.0 });
    }

    switch (init_opts.dir) {
        .horizontal => {
            part.x = part.x + part.w;
            part.w = trackrs.r.w - part.w;
        },
        .vertical => {
            part = trackrs.r;
            part.h *= (1 - perc);
        },
    }
    if (b.data().visible()) {
        part.fill(options.cornersGet().scale(trackrs.s, CornerRect.Physical), .{ .color = options.color(.fill), .fade = 1.0 });
    }

    const knobRect = switch (init_opts.dir) {
        .horizontal => Rect{ .x = (br.w - knobsize) * perc, .w = knobsize, .h = knobsize },
        .vertical => Rect{ .y = (br.h - knobsize) * (1 - perc), .w = knobsize, .h = knobsize },
    };

    const fill_color: Color = if (captured(b.data().id))
        options.color(.fill_press)
    else if (hovered)
        options.color(.fill_hover)
    else
        options.color(.fill);

    var knob: BoxWidget = undefined;
    knob.init(@src(), .{ .dir = .horizontal }, .{ .rect = knobRect, .padding = .{}, .margin = .{}, .background = true, .border = Rect.all(1), .corners = .all(100), .color_fill = fill_color });

    knob.drawBackground();
    if (b.data().id == focusedWidgetId()) {
        knob.data().focusBorder();
    }
    knob.deinit();

    if (ret) {
        refresh(null, @src(), b.data().id);
    }

    return ret;
}