Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

NodeSearch

Layer: graph (compose-tier) · Path: src/graph/search.rs · Exports: NodeSearch

A command-palette popover for picking a node kind to create. It is a Popover holding a text Input filter over a caller-supplied list of node kinds, each rendered as a MenuItem. It is data-model-agnostic: the caller owns the kind list and decides where the chosen kind is placed. show returns the picked NodeKindId; the caller then emits a create_request into GraphResponse (or just spawns the node directly).

It paints nothing of its own — it composes existing atoms/cells/organisms, honouring the compose-tier contract (see layer README and guards).

Design

Purpose. Turn a click on an “Add node” trigger into a filtered menu of node kinds, returning the kind the user picked. It does not place the node — placement (and the (NodeKindId, Pos2) pairing) is the caller’s job.

Anatomy.

  • A [Popover] anchored to a trigger: &Response (typically a Button).
  • Inside: an [Input] with placeholder "Search nodes…", whose query is persisted in ui.data under ui.id().with("node_search_query") (survives across frames while the popover is open).
  • Below: one [MenuItem] per kind whose label (case-insensitively) contains the query. Empty query shows all kinds. Clicking a [MenuItem] records that kind as the chosen result.

API surface. A small consuming builder: new() → chain .kind(id, label) once per kind → terminal .show(ui, trigger). show takes self by value (consumes the builder) and returns Option<NodeKindId>.

Tokens. None applied directly — all colour/spacing flows through the composed atoms ([Input], [MenuItem], [Popover]), which resolve from Theme. NodeSearch does not touch GraphTokens.

API

use ouroboros_ui::graph::NodeSearch;

ItemSignatureNotes
fieldkinds: Vec<(NodeKindId, String)>private; populated via .kind()
NodeSearch::newfn new() -> Selfempty palette (also #[derive(Default)])
.kindfn kind(self, id: NodeKindId, label: impl Into<String>) -> Selfappend one selectable kind; chainable
.showfn show(self, ui: &mut egui::Ui, trigger: &egui::Response) -> Option<NodeKindId>consumes self; returns the kind picked this frame, else None

NodeKindId(pub u64) is a caller-defined identifier for a node kind (not a node instance). See identity. The picked id is Copy; read .0 for the raw u64.

Usage

Realistic flow: a trigger button feeds NodeSearch, and the picked kind is paired with a world position to fill GraphResponse.create_request. Because NodeSearch::show returns only the kind, the caller supplies the Pos2 (e.g. the canvas centre, or the last right-click point).

#![allow(unused)]
fn main() {
use egui::{pos2, Pos2};
use ouroboros_ui::atoms::Button;
use ouroboros_ui::graph::{NodeKindId, NodeSearch};

// 1. Trigger.
let trigger = Button::new("Add node")
    .icon_left(egui_phosphor::light::PLUS)
    .id_source("add_node")
    .show(ui);

// 2. Palette anchored to the trigger.
let chosen: Option<NodeKindId> = NodeSearch::new()
    .kind(NodeKindId(1), "Trigger")
    .kind(NodeKindId(2), "Condition")
    .kind(NodeKindId(3), "Action")
    .kind(NodeKindId(4), "Delay")
    .show(ui, &trigger);

// 3. Pair the kind with a drop point and hand it to the caller's create handler.
//    (Mirrors GraphResponse.create_request: Option<(NodeKindId, Pos2)>.)
if let Some(kind) = chosen {
    let drop_at: Pos2 = pos2(120.0, 80.0); // caller-chosen world position
    spawn_node(kind, drop_at);             // caller commits to its own model
}
}

The graph canvas exposes the committed form of this on its response:

#![allow(unused)]
fn main() {
let resp = GraphView::new("graph").show(ui, |g| { /* … */ });
if let Some((kind, world_pos)) = resp.create_request {
    // caller adds a node of `kind` at `world_pos` to its data model
}
}

NodeSearch itself does not populate GraphResponse.create_request — it is a standalone palette. The canvas field is filled by the canvas’s own internal create path; NodeSearch is the recommended UI for producing the NodeKindId half of that pair. See canvas.

Composition / Notes

  • Tier: compose. Reuses [Popover], [Input], [MenuItem] — no inline painting, satisfying the graph layer’s compose-tier rule.
  • Stateless across instances: the only persisted state is the filter query, keyed off ui.id(). Use distinct parent ids if you host two palettes in one Ui to avoid query bleed.
  • Caller owns identity & placement: NodeKindIds are caller-defined and the drop position is caller-chosen; the library never sees domain types. This is the same data-agnostic contract the rest of the graph layer follows.
  • Filtering: substring, case-insensitive, on the label only. No fuzzy match, no keyboard navigation, no scroll virtualization — fine for the tens-of-kinds palettes this targets.