Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-core/examples/deck_fx_demo.rs

//! **deck_fx_demo** — the deck-level effects + theming, wired through
//! [`FacetDeck`] itself (not the bare `effects` module).
//!
//! Shows the three opt-in deck features from `deckfx.rs`:
//!
//! * **Palette switcher** — [`FacetDeck::palette_picker`] over every [`Theme`] in
//!   [`Theme::ALL`]; pick one and the *whole deck* (every facet) re-themes.
//! * **Glow toggle** — flip `deck.fx_mut().glow`; the active facet's content rect
//!   blooms with the palette's `glow` colour, pulsing.
//! * **Send the raven** — [`FacetDeck::send_raven`] launches the signature
//!   [`RavenSprite`](facett_core::effects::RavenSprite) to **perch on a target
//!   rect** (here, the selected row of the "Datasets" facet).
//!
//! Everything is **off by default**: a deck that never calls these is unchanged.
//! Pure egui on the **glow** backend — no wgpu, no custom shader.
//!
//! Run: `cargo run -p facett-core --example deck_fx_demo`

use eframe::egui;
use egui::{Align2, FontId, Rect, Sense, Vec2, vec2};
use facett_core::{Facet, FacetCaps, FacetDeck, theme};

const ROWS: &[&str] = &["bronze.events", "silver.sessions", "gold.kpis", "ml.features", "exports.daily"];

/// A tiny "datasets" facet: status rows that double as **perch targets**. It
/// records each row's rect into a shared cell so the host can summon the raven
/// onto the selected row through the deck.
struct Datasets {
    selected: usize,
    /// Shared with the app so it can read the live row rects (the deck doesn't
    /// know facet internals — the host bridges the rect to `send_raven`).
    row_rects: std::rc::Rc<std::cell::RefCell<Vec<Rect>>>,
}

impl Facet for Datasets {
    fn title(&self) -> &str {
        "Datasets"
    }
    fn caps(&self) -> FacetCaps {
        FacetCaps::NONE.themeable().selectable()
    }
    fn ui(&mut self, ui: &mut egui::Ui) {
        let th = theme(ui);
        ui.label("Click a row to aim the raven, then “Send raven” above.");
        ui.add_space(6.0);
        let mut rects = self.row_rects.borrow_mut();
        rects.clear();
        for (i, name) in ROWS.iter().enumerate() {
            let (rect, resp) = ui.allocate_exact_size(vec2(ui.available_width().min(420.0), 34.0), Sense::click());
            rects.push(rect);
            let selected = i == self.selected;
            let p = ui.painter();
            let fill = if selected { th.accent.linear_multiply(0.18) } else { th.node_fill };
            p.rect_filled(rect, 5.0, fill);
            p.rect_stroke(rect, 5.0, egui::Stroke::new(1.0_f32, th.panel_stroke), egui::StrokeKind::Inside);
            p.text(rect.left_center() + vec2(12.0, 0.0), Align2::LEFT_CENTER, *name, FontId::proportional(15.0), th.text);
            if resp.clicked() {
                self.selected = i;
            }
        }
    }
    fn state_json(&self) -> serde_json::Value {
        serde_json::json!({ "selected": ROWS[self.selected], "rows": ROWS.len() })
    }
}

/// A second facet so the palette switch visibly re-themes more than one tab — a
/// little themed "metrics" panel.
struct Metrics;
impl Facet for Metrics {
    fn title(&self) -> &str {
        "Metrics"
    }
    fn caps(&self) -> FacetCaps {
        FacetCaps::NONE.themeable()
    }
    fn ui(&mut self, ui: &mut egui::Ui) {
        let th = theme(ui);
        ui.label("Throughput (rows/s) — colours come from the active palette.");
        ui.add_space(8.0);
        let bars = [0.9_f32, 0.6, 0.75, 0.4, 0.85, 0.55, 0.7];
        let (rect, _) = ui.allocate_exact_size(vec2(ui.available_width().min(420.0), 120.0), Sense::hover());
        let p = ui.painter();
        p.rect_filled(rect, 4.0, th.node_fill);
        let n = bars.len();
        let bw = rect.width() / n as f32;
        for (i, &h) in bars.iter().enumerate() {
            let x0 = rect.left() + i as f32 * bw + 4.0;
            let bar = Rect::from_min_max(
                egui::pos2(x0, rect.bottom() - h * (rect.height() - 16.0)),
                egui::pos2(x0 + bw - 8.0, rect.bottom() - 4.0),
            );
            p.rect_filled(bar, 3.0, th.accent.gamma_multiply(0.6 + 0.4 * h));
        }
    }
    fn state_json(&self) -> serde_json::Value {
        serde_json::json!({ "kind": "metrics" })
    }
}

struct App {
    deck: FacetDeck,
    row_rects: std::rc::Rc<std::cell::RefCell<Vec<Rect>>>,
    target_row: usize,
}

impl Default for App {
    fn default() -> Self {
        let row_rects = std::rc::Rc::new(std::cell::RefCell::new(Vec::new()));
        let datasets = Datasets { selected: 1, row_rects: row_rects.clone() };
        let mut deck = FacetDeck::new(vec![Box::new(datasets), Box::new(Metrics)]);
        deck.set_palette(2); // start on nordic-aurora to show the override works
        Self { deck, row_rects, target_row: 1 }
    }
}

impl eframe::App for App {
    fn ui(&mut self, root: &mut egui::Ui, _frame: &mut eframe::Frame) {
        egui::Panel::top("fx_controls").show(root, |ui| {
            // The deck's own palette switcher — re-themes every facet.
            self.deck.palette_picker(ui);
            ui.horizontal(|ui| {
                // Opt into glow on the active facet.
                let mut glow = self.deck.fx().glow;
                if ui.checkbox(&mut glow, "Glow active facet").changed() {
                    self.deck.fx_mut().glow = glow;
                }
                ui.separator();
                ui.label("Aim row:");
                for (i, name) in ROWS.iter().enumerate() {
                    ui.selectable_value(&mut self.target_row, i, *name);
                }
                if ui.button("🐦 Send raven").clicked() {
                    // Bridge the selected row's live rect into the deck.
                    if let Some(&rect) = self.row_rects.borrow().get(self.target_row) {
                        self.deck.send_raven(rect);
                    }
                }
                if self.deck.has_raven()
                    && ui.button(if self.deck.raven_perched() { "Dismiss (perched)" } else { "Dismiss" }).clicked()
                {
                    self.deck.clear_raven();
                }
            });
        });

        egui::CentralPanel::default().show(root, |ui| {
            self.deck.ui(ui);
        });
    }
}

fn main() -> eframe::Result {
    let options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default().with_inner_size(Vec2::new(880.0, 600.0)),
        ..Default::default()
    };
    eframe::run_native("facett · deck_fx_demo", options, Box::new(|_cc| Ok(Box::<App>::default())))
}