diff --git a/lightningbeam-ui/lightningbeam-editor/assets/fonts/LICENSE-lucide.txt b/lightningbeam-ui/lightningbeam-editor/assets/fonts/LICENSE-lucide.txt new file mode 100644 index 0000000..718bb3f --- /dev/null +++ b/lightningbeam-ui/lightningbeam-editor/assets/fonts/LICENSE-lucide.txt @@ -0,0 +1,43 @@ +ISC License + +Copyright (c) 2026 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The following Lucide icons are derived from the Feather project: + +airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + +The MIT License (MIT) (for the icons listed above) + +Copyright (c) 2013-present Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lightningbeam-ui/lightningbeam-editor/assets/fonts/lucide.ttf b/lightningbeam-ui/lightningbeam-editor/assets/fonts/lucide.ttf new file mode 100644 index 0000000..0a69bf1 Binary files /dev/null and b/lightningbeam-ui/lightningbeam-editor/assets/fonts/lucide.ttf differ diff --git a/lightningbeam-ui/lightningbeam-editor/src/main.rs b/lightningbeam-ui/lightningbeam-editor/src/main.rs index 81d8b10..a936514 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/main.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/main.rs @@ -1207,6 +1207,9 @@ impl EditorApp { ) -> Self { let current_layout = layouts[0].layout.clone(); + // Register the bundled Lucide icon font (used by the mobile UI; harmless on desktop). + mobile::icons::install(&cc.egui_ctx); + // Disable egui's "Unaligned" debug overlay (on by default in debug builds) #[cfg(debug_assertions)] cc.egui_ctx.style_mut(|style| style.debug.show_unaligned = false); diff --git a/lightningbeam-ui/lightningbeam-editor/src/mobile/icons.rs b/lightningbeam-ui/lightningbeam-editor/src/mobile/icons.rs new file mode 100644 index 0000000..db7a954 --- /dev/null +++ b/lightningbeam-ui/lightningbeam-editor/src/mobile/icons.rs @@ -0,0 +1,47 @@ +//! Lucide icon font (ISC license — see `assets/fonts/LICENSE-lucide.txt`). +#![allow(dead_code)] // a forward-looking icon palette; not all are wired up yet +//! +//! The font is embedded and registered as a named egui font family ("lucide"). Icons are drawn as +//! text using [`font`] for the `FontId`, with the codepoint constants below (Lucide's private-use +//! area). Run [`install`] once at startup. + +use std::sync::Arc; + +use eframe::egui; + +/// The egui font-family name the Lucide glyphs live under. +pub const FAMILY: &str = "lucide"; + +/// Embed + register the Lucide icon font. Call once with the egui context at startup. +pub fn install(ctx: &egui::Context) { + let mut fonts = egui::FontDefinitions::default(); + fonts.font_data.insert( + "lucide".to_owned(), + Arc::new(egui::FontData::from_static(include_bytes!( + "../../assets/fonts/lucide.ttf" + ))), + ); + fonts + .families + .insert(egui::FontFamily::Name(FAMILY.into()), vec!["lucide".to_owned()]); + ctx.set_fonts(fonts); +} + +/// A `FontId` in the Lucide family at `size`. +pub fn font(size: f32) -> egui::FontId { + egui::FontId::new(size, egui::FontFamily::Name(FAMILY.into())) +} + +// --- Codepoints (Lucide PUA). Add more as the mobile UI needs them. --- +pub const MAXIMIZE: &str = "\u{e112}"; +pub const MINIMIZE: &str = "\u{e11a}"; +pub const ARROW_LEFT_RIGHT: &str = "\u{e24a}"; +pub const GRIP_HORIZONTAL: &str = "\u{e0ea}"; +pub const CHEVRONS_UP: &str = "\u{e074}"; +pub const PLAY: &str = "\u{e13c}"; +pub const PAUSE: &str = "\u{e12e}"; +pub const SETTINGS: &str = "\u{e154}"; +pub const SEARCH: &str = "\u{e151}"; +pub const PLUS: &str = "\u{e13d}"; +pub const X: &str = "\u{e1b2}"; +pub const MENU: &str = "\u{e115}"; diff --git a/lightningbeam-ui/lightningbeam-editor/src/mobile/mod.rs b/lightningbeam-ui/lightningbeam-editor/src/mobile/mod.rs index 907afca..2d873f3 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/mobile/mod.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/mobile/mod.rs @@ -17,6 +17,7 @@ use lightningbeam_core::pane::PaneType; use crate::panes::NodePath; use crate::RenderContext; +pub mod icons; mod stack; mod surface; mod transport; diff --git a/lightningbeam-ui/lightningbeam-editor/src/mobile/stack.rs b/lightningbeam-ui/lightningbeam-editor/src/mobile/stack.rs index 52a8cb6..53dfa4b 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/mobile/stack.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/mobile/stack.rs @@ -18,7 +18,7 @@ use eframe::egui; -use super::{slot_path, MobileState, StackDrag, StackPane, STACK}; +use super::{icons, slot_path, MobileState, StackDrag, StackPane, STACK}; use crate::RenderContext; const N: usize = STACK.len(); @@ -242,33 +242,35 @@ fn draw_header(ui: &egui::Ui, hr: egui::Rect, sp: StackPane, show_instruments: b p.rect_filled(hr, 0.0, C_HEADER); p.hline(hr.x_range(), hr.bottom(), egui::Stroke::new(1.0, C_LINE)); let cy = hr.center().y; - // Grip dots on the left. - for i in 0..2 { - let x = hr.left() + 11.0 + i as f32 * 4.0; - p.circle_filled(egui::pos2(x, cy), 1.5, C_DIM); - } + // Grip glyph on the left (Lucide). p.text( - egui::pos2(hr.left() + 26.0, cy), + egui::pos2(hr.left() + 15.0, cy), + egui::Align2::CENTER_CENTER, + icons::GRIP_HORIZONTAL, + icons::font(16.0), + C_DIM, + ); + p.text( + egui::pos2(hr.left() + 32.0, cy), egui::Align2::LEFT_CENTER, sp.label(show_instruments), egui::FontId::proportional(15.0), C_BRIGHT, ); // Right-side buttons: fullscreen / restore rightmost, Node/Instrument toggle just left of it. - // These glyphs are covered by egui's bundled emoji-icon-font (⛶ ▣ ⇄). p.text( egui::pos2(hr.right() - BTN_W * 0.5, cy), egui::Align2::CENTER_CENTER, - if fullscreen { "▣" } else { "⛶" }, - egui::FontId::proportional(18.0), + if fullscreen { icons::MINIMIZE } else { icons::MAXIMIZE }, + icons::font(17.0), C_DIM, ); if sp == StackPane::NodeInstrument { p.text( egui::pos2(hr.right() - BTN_W * 1.5, cy), egui::Align2::CENTER_CENTER, - "⇄", - egui::FontId::proportional(17.0), + icons::ARROW_LEFT_RIGHT, + icons::font(17.0), C_AMBER, ); } @@ -279,13 +281,23 @@ fn draw_footer(ui: &egui::Ui, fr: egui::Rect, at_end: bool) { p.rect_filled(fr, 0.0, C_HEADER); p.hline(fr.x_range(), fr.top(), egui::Stroke::new(1.0, C_LINE)); let col = if at_end { C_LINE } else { C_DIM }; - p.text( - fr.center(), - egui::Align2::CENTER_CENTER, - "▲ pull up for more", + let cy = fr.center().y; + let galley = p.layout_no_wrap( + "pull up for more".to_string(), egui::FontId::proportional(11.0), col, ); + let total_w = 22.0 + galley.size().x; + let start_x = fr.center().x - total_w * 0.5; + p.text( + egui::pos2(start_x + 8.0, cy), + egui::Align2::CENTER_CENTER, + icons::CHEVRONS_UP, + icons::font(14.0), + col, + ); + let gy = cy - galley.size().y * 0.5; + p.galley(egui::pos2(start_x + 22.0, gy), galley, col); } fn handle_key(h: Handle) -> (usize, usize) { diff --git a/lightningbeam-ui/lightningbeam-editor/src/mobile/transport.rs b/lightningbeam-ui/lightningbeam-editor/src/mobile/transport.rs index 1623b72..90b2a50 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/mobile/transport.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/mobile/transport.rs @@ -3,6 +3,7 @@ use eframe::egui; +use super::icons; use crate::panes::SharedPaneState; const C_PANEL: egui::Color32 = egui::Color32::from_rgb(0x1f, 0x24, 0x2c); @@ -24,12 +25,12 @@ pub fn render(ui: &mut egui::Ui, rect: egui::Rect, shared: &mut SharedPaneState) let btn_rect = egui::Rect::from_center_size(btn_center, egui::vec2(btn_r * 2.0, btn_r * 2.0)); let btn_resp = ui.interact(btn_rect, ui.id().with("mobile_transport_play"), egui::Sense::click()); painter.circle_filled(btn_center, btn_r, C_AMBER); - let glyph = if *shared.is_playing { "⏸" } else { "▶" }; + let glyph = if *shared.is_playing { icons::PAUSE } else { icons::PLAY }; painter.text( btn_center, egui::Align2::CENTER_CENTER, glyph, - egui::FontId::proportional(16.0), + icons::font(16.0), C_DARK, ); if btn_resp.clicked() {