jellyflow-egui 0.3.0

Immediate-mode egui adapter for Jellyflow node graphs.
Documentation
use eframe::egui::{ScrollArea, TextEdit, Ui};
use jellyflow::runtime::schema::NodeKindViewDescriptor;

use crate::bridge::JellyflowEguiBridge;
use crate::state::{CanvasTool, JellyflowEguiState};

pub fn show_palette(ui: &mut Ui, bridge: &JellyflowEguiBridge, state: &mut JellyflowEguiState) {
    ui.heading("Nodes");
    ui.add(
        TextEdit::singleline(&mut state.palette_filter)
            .hint_text("Filter")
            .desired_width(f32::INFINITY),
    );
    ui.separator();

    let filter = state.palette_filter.trim().to_ascii_lowercase();
    ScrollArea::vertical().show(ui, |ui| {
        for descriptor in bridge.descriptors() {
            if !matches_filter(&descriptor, &filter) {
                continue;
            }
            let selected = state.pending_create_kind.as_ref() == Some(&descriptor.kind);
            if ui
                .selectable_label(selected, descriptor_label(&descriptor))
                .on_hover_text("Click a point on the canvas to create this node")
                .clicked()
            {
                state.pending_create_kind = Some(descriptor.kind.clone());
                state.canvas_tool = CanvasTool::CreateNode;
                state.set_status(format!("Creating {}", descriptor.title));
            }
        }
    });
}

fn matches_filter(descriptor: &NodeKindViewDescriptor, filter: &str) -> bool {
    if filter.is_empty() {
        return true;
    }
    descriptor.title.to_ascii_lowercase().contains(filter)
        || descriptor
            .renderer_key
            .to_ascii_lowercase()
            .contains(filter)
        || descriptor.kind.0.to_ascii_lowercase().contains(filter)
        || descriptor
            .category
            .iter()
            .any(|value| value.to_ascii_lowercase().contains(filter))
        || descriptor
            .keywords
            .iter()
            .any(|value| value.to_ascii_lowercase().contains(filter))
}

fn descriptor_label(descriptor: &NodeKindViewDescriptor) -> String {
    let category = descriptor.category.join(" / ");
    if category.is_empty() {
        descriptor.title.clone()
    } else {
        format!("{category} / {}", descriptor.title)
    }
}