DVUI

suggestion

Wraps a textEntry to provide an attached menu (dropdown) of choices.

Use after TextEntryWidget handles events, so don't call TextEntryWidget.processEvents().

Only valid between Window.beginand Window.end.

Parameters

#
te:*TextEntryWidget
*TextEntryWidget
init_opts:SuggestionInitOptions
SuggestionInitOptions

Source

Implementation

#
pub fn suggestion(te: *TextEntryWidget, init_opts: SuggestionInitOptions) *SuggestionWidget {
    var button_clicked = false;

    const src = @src();
    const button_id = dvui.parentGet().extendId(src, 0);

    if (init_opts.button) {
        if (dvui.buttonIcon(
            src,
            "combobox_triangle",
            entypo.chevron_small_down,
            .{},
            .{},
            .{ .expand = .ratio, .margin = dvui.Rect.all(2), .gravity_x = 1.0, .tab_index = 0 },
        )) {
            button_clicked = true;
        }

        if (button_id == dvui.focusedWidgetIdInCurrentSubwindow()) {
            dvui.focusWidget(te.data().id, null, null);
        }
    }

    const min_width = te.textLayout.data().backgroundRect().w;

    var sug = widgetAlloc(SuggestionWidget);
    sug.init(@src(), .{
        .rs = te.data().borderRectScale(),
        .text_entry_id = te.data().id,
    }, .{ .label = .{ .text = te.getText() }, .min_size_content = .{ .w = min_width }, .padding = .{}, .border = te.data().options.borderGet() });
    if (button_clicked) {
        if (sug.willOpen()) sug.close() else sug.open();
    }

    // process events from textEntry
    const evts = dvui.events();
    for (evts) |*e| {
        if (!te.matchEvent(e)) {
            continue;
        }

        if (e.evt == .key and (e.evt.key.action == .down or e.evt.key.action == .repeat)) {
            switch (e.evt.key.code) {
                .up => {
                    e.handle(@src(), sug.menu.data());
                    if (sug.willOpen()) {
                        sug.selected_index -|= 1;
                    } else {
                        sug.open();
                    }
                },
                .down => {
                    e.handle(@src(), sug.menu.data());
                    if (sug.willOpen()) {
                        sug.selected_index += 1;
                    } else {
                        sug.open();
                    }
                },
                .escape => {
                    e.handle(@src(), sug.menu.data());
                    sug.close();
                },
                .enter => {
                    if (sug.willOpen()) {
                        e.handle(@src(), sug.menu.data());
                        sug.activate_selected = true;
                    }
                },
                else => {
                    if (sug.willOpen() and e.evt.key.action == .down) {
                        if (e.evt.key.matchBind("next_widget")) {
                            e.handle(@src(), sug.menu.data());
                            sug.close();
                        } else if (e.evt.key.matchBind("prev_widget")) {
                            e.handle(@src(), sug.menu.data());
                            sug.close();
                        }
                    }
                },
            }
        }

        if (!e.handled) {
            te.processEvent(e);
        }
    }

    if (init_opts.open_on_text_change and te.text_changed) {
        sug.open();
    }

    const focused_now = dvui.focusedWidgetIdInCurrentSubwindow() == te.data().id;
    if (init_opts.open_on_focus) {
        const focused_last_frame = dvui.dataGet(null, te.data().id, "_focused_last_frame", bool) orelse false;

        if (!focused_last_frame and focused_now) {
            sug.open();
        }

        dvui.dataSet(null, te.data().id, "_focused_last_frame", focused_now);
    }
    if (!focused_now) {
        sug.close();
    }

    return sug;
}