Add Lucide icon font for the mobile UI
Bundle Lucide (ISC license) at assets/fonts/lucide.ttf and register it as the egui font family "lucide" via mobile/icons.rs::install (called in EditorApp::new). Icons are drawn as text with icons::font(size) + PUA codepoint constants. Switch the stack headers (grip, maximize/minimize, node⇄instrument), the footer chevron, and the transport play/pause to Lucide glyphs instead of relying on egui's limited built-in emoji-icon-font.
This commit is contained in:
parent
597697d0a6
commit
76e7963f03
|
|
@ -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.
|
||||||
Binary file not shown.
|
|
@ -1207,6 +1207,9 @@ impl EditorApp {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let current_layout = layouts[0].layout.clone();
|
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)
|
// Disable egui's "Unaligned" debug overlay (on by default in debug builds)
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
cc.egui_ctx.style_mut(|style| style.debug.show_unaligned = false);
|
cc.egui_ctx.style_mut(|style| style.debug.show_unaligned = false);
|
||||||
|
|
|
||||||
|
|
@ -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}";
|
||||||
|
|
@ -17,6 +17,7 @@ use lightningbeam_core::pane::PaneType;
|
||||||
use crate::panes::NodePath;
|
use crate::panes::NodePath;
|
||||||
use crate::RenderContext;
|
use crate::RenderContext;
|
||||||
|
|
||||||
|
pub mod icons;
|
||||||
mod stack;
|
mod stack;
|
||||||
mod surface;
|
mod surface;
|
||||||
mod transport;
|
mod transport;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
|
||||||
use super::{slot_path, MobileState, StackDrag, StackPane, STACK};
|
use super::{icons, slot_path, MobileState, StackDrag, StackPane, STACK};
|
||||||
use crate::RenderContext;
|
use crate::RenderContext;
|
||||||
|
|
||||||
const N: usize = STACK.len();
|
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.rect_filled(hr, 0.0, C_HEADER);
|
||||||
p.hline(hr.x_range(), hr.bottom(), egui::Stroke::new(1.0, C_LINE));
|
p.hline(hr.x_range(), hr.bottom(), egui::Stroke::new(1.0, C_LINE));
|
||||||
let cy = hr.center().y;
|
let cy = hr.center().y;
|
||||||
// Grip dots on the left.
|
// Grip glyph on the left (Lucide).
|
||||||
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);
|
|
||||||
}
|
|
||||||
p.text(
|
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,
|
egui::Align2::LEFT_CENTER,
|
||||||
sp.label(show_instruments),
|
sp.label(show_instruments),
|
||||||
egui::FontId::proportional(15.0),
|
egui::FontId::proportional(15.0),
|
||||||
C_BRIGHT,
|
C_BRIGHT,
|
||||||
);
|
);
|
||||||
// Right-side buttons: fullscreen / restore rightmost, Node/Instrument toggle just left of it.
|
// 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(
|
p.text(
|
||||||
egui::pos2(hr.right() - BTN_W * 0.5, cy),
|
egui::pos2(hr.right() - BTN_W * 0.5, cy),
|
||||||
egui::Align2::CENTER_CENTER,
|
egui::Align2::CENTER_CENTER,
|
||||||
if fullscreen { "▣" } else { "⛶" },
|
if fullscreen { icons::MINIMIZE } else { icons::MAXIMIZE },
|
||||||
egui::FontId::proportional(18.0),
|
icons::font(17.0),
|
||||||
C_DIM,
|
C_DIM,
|
||||||
);
|
);
|
||||||
if sp == StackPane::NodeInstrument {
|
if sp == StackPane::NodeInstrument {
|
||||||
p.text(
|
p.text(
|
||||||
egui::pos2(hr.right() - BTN_W * 1.5, cy),
|
egui::pos2(hr.right() - BTN_W * 1.5, cy),
|
||||||
egui::Align2::CENTER_CENTER,
|
egui::Align2::CENTER_CENTER,
|
||||||
"⇄",
|
icons::ARROW_LEFT_RIGHT,
|
||||||
egui::FontId::proportional(17.0),
|
icons::font(17.0),
|
||||||
C_AMBER,
|
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.rect_filled(fr, 0.0, C_HEADER);
|
||||||
p.hline(fr.x_range(), fr.top(), egui::Stroke::new(1.0, C_LINE));
|
p.hline(fr.x_range(), fr.top(), egui::Stroke::new(1.0, C_LINE));
|
||||||
let col = if at_end { C_LINE } else { C_DIM };
|
let col = if at_end { C_LINE } else { C_DIM };
|
||||||
p.text(
|
let cy = fr.center().y;
|
||||||
fr.center(),
|
let galley = p.layout_no_wrap(
|
||||||
egui::Align2::CENTER_CENTER,
|
"pull up for more".to_string(),
|
||||||
"▲ pull up for more",
|
|
||||||
egui::FontId::proportional(11.0),
|
egui::FontId::proportional(11.0),
|
||||||
col,
|
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) {
|
fn handle_key(h: Handle) -> (usize, usize) {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
|
||||||
|
use super::icons;
|
||||||
use crate::panes::SharedPaneState;
|
use crate::panes::SharedPaneState;
|
||||||
|
|
||||||
const C_PANEL: egui::Color32 = egui::Color32::from_rgb(0x1f, 0x24, 0x2c);
|
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_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());
|
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);
|
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(
|
painter.text(
|
||||||
btn_center,
|
btn_center,
|
||||||
egui::Align2::CENTER_CENTER,
|
egui::Align2::CENTER_CENTER,
|
||||||
glyph,
|
glyph,
|
||||||
egui::FontId::proportional(16.0),
|
icons::font(16.0),
|
||||||
C_DARK,
|
C_DARK,
|
||||||
);
|
);
|
||||||
if btn_resp.clicked() {
|
if btn_resp.clicked() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue