Unify colors via theme CSS variables; theme egui visuals
Add Theme::var(name, ctx) (CSS custom-property getter) and Theme::apply_to_egui(ctx) which maps the palette onto egui's global Visuals so standard widgets share the theme colors. Add a mobile::palette::Palette built from the theme each frame and replace the duplicated per-file C_* color constants across all mobile modules (omni, topbar, stack, inspector, transport, intent, shell) with it. Add --scrim and --accent-* category vars to styles.css. Apply the theme visuals every frame in update(). Mobile and the main UI now share one CSS-variable-driven palette that responds to light/dark/user themes.
This commit is contained in:
parent
f13a127c9d
commit
943ff7c15f
|
|
@ -99,6 +99,14 @@
|
||||||
--font-size-small: 11px;
|
--font-size-small: 11px;
|
||||||
--font-size-default: 13px;
|
--font-size-default: 13px;
|
||||||
--pane-border-width: 1px;
|
--pane-border-width: 1px;
|
||||||
|
|
||||||
|
/* Modal backdrop (translucent) + intent/category accents (shared by light & dark) */
|
||||||
|
--scrim: #10141ab0;
|
||||||
|
--accent-coral: #e8826b;
|
||||||
|
--accent-cyan: #54c3e8;
|
||||||
|
--accent-amber: #f4a340;
|
||||||
|
--accent-pink: #c75b8a;
|
||||||
|
--accent-violet: #8a6ec0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
|
|
|
||||||
|
|
@ -5343,6 +5343,10 @@ impl eframe::App for EditorApp {
|
||||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||||
let _frame_start = std::time::Instant::now();
|
let _frame_start = std::time::Instant::now();
|
||||||
|
|
||||||
|
// Apply the theme's palette to egui's global visuals so the standard widgets (dialogs, pane
|
||||||
|
// chrome, text) share the same colors as the mobile UI and respond to light/dark/user themes.
|
||||||
|
self.theme.apply_to_egui(ctx);
|
||||||
|
|
||||||
// === Raster fault-in (Phase 3 paging) ===
|
// === Raster fault-in (Phase 3 paging) ===
|
||||||
// The canvas records raster keyframe ids whose `raw_pixels` weren't resident
|
// The canvas records raster keyframe ids whose `raw_pixels` weren't resident
|
||||||
// (it can't mutate the document while rendering). Drain that sink here, BEFORE
|
// (it can't mutate the document while rendering). Drain that sink here, BEFORE
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,10 @@ use eframe::egui;
|
||||||
use lightningbeam_core::pane::PaneType;
|
use lightningbeam_core::pane::PaneType;
|
||||||
use lightningbeam_core::selection::FocusSelection;
|
use lightningbeam_core::selection::FocusSelection;
|
||||||
|
|
||||||
use super::{surface, MobileState, MOBILE_NS};
|
use super::{surface, MobileState, Palette, MOBILE_NS};
|
||||||
use crate::panes::{NodePath, SharedPaneState};
|
use crate::panes::{NodePath, SharedPaneState};
|
||||||
use crate::RenderContext;
|
use crate::RenderContext;
|
||||||
|
|
||||||
const C_SHEET: egui::Color32 = egui::Color32::from_rgb(0x27, 0x2d, 0x37);
|
|
||||||
const C_LINE: egui::Color32 = egui::Color32::from_rgb(0x36, 0x3d, 0x49);
|
|
||||||
const C_DIM: egui::Color32 = egui::Color32::from_rgb(0x8b, 0x95, 0xa1);
|
|
||||||
const C_BRIGHT: egui::Color32 = egui::Color32::from_rgb(0xea, 0xee, 0xf3);
|
|
||||||
const C_CYAN: egui::Color32 = egui::Color32::from_rgb(0x54, 0xc3, 0xe8);
|
|
||||||
|
|
||||||
const GRAB_H: f32 = 16.0;
|
const GRAB_H: f32 = 16.0;
|
||||||
const HEAD_H: f32 = 30.0;
|
const HEAD_H: f32 = 30.0;
|
||||||
const CHIP_H: f32 = 30.0;
|
const CHIP_H: f32 = 30.0;
|
||||||
|
|
@ -104,14 +98,15 @@ pub fn render(
|
||||||
region_h: f32,
|
region_h: f32,
|
||||||
rc: &mut RenderContext,
|
rc: &mut RenderContext,
|
||||||
state: &mut MobileState,
|
state: &mut MobileState,
|
||||||
|
pal: &Palette,
|
||||||
) {
|
) {
|
||||||
// Sheet background with rounded top.
|
// Sheet background with rounded top.
|
||||||
ui.painter().rect_filled(
|
ui.painter().rect_filled(
|
||||||
rect,
|
rect,
|
||||||
egui::CornerRadius { nw: 14, ne: 14, sw: 0, se: 0 },
|
egui::CornerRadius { nw: 14, ne: 14, sw: 0, se: 0 },
|
||||||
C_SHEET,
|
pal.surface_alt,
|
||||||
);
|
);
|
||||||
ui.painter().hline(rect.x_range(), rect.top(), egui::Stroke::new(1.0, C_LINE));
|
ui.painter().hline(rect.x_range(), rect.top(), egui::Stroke::new(1.0, pal.line));
|
||||||
|
|
||||||
// Grab handle — drag to resize the sheet.
|
// Grab handle — drag to resize the sheet.
|
||||||
let grab = egui::Rect::from_min_max(
|
let grab = egui::Rect::from_min_max(
|
||||||
|
|
@ -120,7 +115,7 @@ pub fn render(
|
||||||
);
|
);
|
||||||
let gresp = ui.interact(grab, ui.id().with("mobile_inspector_grab"), egui::Sense::drag());
|
let gresp = ui.interact(grab, ui.id().with("mobile_inspector_grab"), egui::Sense::drag());
|
||||||
let pill = egui::Rect::from_center_size(grab.center(), egui::vec2(34.0, 4.0));
|
let pill = egui::Rect::from_center_size(grab.center(), egui::vec2(34.0, 4.0));
|
||||||
ui.painter().rect_filled(pill, 2.0, if gresp.hovered() || gresp.dragged() { C_CYAN } else { C_LINE });
|
ui.painter().rect_filled(pill, 2.0, if gresp.hovered() || gresp.dragged() { pal.accent } else { pal.line });
|
||||||
if gresp.dragged() && region_h > 1.0 {
|
if gresp.dragged() && region_h > 1.0 {
|
||||||
state.inspector_frac = (state.inspector_frac - gresp.drag_delta().y / region_h).clamp(0.2, 0.85);
|
state.inspector_frac = (state.inspector_frac - gresp.drag_delta().y / region_h).clamp(0.2, 0.85);
|
||||||
}
|
}
|
||||||
|
|
@ -139,7 +134,7 @@ pub fn render(
|
||||||
egui::Align2::LEFT_CENTER,
|
egui::Align2::LEFT_CENTER,
|
||||||
title(&rc.shared),
|
title(&rc.shared),
|
||||||
egui::FontId::proportional(13.0),
|
egui::FontId::proportional(13.0),
|
||||||
C_BRIGHT,
|
pal.text,
|
||||||
);
|
);
|
||||||
let close = egui::Rect::from_min_size(egui::pos2(head.right() - 36.0, head.top()), egui::vec2(36.0, HEAD_H));
|
let close = egui::Rect::from_min_size(egui::pos2(head.right() - 36.0, head.top()), egui::vec2(36.0, HEAD_H));
|
||||||
let cresp = ui.interact(close, ui.id().with("mobile_inspector_close"), egui::Sense::click());
|
let cresp = ui.interact(close, ui.id().with("mobile_inspector_close"), egui::Sense::click());
|
||||||
|
|
@ -148,7 +143,7 @@ pub fn render(
|
||||||
egui::Align2::CENTER_CENTER,
|
egui::Align2::CENTER_CENTER,
|
||||||
super::icons::X,
|
super::icons::X,
|
||||||
super::icons::font(16.0),
|
super::icons::font(16.0),
|
||||||
if cresp.hovered() { C_BRIGHT } else { C_DIM },
|
if cresp.hovered() { pal.text } else { pal.text_dim },
|
||||||
);
|
);
|
||||||
if cresp.clicked() {
|
if cresp.clicked() {
|
||||||
rc.shared.selection.clear();
|
rc.shared.selection.clear();
|
||||||
|
|
@ -162,14 +157,14 @@ pub fn render(
|
||||||
let galley = ui.painter().layout_no_wrap(
|
let galley = ui.painter().layout_no_wrap(
|
||||||
chip.label.to_string(),
|
chip.label.to_string(),
|
||||||
egui::FontId::proportional(11.0),
|
egui::FontId::proportional(11.0),
|
||||||
C_BRIGHT,
|
pal.text,
|
||||||
);
|
);
|
||||||
let w = galley.size().x + 18.0;
|
let w = galley.size().x + 18.0;
|
||||||
let chip_rect = egui::Rect::from_min_size(egui::pos2(cx, chip_y + 4.0), egui::vec2(w, CHIP_H - 8.0));
|
let chip_rect = egui::Rect::from_min_size(egui::pos2(cx, chip_y + 4.0), egui::vec2(w, CHIP_H - 8.0));
|
||||||
let resp = ui.interact(chip_rect, ui.id().with(("mobile_inspector_chip", i)), egui::Sense::click());
|
let resp = ui.interact(chip_rect, ui.id().with(("mobile_inspector_chip", i)), egui::Sense::click());
|
||||||
ui.painter().rect_filled(chip_rect, 11.0, if resp.hovered() { C_LINE } else { C_SHEET });
|
ui.painter().rect_filled(chip_rect, 11.0, if resp.hovered() { pal.line } else { pal.surface_alt });
|
||||||
ui.painter().rect_stroke(chip_rect, 11.0, egui::Stroke::new(1.0, C_LINE), egui::StrokeKind::Inside);
|
ui.painter().rect_stroke(chip_rect, 11.0, egui::Stroke::new(1.0, pal.line), egui::StrokeKind::Inside);
|
||||||
ui.painter().galley(chip_rect.center() - galley.size() * 0.5, galley, C_BRIGHT);
|
ui.painter().galley(chip_rect.center() - galley.size() * 0.5, galley, pal.text);
|
||||||
if resp.clicked() {
|
if resp.clicked() {
|
||||||
let (top, count) = chip.window;
|
let (top, count) = chip.window;
|
||||||
state.window_top = top;
|
state.window_top = top;
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,9 @@
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
|
||||||
use super::icons;
|
use super::{icons, Palette};
|
||||||
use crate::EditorApp;
|
use crate::EditorApp;
|
||||||
|
|
||||||
const C_DEVICE: egui::Color32 = egui::Color32::from_rgb(0x14, 0x16, 0x1b);
|
|
||||||
const C_CARD: egui::Color32 = egui::Color32::from_rgb(0x1f, 0x24, 0x2c);
|
|
||||||
const C_CARD_HOT: egui::Color32 = egui::Color32::from_rgb(0x27, 0x2d, 0x37);
|
|
||||||
const C_LINE: egui::Color32 = egui::Color32::from_rgb(0x36, 0x3d, 0x49);
|
|
||||||
const C_BRIGHT: egui::Color32 = egui::Color32::from_rgb(0xea, 0xee, 0xf3);
|
|
||||||
const C_DIM: egui::Color32 = egui::Color32::from_rgb(0x8b, 0x95, 0xa1);
|
|
||||||
|
|
||||||
/// One intent card.
|
/// One intent card.
|
||||||
struct Intent {
|
struct Intent {
|
||||||
label: &'static str,
|
label: &'static str,
|
||||||
|
|
@ -28,12 +21,8 @@ struct Intent {
|
||||||
window: (usize, usize),
|
window: (usize, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn intents() -> [Intent; 6] {
|
fn intents(pal: &Palette) -> [Intent; 6] {
|
||||||
let coral = egui::Color32::from_rgb(0xe8, 0x82, 0x6b);
|
let [coral, cyan, amber, pink, violet] = pal.accents;
|
||||||
let cyan = egui::Color32::from_rgb(0x54, 0xc3, 0xe8);
|
|
||||||
let amber = egui::Color32::from_rgb(0xf4, 0xa3, 0x40);
|
|
||||||
let pink = egui::Color32::from_rgb(0xc7, 0x5b, 0x8a);
|
|
||||||
let violet = egui::Color32::from_rgb(0x6a, 0x46, 0x90);
|
|
||||||
[
|
[
|
||||||
// Stage indices (see super::STACK): Stage=2, Timeline=3, PianoRoll=4, VirtualPiano=5.
|
// Stage indices (see super::STACK): Stage=2, Timeline=3, PianoRoll=4, VirtualPiano=5.
|
||||||
Intent { label: "Draw", icon: icons::BRUSH, accent: coral, focus: 5, window: (2, 1) },
|
Intent { label: "Draw", icon: icons::BRUSH, accent: coral, focus: 5, window: (2, 1) },
|
||||||
|
|
@ -41,13 +30,14 @@ fn intents() -> [Intent; 6] {
|
||||||
Intent { label: "Compose", icon: icons::MUSIC, accent: amber, focus: 2, window: (3, 3) },
|
Intent { label: "Compose", icon: icons::MUSIC, accent: amber, focus: 2, window: (3, 3) },
|
||||||
Intent { label: "Record", icon: icons::MIC, accent: pink, focus: 2, window: (3, 3) },
|
Intent { label: "Record", icon: icons::MIC, accent: pink, focus: 2, window: (3, 3) },
|
||||||
Intent { label: "Edit video", icon: icons::CLAPPERBOARD, accent: violet, focus: 1, window: (2, 2) },
|
Intent { label: "Edit video", icon: icons::CLAPPERBOARD, accent: violet, focus: 1, window: (2, 2) },
|
||||||
Intent { label: "Blank", icon: icons::SQUARE_DASHED, accent: C_DIM, focus: 0, window: (2, 2) },
|
Intent { label: "Blank", icon: icons::SQUARE_DASHED, accent: pal.text_dim, focus: 0, window: (2, 2) },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(app: &mut EditorApp, ctx: &egui::Context) {
|
pub fn render(app: &mut EditorApp, ctx: &egui::Context) {
|
||||||
|
let pal = Palette::from_theme(&app.theme, ctx);
|
||||||
egui::CentralPanel::default()
|
egui::CentralPanel::default()
|
||||||
.frame(egui::Frame::NONE.fill(C_DEVICE))
|
.frame(egui::Frame::NONE.fill(pal.bg))
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
let rect = ui.available_rect_before_wrap();
|
let rect = ui.available_rect_before_wrap();
|
||||||
let margin = 16.0;
|
let margin = 16.0;
|
||||||
|
|
@ -60,7 +50,7 @@ pub fn render(app: &mut EditorApp, ctx: &egui::Context) {
|
||||||
egui::Align2::LEFT_TOP,
|
egui::Align2::LEFT_TOP,
|
||||||
"Start something",
|
"Start something",
|
||||||
egui::FontId::proportional(22.0),
|
egui::FontId::proportional(22.0),
|
||||||
C_BRIGHT,
|
pal.text,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Vertical budget: ~2/3 for the intent grid, ~1/3 for the recent list.
|
// Vertical budget: ~2/3 for the intent grid, ~1/3 for the recent list.
|
||||||
|
|
@ -72,7 +62,7 @@ pub fn render(app: &mut EditorApp, ctx: &egui::Context) {
|
||||||
// 2×3 grid of intent cards filling the grid budget.
|
// 2×3 grid of intent cards filling the grid budget.
|
||||||
let col_w = (right - left - gap) / 2.0;
|
let col_w = (right - left - gap) / 2.0;
|
||||||
let card_h = (grid_h - 2.0 * gap) / 3.0;
|
let card_h = (grid_h - 2.0 * gap) / 3.0;
|
||||||
for (i, intent) in intents().iter().enumerate() {
|
for (i, intent) in intents(&pal).iter().enumerate() {
|
||||||
let col = (i % 2) as f32;
|
let col = (i % 2) as f32;
|
||||||
let row = (i / 2) as f32;
|
let row = (i / 2) as f32;
|
||||||
let cx = left + col * (col_w + gap);
|
let cx = left + col * (col_w + gap);
|
||||||
|
|
@ -81,8 +71,8 @@ pub fn render(app: &mut EditorApp, ctx: &egui::Context) {
|
||||||
|
|
||||||
let resp = ui.interact(card, ui.id().with(("mobile_intent", i)), egui::Sense::click());
|
let resp = ui.interact(card, ui.id().with(("mobile_intent", i)), egui::Sense::click());
|
||||||
let p = ui.painter();
|
let p = ui.painter();
|
||||||
p.rect_filled(card, 12.0, if resp.hovered() { C_CARD_HOT } else { C_CARD });
|
p.rect_filled(card, 12.0, if resp.hovered() { pal.surface_alt } else { pal.surface });
|
||||||
p.rect_stroke(card, 12.0, egui::Stroke::new(1.0, C_LINE), egui::StrokeKind::Inside);
|
p.rect_stroke(card, 12.0, egui::Stroke::new(1.0, pal.line), egui::StrokeKind::Inside);
|
||||||
p.text(
|
p.text(
|
||||||
egui::pos2(card.center().x, card.top() + card.height() * 0.40),
|
egui::pos2(card.center().x, card.top() + card.height() * 0.40),
|
||||||
egui::Align2::CENTER_CENTER,
|
egui::Align2::CENTER_CENTER,
|
||||||
|
|
@ -95,7 +85,7 @@ pub fn render(app: &mut EditorApp, ctx: &egui::Context) {
|
||||||
egui::Align2::CENTER_CENTER,
|
egui::Align2::CENTER_CENTER,
|
||||||
intent.label,
|
intent.label,
|
||||||
egui::FontId::proportional(15.0),
|
egui::FontId::proportional(15.0),
|
||||||
C_BRIGHT,
|
pal.text,
|
||||||
);
|
);
|
||||||
|
|
||||||
if resp.clicked() {
|
if resp.clicked() {
|
||||||
|
|
@ -113,7 +103,7 @@ pub fn render(app: &mut EditorApp, ctx: &egui::Context) {
|
||||||
egui::Align2::LEFT_TOP,
|
egui::Align2::LEFT_TOP,
|
||||||
"Recent",
|
"Recent",
|
||||||
egui::FontId::proportional(13.0),
|
egui::FontId::proportional(13.0),
|
||||||
C_DIM,
|
pal.text_dim,
|
||||||
);
|
);
|
||||||
let list_top = recent_top + 22.0;
|
let list_top = recent_top + 22.0;
|
||||||
let recents = app.config.get_recent_files();
|
let recents = app.config.get_recent_files();
|
||||||
|
|
@ -123,7 +113,7 @@ pub fn render(app: &mut EditorApp, ctx: &egui::Context) {
|
||||||
egui::Align2::LEFT_TOP,
|
egui::Align2::LEFT_TOP,
|
||||||
"No recent projects",
|
"No recent projects",
|
||||||
egui::FontId::proportional(12.0),
|
egui::FontId::proportional(12.0),
|
||||||
C_DIM,
|
pal.text_dim,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let row_h = 38.0;
|
let row_h = 38.0;
|
||||||
|
|
@ -138,14 +128,14 @@ pub fn render(app: &mut EditorApp, ctx: &egui::Context) {
|
||||||
let resp =
|
let resp =
|
||||||
ui.interact(row_rect, ui.id().with(("mobile_recent", j)), egui::Sense::click());
|
ui.interact(row_rect, ui.id().with(("mobile_recent", j)), egui::Sense::click());
|
||||||
let p = ui.painter();
|
let p = ui.painter();
|
||||||
p.rect_filled(row_rect, 8.0, if resp.hovered() { C_CARD_HOT } else { C_CARD });
|
p.rect_filled(row_rect, 8.0, if resp.hovered() { pal.surface_alt } else { pal.surface });
|
||||||
p.rect_stroke(row_rect, 8.0, egui::Stroke::new(1.0, C_LINE), egui::StrokeKind::Inside);
|
p.rect_stroke(row_rect, 8.0, egui::Stroke::new(1.0, pal.line), egui::StrokeKind::Inside);
|
||||||
p.text(
|
p.text(
|
||||||
egui::pos2(row_rect.left() + 12.0, row_rect.center().y),
|
egui::pos2(row_rect.left() + 12.0, row_rect.center().y),
|
||||||
egui::Align2::LEFT_CENTER,
|
egui::Align2::LEFT_CENTER,
|
||||||
icons::FOLDER_OPEN,
|
icons::FOLDER_OPEN,
|
||||||
icons::font(15.0),
|
icons::font(15.0),
|
||||||
C_DIM,
|
pal.text_dim,
|
||||||
);
|
);
|
||||||
let name = path
|
let name = path
|
||||||
.file_name()
|
.file_name()
|
||||||
|
|
@ -156,7 +146,7 @@ pub fn render(app: &mut EditorApp, ctx: &egui::Context) {
|
||||||
egui::Align2::LEFT_CENTER,
|
egui::Align2::LEFT_CENTER,
|
||||||
&name,
|
&name,
|
||||||
egui::FontId::proportional(13.0),
|
egui::FontId::proportional(13.0),
|
||||||
C_BRIGHT,
|
pal.text,
|
||||||
);
|
);
|
||||||
if resp.clicked() {
|
if resp.clicked() {
|
||||||
chosen = Some(path.clone());
|
chosen = Some(path.clone());
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,14 @@ pub mod icons;
|
||||||
mod inspector;
|
mod inspector;
|
||||||
pub mod intent;
|
pub mod intent;
|
||||||
mod omni;
|
mod omni;
|
||||||
|
mod palette;
|
||||||
mod stack;
|
mod stack;
|
||||||
mod surface;
|
mod surface;
|
||||||
mod topbar;
|
mod topbar;
|
||||||
mod transport;
|
mod transport;
|
||||||
|
|
||||||
|
use palette::Palette;
|
||||||
|
|
||||||
/// Reserved sentinel namespace for mobile pane-instance paths. Desktop layout paths are built
|
/// Reserved sentinel namespace for mobile pane-instance paths. Desktop layout paths are built
|
||||||
/// from small child indices, so prefixing with `usize::MAX` guarantees mobile slots never alias a
|
/// from small child indices, so prefixing with `usize::MAX` guarantees mobile slots never alias a
|
||||||
/// real layout path in the shared `pane_instances` map.
|
/// real layout path in the shared `pane_instances` map.
|
||||||
|
|
@ -203,9 +206,10 @@ pub fn render_mobile_shell(
|
||||||
rc: &mut RenderContext,
|
rc: &mut RenderContext,
|
||||||
state: &mut MobileState,
|
state: &mut MobileState,
|
||||||
) {
|
) {
|
||||||
// Background (wireframe device color; bands paint over most of it).
|
let pal = Palette::from_theme(rc.shared.theme, ui.ctx());
|
||||||
ui.painter()
|
|
||||||
.rect_filled(available_rect, 0.0, egui::Color32::from_rgb(0x14, 0x16, 0x1b));
|
// Background (device color; bands paint over most of it).
|
||||||
|
ui.painter().rect_filled(available_rect, 0.0, pal.bg);
|
||||||
|
|
||||||
let topbar_rect = egui::Rect::from_min_max(
|
let topbar_rect = egui::Rect::from_min_max(
|
||||||
available_rect.min,
|
available_rect.min,
|
||||||
|
|
@ -243,19 +247,19 @@ pub fn render_mobile_shell(
|
||||||
region
|
region
|
||||||
};
|
};
|
||||||
|
|
||||||
stack::render(ui, stack_rect, rc, state);
|
stack::render(ui, stack_rect, rc, state, &pal);
|
||||||
|
|
||||||
if inspector_shown {
|
if inspector_shown {
|
||||||
let sheet_rect = egui::Rect::from_min_max(egui::pos2(region.left(), sheet_top), region.max);
|
let sheet_rect = egui::Rect::from_min_max(egui::pos2(region.left(), sheet_top), region.max);
|
||||||
inspector::render(ui, sheet_rect, region.height(), rc, state);
|
inspector::render(ui, sheet_rect, region.height(), rc, state, &pal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transport floor: drawn last = always on top, the persistent spine.
|
// Transport floor: drawn last = always on top, the persistent spine.
|
||||||
transport::render(ui, transport_rect, &mut rc.shared);
|
transport::render(ui, transport_rect, &mut rc.shared, &pal);
|
||||||
|
|
||||||
// Omnibutton FAB (radial tool menu) — drawn above the stack region, on top of everything else.
|
// Omnibutton FAB (radial tool menu) — drawn above the stack region, on top of everything else.
|
||||||
omni::render(ui, region, rc, state);
|
omni::render(ui, region, rc, state, &pal);
|
||||||
|
|
||||||
// Top bar (filename + ⌕ palette + ⋯ commands). Its menus overlay the whole shell, so it's last.
|
// Top bar (filename + ⌕ palette + ⋯ commands). Its menus overlay the whole shell, so it's last.
|
||||||
topbar::render(ui, topbar_rect, available_rect, rc, state);
|
topbar::render(ui, topbar_rect, available_rect, rc, state, &pal);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,18 +9,10 @@ use lightningbeam_core::layer::{AnyLayer, LayerType};
|
||||||
use lightningbeam_core::selection::FocusSelection;
|
use lightningbeam_core::selection::FocusSelection;
|
||||||
use lightningbeam_core::tool::Tool;
|
use lightningbeam_core::tool::Tool;
|
||||||
|
|
||||||
use super::{icons, MobileState};
|
use super::{icons, MobileState, Palette};
|
||||||
use crate::menu::MenuAction;
|
use crate::menu::MenuAction;
|
||||||
use crate::RenderContext;
|
use crate::RenderContext;
|
||||||
|
|
||||||
const C_AMBER: egui::Color32 = egui::Color32::from_rgb(0xf4, 0xa3, 0x40);
|
|
||||||
const C_DARK: egui::Color32 = egui::Color32::from_rgb(0x1b, 0x13, 0x0a);
|
|
||||||
const C_PANEL: egui::Color32 = egui::Color32::from_rgb(0x1f, 0x24, 0x2c);
|
|
||||||
const C_PANEL2: egui::Color32 = egui::Color32::from_rgb(0x27, 0x2d, 0x37);
|
|
||||||
const C_LINE: egui::Color32 = egui::Color32::from_rgb(0x36, 0x3d, 0x49);
|
|
||||||
const C_BRIGHT: egui::Color32 = egui::Color32::from_rgb(0xea, 0xee, 0xf3);
|
|
||||||
const C_DIM: egui::Color32 = egui::Color32::from_rgb(0x8b, 0x95, 0xa1);
|
|
||||||
|
|
||||||
const FAB_R: f32 = 26.0;
|
const FAB_R: f32 = 26.0;
|
||||||
const IR: f32 = 20.0;
|
const IR: f32 = 20.0;
|
||||||
const PRIMARY: [Tool; 14] = [
|
const PRIMARY: [Tool; 14] = [
|
||||||
|
|
@ -106,7 +98,7 @@ fn close_all(state: &mut MobileState) {
|
||||||
state.omni_create_open = false;
|
state.omni_create_open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(ui: &mut egui::Ui, region: egui::Rect, rc: &mut RenderContext, state: &mut MobileState) {
|
pub fn render(ui: &mut egui::Ui, region: egui::Rect, rc: &mut RenderContext, state: &mut MobileState, pal: &Palette) {
|
||||||
let ctx_tools: Vec<Tool> = Tool::for_layer_type(active_layer_type(rc)).to_vec();
|
let ctx_tools: Vec<Tool> = Tool::for_layer_type(active_layer_type(rc)).to_vec();
|
||||||
let primary: Vec<Tool> = PRIMARY
|
let primary: Vec<Tool> = PRIMARY
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -140,7 +132,7 @@ pub fn render(ui: &mut egui::Ui, region: egui::Rect, rc: &mut RenderContext, sta
|
||||||
enabled: true,
|
enabled: true,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let (close, clicked) = draw_grid(ui, region, "Tools", &cells);
|
let (close, clicked) = draw_grid(ui, region, "Tools", &cells, pal);
|
||||||
if let Some(i) = clicked {
|
if let Some(i) = clicked {
|
||||||
*rc.shared.selected_tool = ctx_tools[i];
|
*rc.shared.selected_tool = ctx_tools[i];
|
||||||
close_all(state);
|
close_all(state);
|
||||||
|
|
@ -159,7 +151,7 @@ pub fn render(ui: &mut egui::Ui, region: egui::Rect, rc: &mut RenderContext, sta
|
||||||
enabled: it.3,
|
enabled: it.3,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let (close, clicked) = draw_grid(ui, region, "New", &cells);
|
let (close, clicked) = draw_grid(ui, region, "New", &cells, pal);
|
||||||
if let Some(i) = clicked {
|
if let Some(i) = clicked {
|
||||||
rc.shared.pending_menu_actions.push(items[i].2);
|
rc.shared.pending_menu_actions.push(items[i].2);
|
||||||
close_all(state);
|
close_all(state);
|
||||||
|
|
@ -169,8 +161,7 @@ pub fn render(ui: &mut egui::Ui, region: egui::Rect, rc: &mut RenderContext, sta
|
||||||
} else if state.omni_open {
|
} else if state.omni_open {
|
||||||
// Radial: scrim, then tool petals + special petals (more / new).
|
// Radial: scrim, then tool petals + special petals (more / new).
|
||||||
let scrim = ui.interact(region, ui.id().with("mobile_omni_scrim"), egui::Sense::click());
|
let scrim = ui.interact(region, ui.id().with("mobile_omni_scrim"), egui::Sense::click());
|
||||||
ui.painter()
|
ui.painter().rect_filled(region, 0.0, pal.scrim);
|
||||||
.rect_filled(region, 0.0, egui::Color32::from_rgba_premultiplied(8, 10, 14, 140));
|
|
||||||
if scrim.clicked() {
|
if scrim.clicked() {
|
||||||
state.omni_open = false;
|
state.omni_open = false;
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +185,7 @@ pub fn render(ui: &mut egui::Ui, region: egui::Rect, rc: &mut RenderContext, sta
|
||||||
let t = primary[k];
|
let t = primary[k];
|
||||||
let resp = ui.interact(rect, ui.id().with(("mobile_omni_tool", k)), egui::Sense::click());
|
let resp = ui.interact(rect, ui.id().with(("mobile_omni_tool", k)), egui::Sense::click());
|
||||||
let selected = *rc.shared.selected_tool == t;
|
let selected = *rc.shared.selected_tool == t;
|
||||||
petal(ui, c, if selected { C_AMBER } else { petal_bg(&resp) }, tool_icon(t), if selected { C_DARK } else { C_BRIGHT });
|
petal(ui, c, if selected { pal.accent } else { petal_bg(&resp, pal) }, tool_icon(t), if selected { pal.on_accent } else { pal.text }, pal);
|
||||||
if resp.clicked() {
|
if resp.clicked() {
|
||||||
*rc.shared.selected_tool = t;
|
*rc.shared.selected_tool = t;
|
||||||
state.omni_open = false;
|
state.omni_open = false;
|
||||||
|
|
@ -203,16 +194,16 @@ pub fn render(ui: &mut egui::Ui, region: egui::Rect, rc: &mut RenderContext, sta
|
||||||
match specials[k - primary.len()] {
|
match specials[k - primary.len()] {
|
||||||
Special::More => {
|
Special::More => {
|
||||||
let resp = ui.interact(rect, ui.id().with("mobile_omni_more"), egui::Sense::click());
|
let resp = ui.interact(rect, ui.id().with("mobile_omni_more"), egui::Sense::click());
|
||||||
petal(ui, c, petal_bg(&resp), icons::MENU, C_BRIGHT);
|
petal(ui, c, petal_bg(&resp, pal), icons::MENU, pal.text, pal);
|
||||||
if resp.clicked() {
|
if resp.clicked() {
|
||||||
state.omni_grid_open = true;
|
state.omni_grid_open = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Special::New => {
|
Special::New => {
|
||||||
let resp = ui.interact(rect, ui.id().with("mobile_omni_new"), egui::Sense::click());
|
let resp = ui.interact(rect, ui.id().with("mobile_omni_new"), egui::Sense::click());
|
||||||
petal(ui, c, C_AMBER, icons::PLUS, C_DARK);
|
petal(ui, c, pal.accent, icons::PLUS, pal.on_accent, pal);
|
||||||
if resp.hovered() {
|
if resp.hovered() {
|
||||||
ui.painter().circle_stroke(c, IR, egui::Stroke::new(1.5, C_BRIGHT));
|
ui.painter().circle_stroke(c, IR, egui::Stroke::new(1.5, pal.text));
|
||||||
}
|
}
|
||||||
if resp.clicked() {
|
if resp.clicked() {
|
||||||
state.omni_create_open = true;
|
state.omni_create_open = true;
|
||||||
|
|
@ -226,7 +217,7 @@ pub fn render(ui: &mut egui::Ui, region: egui::Rect, rc: &mut RenderContext, sta
|
||||||
// The FAB itself (on top). Closed → current tool icon; open → ✕.
|
// The FAB itself (on top). Closed → current tool icon; open → ✕.
|
||||||
let fab_rect = egui::Rect::from_center_size(fab_center, egui::vec2(FAB_R * 2.0, FAB_R * 2.0));
|
let fab_rect = egui::Rect::from_center_size(fab_center, egui::vec2(FAB_R * 2.0, FAB_R * 2.0));
|
||||||
let fresp = ui.interact(fab_rect, ui.id().with("mobile_omni_fab"), egui::Sense::click());
|
let fresp = ui.interact(fab_rect, ui.id().with("mobile_omni_fab"), egui::Sense::click());
|
||||||
ui.painter().circle_filled(fab_center, FAB_R, C_AMBER);
|
ui.painter().circle_filled(fab_center, FAB_R, pal.accent);
|
||||||
let open = state.omni_open || state.omni_grid_open || state.omni_create_open;
|
let open = state.omni_open || state.omni_grid_open || state.omni_create_open;
|
||||||
let glyph = if open {
|
let glyph = if open {
|
||||||
icons::X
|
icons::X
|
||||||
|
|
@ -236,7 +227,7 @@ pub fn render(ui: &mut egui::Ui, region: egui::Rect, rc: &mut RenderContext, sta
|
||||||
icons::PLUS // off-Stage: a create button
|
icons::PLUS // off-Stage: a create button
|
||||||
};
|
};
|
||||||
ui.painter()
|
ui.painter()
|
||||||
.text(fab_center, egui::Align2::CENTER_CENTER, glyph, icons::font(22.0), C_DARK);
|
.text(fab_center, egui::Align2::CENTER_CENTER, glyph, icons::font(22.0), pal.on_accent);
|
||||||
if fresp.clicked() {
|
if fresp.clicked() {
|
||||||
if open {
|
if open {
|
||||||
close_all(state);
|
close_all(state);
|
||||||
|
|
@ -248,17 +239,17 @@ pub fn render(ui: &mut egui::Ui, region: egui::Rect, rc: &mut RenderContext, sta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn petal_bg(resp: &egui::Response) -> egui::Color32 {
|
fn petal_bg(resp: &egui::Response, pal: &Palette) -> egui::Color32 {
|
||||||
if resp.hovered() {
|
if resp.hovered() {
|
||||||
C_LINE
|
pal.line
|
||||||
} else {
|
} else {
|
||||||
C_PANEL2
|
pal.surface_alt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn petal(ui: &egui::Ui, c: egui::Pos2, bg: egui::Color32, glyph: &str, fg: egui::Color32) {
|
fn petal(ui: &egui::Ui, c: egui::Pos2, bg: egui::Color32, glyph: &str, fg: egui::Color32, pal: &Palette) {
|
||||||
ui.painter().circle_filled(c, IR, bg);
|
ui.painter().circle_filled(c, IR, bg);
|
||||||
ui.painter().circle_stroke(c, IR, egui::Stroke::new(1.0, C_LINE));
|
ui.painter().circle_stroke(c, IR, egui::Stroke::new(1.0, pal.line));
|
||||||
ui.painter().text(c, egui::Align2::CENTER_CENTER, glyph, icons::font(18.0), fg);
|
ui.painter().text(c, egui::Align2::CENTER_CENTER, glyph, icons::font(18.0), fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,20 +261,19 @@ struct Cell {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A modal grid sheet of icon+label cells. Returns (backdrop-tapped, Some(clicked-enabled-index)).
|
/// A modal grid sheet of icon+label cells. Returns (backdrop-tapped, Some(clicked-enabled-index)).
|
||||||
fn draw_grid(ui: &mut egui::Ui, region: egui::Rect, title: &str, cells: &[Cell]) -> (bool, Option<usize>) {
|
fn draw_grid(ui: &mut egui::Ui, region: egui::Rect, title: &str, cells: &[Cell], pal: &Palette) -> (bool, Option<usize>) {
|
||||||
let scrim = ui.interact(region, ui.id().with(("mobile_omni_gridscrim", title)), egui::Sense::click());
|
let scrim = ui.interact(region, ui.id().with(("mobile_omni_gridscrim", title)), egui::Sense::click());
|
||||||
ui.painter()
|
ui.painter().rect_filled(region, 0.0, pal.scrim);
|
||||||
.rect_filled(region, 0.0, egui::Color32::from_rgba_premultiplied(8, 10, 14, 170));
|
|
||||||
|
|
||||||
let panel = region.shrink2(egui::vec2(16.0, 40.0));
|
let panel = region.shrink2(egui::vec2(16.0, 40.0));
|
||||||
ui.painter().rect_filled(panel, 14.0, C_PANEL);
|
ui.painter().rect_filled(panel, 14.0, pal.surface);
|
||||||
ui.painter().rect_stroke(panel, 14.0, egui::Stroke::new(1.0, C_LINE), egui::StrokeKind::Inside);
|
ui.painter().rect_stroke(panel, 14.0, egui::Stroke::new(1.0, pal.line), egui::StrokeKind::Inside);
|
||||||
ui.painter().text(
|
ui.painter().text(
|
||||||
egui::pos2(panel.left() + 16.0, panel.top() + 18.0),
|
egui::pos2(panel.left() + 16.0, panel.top() + 18.0),
|
||||||
egui::Align2::LEFT_CENTER,
|
egui::Align2::LEFT_CENTER,
|
||||||
title,
|
title,
|
||||||
egui::FontId::proportional(14.0),
|
egui::FontId::proportional(14.0),
|
||||||
C_BRIGHT,
|
pal.text,
|
||||||
);
|
);
|
||||||
|
|
||||||
let cols = 4usize;
|
let cols = 4usize;
|
||||||
|
|
@ -310,20 +300,20 @@ fn draw_grid(ui: &mut egui::Ui, region: egui::Rect, title: &str, cells: &[Cell])
|
||||||
ui.interact(r, ui.id().with(("mobile_omni_cell_off", title, i)), egui::Sense::hover())
|
ui.interact(r, ui.id().with(("mobile_omni_cell_off", title, i)), egui::Sense::hover())
|
||||||
};
|
};
|
||||||
let bg = if cell.selected {
|
let bg = if cell.selected {
|
||||||
C_AMBER
|
pal.accent
|
||||||
} else if cell.enabled && resp.hovered() {
|
} else if cell.enabled && resp.hovered() {
|
||||||
C_PANEL2
|
pal.surface_alt
|
||||||
} else {
|
} else {
|
||||||
C_PANEL
|
pal.surface
|
||||||
};
|
};
|
||||||
ui.painter().rect_filled(r, 10.0, bg);
|
ui.painter().rect_filled(r, 10.0, bg);
|
||||||
ui.painter().rect_stroke(r, 10.0, egui::Stroke::new(1.0, C_LINE), egui::StrokeKind::Inside);
|
ui.painter().rect_stroke(r, 10.0, egui::Stroke::new(1.0, pal.line), egui::StrokeKind::Inside);
|
||||||
let fg = if cell.selected {
|
let fg = if cell.selected {
|
||||||
C_DARK
|
pal.on_accent
|
||||||
} else if cell.enabled {
|
} else if cell.enabled {
|
||||||
C_BRIGHT
|
pal.text
|
||||||
} else {
|
} else {
|
||||||
C_LINE
|
pal.line
|
||||||
};
|
};
|
||||||
ui.painter().text(
|
ui.painter().text(
|
||||||
egui::pos2(r.center().x, r.top() + r.height() * 0.38),
|
egui::pos2(r.center().x, r.top() + r.height() * 0.38),
|
||||||
|
|
@ -337,7 +327,7 @@ fn draw_grid(ui: &mut egui::Ui, region: egui::Rect, title: &str, cells: &[Cell])
|
||||||
egui::Align2::CENTER_CENTER,
|
egui::Align2::CENTER_CENTER,
|
||||||
cell.label,
|
cell.label,
|
||||||
egui::FontId::proportional(9.5),
|
egui::FontId::proportional(9.5),
|
||||||
if cell.selected { C_DARK } else if cell.enabled { C_DIM } else { C_LINE },
|
if cell.selected { pal.on_accent } else if cell.enabled { pal.text_dim } else { pal.line },
|
||||||
);
|
);
|
||||||
if cell.enabled && resp.clicked() {
|
if cell.enabled && resp.clicked() {
|
||||||
clicked = Some(i);
|
clicked = Some(i);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
//! The mobile UI's semantic color palette, sourced from the shared theme CSS variables so it
|
||||||
|
//! matches the main UI and responds to light/dark/user themes. Built once per frame from the active
|
||||||
|
//! [`Theme`](crate::theme::Theme) and passed (by `Copy`) into the mobile render functions, replacing
|
||||||
|
//! the per-file hardcoded color constants.
|
||||||
|
|
||||||
|
use eframe::egui::{Color32, Context};
|
||||||
|
|
||||||
|
use crate::theme::Theme;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct Palette {
|
||||||
|
/// App/device background behind everything.
|
||||||
|
pub bg: Color32,
|
||||||
|
/// Panels, cards, bands, sheets.
|
||||||
|
pub surface: Color32,
|
||||||
|
/// Raised / hovered surface.
|
||||||
|
pub surface_alt: Color32,
|
||||||
|
/// Band headers, the top bar.
|
||||||
|
pub header: Color32,
|
||||||
|
/// Borders and dividers.
|
||||||
|
pub line: Color32,
|
||||||
|
/// Primary text / icons.
|
||||||
|
pub text: Color32,
|
||||||
|
/// Secondary (dim) text / icons.
|
||||||
|
pub text_dim: Color32,
|
||||||
|
/// Selection / active accent.
|
||||||
|
pub accent: Color32,
|
||||||
|
/// Text / icons drawn on top of `accent`.
|
||||||
|
pub on_accent: Color32,
|
||||||
|
/// Modal backdrop (translucent).
|
||||||
|
pub scrim: Color32,
|
||||||
|
/// Decorative category accents (intent picker, chips): coral, cyan, amber, pink, violet.
|
||||||
|
pub accents: [Color32; 5],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Palette {
|
||||||
|
pub fn from_theme(theme: &Theme, ctx: &Context) -> Self {
|
||||||
|
let c = |name: &str, fb: Color32| theme.var(name, ctx).unwrap_or(fb);
|
||||||
|
Self {
|
||||||
|
bg: c("bg-app", Color32::from_rgb(0x2a, 0x2a, 0x2a)),
|
||||||
|
surface: c("bg-panel", Color32::from_rgb(0x22, 0x22, 0x22)),
|
||||||
|
surface_alt: c("bg-surface-raised", Color32::from_rgb(0x3f, 0x3f, 0x3f)),
|
||||||
|
header: c("bg-header", Color32::from_rgb(0x35, 0x35, 0x35)),
|
||||||
|
line: c("border-default", Color32::from_rgb(0x44, 0x44, 0x44)),
|
||||||
|
text: c("text-primary", Color32::from_rgb(0xf6, 0xf6, 0xf6)),
|
||||||
|
text_dim: c("text-secondary", Color32::from_rgb(0xaa, 0xaa, 0xaa)),
|
||||||
|
accent: c("accent", Color32::from_rgb(0x39, 0x6c, 0xd8)),
|
||||||
|
on_accent: c("text-on-accent", Color32::WHITE),
|
||||||
|
scrim: c("scrim", Color32::from_rgba_unmultiplied(0x10, 0x14, 0x1a, 0xb0)),
|
||||||
|
accents: [
|
||||||
|
c("accent-coral", Color32::from_rgb(0xe8, 0x82, 0x6b)),
|
||||||
|
c("accent-cyan", Color32::from_rgb(0x54, 0xc3, 0xe8)),
|
||||||
|
c("accent-amber", Color32::from_rgb(0xf4, 0xa3, 0x40)),
|
||||||
|
c("accent-pink", Color32::from_rgb(0xc7, 0x5b, 0x8a)),
|
||||||
|
c("accent-violet", Color32::from_rgb(0x8a, 0x6e, 0xc0)),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
|
||||||
use super::{icons, slot_path, LayoutAnim, MobileState, StackDrag, StackPane, STACK};
|
use super::{icons, slot_path, LayoutAnim, MobileState, Palette, StackDrag, StackPane, STACK};
|
||||||
use crate::RenderContext;
|
use crate::RenderContext;
|
||||||
|
|
||||||
const N: usize = STACK.len();
|
const N: usize = STACK.len();
|
||||||
|
|
@ -34,12 +34,6 @@ const BTN_W: f32 = 44.0;
|
||||||
/// Max pixels of drag to complete a window transition (so you don't drag half the screen).
|
/// Max pixels of drag to complete a window transition (so you don't drag half the screen).
|
||||||
const TRIGGER_MAX: f32 = 150.0;
|
const TRIGGER_MAX: f32 = 150.0;
|
||||||
|
|
||||||
const C_LINE: egui::Color32 = egui::Color32::from_rgb(0x36, 0x3d, 0x49);
|
|
||||||
const C_AMBER: egui::Color32 = egui::Color32::from_rgb(0xf4, 0xa3, 0x40);
|
|
||||||
const C_DIM: egui::Color32 = egui::Color32::from_rgb(0x7c, 0x86, 0x93);
|
|
||||||
const C_BRIGHT: egui::Color32 = egui::Color32::from_rgb(0xea, 0xee, 0xf3);
|
|
||||||
const C_HEADER: egui::Color32 = egui::Color32::from_rgb(0x1f, 0x24, 0x2c);
|
|
||||||
|
|
||||||
/// A draggable boundary of the stack.
|
/// A draggable boundary of the stack.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Handle {
|
pub enum Handle {
|
||||||
|
|
@ -345,7 +339,7 @@ fn interp_layout(
|
||||||
/// The interaction (drag) target for a band's boundary is its full-width header bar. Bands are
|
/// The interaction (drag) target for a band's boundary is its full-width header bar. Bands are
|
||||||
/// laid out in the area above a reserved bottom footer (the BottomEdge handle). Band 0's header is
|
/// laid out in the area above a reserved bottom footer (the BottomEdge handle). Band 0's header is
|
||||||
/// the TopEdge handle; band i's header (i≥1) is the divider above it; the footer is the BottomEdge.
|
/// the TopEdge handle; band i's header (i≥1) is the divider above it; the footer is the BottomEdge.
|
||||||
pub fn render(ui: &mut egui::Ui, rect: egui::Rect, rc: &mut RenderContext, state: &mut MobileState) {
|
pub fn render(ui: &mut egui::Ui, rect: egui::Rect, rc: &mut RenderContext, state: &mut MobileState, pal: &Palette) {
|
||||||
let top = state.window_top;
|
let top = state.window_top;
|
||||||
let count = state.window_count;
|
let count = state.window_count;
|
||||||
|
|
||||||
|
|
@ -437,24 +431,24 @@ pub fn render(ui: &mut egui::Ui, rect: egui::Rect, rc: &mut RenderContext, state
|
||||||
brect.left_top(),
|
brect.left_top(),
|
||||||
egui::pos2(brect.right(), brect.top() + HEADER_H.min(brect.height())),
|
egui::pos2(brect.right(), brect.top() + HEADER_H.min(brect.height())),
|
||||||
);
|
);
|
||||||
draw_header(ui, hr, STACK[*slot], state.show_instruments, fullscreen);
|
draw_header(ui, hr, STACK[*slot], state.show_instruments, fullscreen, pal);
|
||||||
}
|
}
|
||||||
draw_footer(ui, footer_rect, top + count >= N);
|
draw_footer(ui, footer_rect, top + count >= N, pal);
|
||||||
|
|
||||||
// 3) Interactions on the resting header/footer rects (added last → they win the press).
|
// 3) Interactions on the resting header/footer rects (added last → they win the press).
|
||||||
handle_interactions(ui, &rest_bands, content_area, footer_rect, trigger, now, state);
|
handle_interactions(ui, &rest_bands, content_area, footer_rect, trigger, now, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_header(ui: &egui::Ui, hr: egui::Rect, sp: StackPane, show_instruments: bool, fullscreen: bool) {
|
fn draw_header(ui: &egui::Ui, hr: egui::Rect, sp: StackPane, show_instruments: bool, fullscreen: bool, pal: &Palette) {
|
||||||
let p = ui.painter();
|
let p = ui.painter();
|
||||||
// Rounded top corners so the header reads as a tab atop the pane; square at the bottom where
|
// Rounded top corners so the header reads as a tab atop the pane; square at the bottom where
|
||||||
// it meets the pane content.
|
// it meets the pane content.
|
||||||
p.rect_filled(
|
p.rect_filled(
|
||||||
hr,
|
hr,
|
||||||
egui::CornerRadius { nw: HEADER_RADIUS, ne: HEADER_RADIUS, sw: 0, se: 0 },
|
egui::CornerRadius { nw: HEADER_RADIUS, ne: HEADER_RADIUS, sw: 0, se: 0 },
|
||||||
C_HEADER,
|
pal.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, pal.line));
|
||||||
let cy = hr.center().y;
|
let cy = hr.center().y;
|
||||||
// Grip glyph on the left (Lucide).
|
// Grip glyph on the left (Lucide).
|
||||||
p.text(
|
p.text(
|
||||||
|
|
@ -462,14 +456,14 @@ fn draw_header(ui: &egui::Ui, hr: egui::Rect, sp: StackPane, show_instruments: b
|
||||||
egui::Align2::CENTER_CENTER,
|
egui::Align2::CENTER_CENTER,
|
||||||
icons::GRIP_HORIZONTAL,
|
icons::GRIP_HORIZONTAL,
|
||||||
icons::font(16.0),
|
icons::font(16.0),
|
||||||
C_DIM,
|
pal.text_dim,
|
||||||
);
|
);
|
||||||
p.text(
|
p.text(
|
||||||
egui::pos2(hr.left() + 32.0, cy),
|
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,
|
pal.text,
|
||||||
);
|
);
|
||||||
// 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.
|
||||||
p.text(
|
p.text(
|
||||||
|
|
@ -477,7 +471,7 @@ fn draw_header(ui: &egui::Ui, hr: egui::Rect, sp: StackPane, show_instruments: b
|
||||||
egui::Align2::CENTER_CENTER,
|
egui::Align2::CENTER_CENTER,
|
||||||
if fullscreen { icons::MINIMIZE } else { icons::MAXIMIZE },
|
if fullscreen { icons::MINIMIZE } else { icons::MAXIMIZE },
|
||||||
icons::font(17.0),
|
icons::font(17.0),
|
||||||
C_DIM,
|
pal.text_dim,
|
||||||
);
|
);
|
||||||
if sp == StackPane::NodeInstrument {
|
if sp == StackPane::NodeInstrument {
|
||||||
p.text(
|
p.text(
|
||||||
|
|
@ -485,16 +479,16 @@ fn draw_header(ui: &egui::Ui, hr: egui::Rect, sp: StackPane, show_instruments: b
|
||||||
egui::Align2::CENTER_CENTER,
|
egui::Align2::CENTER_CENTER,
|
||||||
icons::ARROW_LEFT_RIGHT,
|
icons::ARROW_LEFT_RIGHT,
|
||||||
icons::font(17.0),
|
icons::font(17.0),
|
||||||
C_AMBER,
|
pal.accent,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_footer(ui: &egui::Ui, fr: egui::Rect, at_end: bool) {
|
fn draw_footer(ui: &egui::Ui, fr: egui::Rect, at_end: bool, pal: &Palette) {
|
||||||
let p = ui.painter();
|
let p = ui.painter();
|
||||||
p.rect_filled(fr, 0.0, C_HEADER);
|
p.rect_filled(fr, 0.0, pal.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, pal.line));
|
||||||
let col = if at_end { C_LINE } else { C_DIM };
|
let col = if at_end { pal.line } else { pal.text_dim };
|
||||||
let cy = fr.center().y;
|
let cy = fr.center().y;
|
||||||
let galley = p.layout_no_wrap(
|
let galley = p.layout_no_wrap(
|
||||||
"pull up for more".to_string(),
|
"pull up for more".to_string(),
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,10 @@
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
|
||||||
use super::{icons, MobileState};
|
use super::{icons, MobileState, Palette};
|
||||||
use crate::menu::{MenuAction, MenuDef, MenuItemDef};
|
use crate::menu::{MenuAction, MenuDef, MenuItemDef};
|
||||||
use crate::RenderContext;
|
use crate::RenderContext;
|
||||||
|
|
||||||
const C_PANEL: egui::Color32 = egui::Color32::from_rgb(0x1f, 0x24, 0x2c);
|
|
||||||
const C_PANEL2: egui::Color32 = egui::Color32::from_rgb(0x27, 0x2d, 0x37);
|
|
||||||
const C_LINE: egui::Color32 = egui::Color32::from_rgb(0x36, 0x3d, 0x49);
|
|
||||||
const C_BRIGHT: egui::Color32 = egui::Color32::from_rgb(0xea, 0xee, 0xf3);
|
|
||||||
const C_DIM: egui::Color32 = egui::Color32::from_rgb(0x8b, 0x95, 0xa1);
|
|
||||||
const BTN: f32 = 40.0;
|
const BTN: f32 = 40.0;
|
||||||
|
|
||||||
/// Curated overflow (⋯) commands.
|
/// Curated overflow (⋯) commands.
|
||||||
|
|
@ -56,9 +51,10 @@ pub fn render(
|
||||||
full: egui::Rect,
|
full: egui::Rect,
|
||||||
rc: &mut RenderContext,
|
rc: &mut RenderContext,
|
||||||
state: &mut MobileState,
|
state: &mut MobileState,
|
||||||
|
pal: &Palette,
|
||||||
) {
|
) {
|
||||||
ui.painter().rect_filled(bar, 0.0, C_PANEL);
|
ui.painter().rect_filled(bar, 0.0, pal.header);
|
||||||
ui.painter().hline(bar.x_range(), bar.bottom(), egui::Stroke::new(1.0, C_LINE));
|
ui.painter().hline(bar.x_range(), bar.bottom(), egui::Stroke::new(1.0, pal.line));
|
||||||
|
|
||||||
// Filename (or app name when unsaved).
|
// Filename (or app name when unsaved).
|
||||||
let name = rc
|
let name = rc
|
||||||
|
|
@ -73,7 +69,7 @@ pub fn render(
|
||||||
egui::Align2::LEFT_CENTER,
|
egui::Align2::LEFT_CENTER,
|
||||||
name,
|
name,
|
||||||
egui::FontId::proportional(14.0),
|
egui::FontId::proportional(14.0),
|
||||||
C_BRIGHT,
|
pal.text,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Right cluster: ⌕ (palette) then ⋯ (overflow).
|
// Right cluster: ⌕ (palette) then ⋯ (overflow).
|
||||||
|
|
@ -82,7 +78,7 @@ pub fn render(
|
||||||
|
|
||||||
let sresp = ui.interact(palette_rect, ui.id().with("mobile_topbar_search"), egui::Sense::click());
|
let sresp = ui.interact(palette_rect, ui.id().with("mobile_topbar_search"), egui::Sense::click());
|
||||||
ui.painter().text(palette_rect.center(), egui::Align2::CENTER_CENTER, icons::SEARCH, icons::font(17.0),
|
ui.painter().text(palette_rect.center(), egui::Align2::CENTER_CENTER, icons::SEARCH, icons::font(17.0),
|
||||||
if sresp.hovered() || state.palette_open { C_BRIGHT } else { C_DIM });
|
if sresp.hovered() || state.palette_open { pal.text } else { pal.text_dim });
|
||||||
if sresp.clicked() {
|
if sresp.clicked() {
|
||||||
state.palette_open = !state.palette_open;
|
state.palette_open = !state.palette_open;
|
||||||
state.overflow_open = false;
|
state.overflow_open = false;
|
||||||
|
|
@ -91,48 +87,48 @@ pub fn render(
|
||||||
|
|
||||||
let oresp = ui.interact(overflow_rect, ui.id().with("mobile_topbar_overflow"), egui::Sense::click());
|
let oresp = ui.interact(overflow_rect, ui.id().with("mobile_topbar_overflow"), egui::Sense::click());
|
||||||
ui.painter().text(overflow_rect.center(), egui::Align2::CENTER_CENTER, icons::ELLIPSIS, icons::font(18.0),
|
ui.painter().text(overflow_rect.center(), egui::Align2::CENTER_CENTER, icons::ELLIPSIS, icons::font(18.0),
|
||||||
if oresp.hovered() || state.overflow_open { C_BRIGHT } else { C_DIM });
|
if oresp.hovered() || state.overflow_open { pal.text } else { pal.text_dim });
|
||||||
if oresp.clicked() {
|
if oresp.clicked() {
|
||||||
state.overflow_open = !state.overflow_open;
|
state.overflow_open = !state.overflow_open;
|
||||||
state.palette_open = false;
|
state.palette_open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.overflow_open {
|
if state.overflow_open {
|
||||||
render_overflow(ui, full, rc, state);
|
render_overflow(ui, full, rc, state, pal);
|
||||||
} else if state.palette_open {
|
} else if state.palette_open {
|
||||||
render_palette(ui, full, rc, state);
|
render_palette(ui, full, rc, state, pal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Common modal scrim + panel. Returns (backdrop-tapped, panel inner rect).
|
/// Common modal scrim + panel. Returns (backdrop-tapped, panel inner rect).
|
||||||
fn open_panel(ui: &mut egui::Ui, full: egui::Rect, id: &str) -> (bool, egui::Rect) {
|
fn open_panel(ui: &mut egui::Ui, full: egui::Rect, id: &str, pal: &Palette) -> (bool, egui::Rect) {
|
||||||
let scrim = ui.interact(full, ui.id().with(("mobile_topbar_scrim", id)), egui::Sense::click());
|
let scrim = ui.interact(full, ui.id().with(("mobile_topbar_scrim", id)), egui::Sense::click());
|
||||||
ui.painter().rect_filled(full, 0.0, egui::Color32::from_rgba_premultiplied(8, 10, 14, 170));
|
ui.painter().rect_filled(full, 0.0, pal.scrim);
|
||||||
let panel = egui::Rect::from_min_max(
|
let panel = egui::Rect::from_min_max(
|
||||||
egui::pos2(full.left() + 16.0, full.top() + 44.0),
|
egui::pos2(full.left() + 16.0, full.top() + 44.0),
|
||||||
egui::pos2(full.right() - 16.0, full.bottom() - 60.0),
|
egui::pos2(full.right() - 16.0, full.bottom() - 60.0),
|
||||||
);
|
);
|
||||||
ui.painter().rect_filled(panel, 14.0, C_PANEL);
|
ui.painter().rect_filled(panel, 14.0, pal.surface);
|
||||||
ui.painter().rect_stroke(panel, 14.0, egui::Stroke::new(1.0, C_LINE), egui::StrokeKind::Inside);
|
ui.painter().rect_stroke(panel, 14.0, egui::Stroke::new(1.0, pal.line), egui::StrokeKind::Inside);
|
||||||
(scrim.clicked(), panel)
|
(scrim.clicked(), panel)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn command_button(ui: &mut egui::Ui, r: egui::Rect, label: &str, key: (&str, usize)) -> bool {
|
fn command_button(ui: &mut egui::Ui, r: egui::Rect, label: &str, key: (&str, usize), pal: &Palette) -> bool {
|
||||||
let resp = ui.interact(r, ui.id().with(("mobile_cmd", key.0, key.1)), egui::Sense::click());
|
let resp = ui.interact(r, ui.id().with(("mobile_cmd", key.0, key.1)), egui::Sense::click());
|
||||||
ui.painter().rect_filled(r, 8.0, if resp.hovered() { C_PANEL2 } else { C_PANEL });
|
ui.painter().rect_filled(r, 8.0, if resp.hovered() { pal.surface_alt } else { pal.surface });
|
||||||
ui.painter().hline(r.x_range(), r.bottom(), egui::Stroke::new(1.0, C_LINE));
|
ui.painter().hline(r.x_range(), r.bottom(), egui::Stroke::new(1.0, pal.line));
|
||||||
ui.painter().text(
|
ui.painter().text(
|
||||||
egui::pos2(r.left() + 14.0, r.center().y),
|
egui::pos2(r.left() + 14.0, r.center().y),
|
||||||
egui::Align2::LEFT_CENTER,
|
egui::Align2::LEFT_CENTER,
|
||||||
label,
|
label,
|
||||||
egui::FontId::proportional(13.0),
|
egui::FontId::proportional(13.0),
|
||||||
C_BRIGHT,
|
pal.text,
|
||||||
);
|
);
|
||||||
resp.clicked()
|
resp.clicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_overflow(ui: &mut egui::Ui, full: egui::Rect, rc: &mut RenderContext, state: &mut MobileState) {
|
fn render_overflow(ui: &mut egui::Ui, full: egui::Rect, rc: &mut RenderContext, state: &mut MobileState, pal: &Palette) {
|
||||||
let (backdrop, panel) = open_panel(ui, full, "overflow");
|
let (backdrop, panel) = open_panel(ui, full, "overflow", pal);
|
||||||
let mut close = backdrop;
|
let mut close = backdrop;
|
||||||
let items = overflow_items();
|
let items = overflow_items();
|
||||||
let row_h = 44.0;
|
let row_h = 44.0;
|
||||||
|
|
@ -145,7 +141,7 @@ fn render_overflow(ui: &mut egui::Ui, full: egui::Rect, rc: &mut RenderContext,
|
||||||
if r.bottom() > inner.bottom() {
|
if r.bottom() > inner.bottom() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if command_button(ui, r, label, ("of", i)) {
|
if command_button(ui, r, label, ("of", i), pal) {
|
||||||
rc.shared.pending_menu_actions.push(*action);
|
rc.shared.pending_menu_actions.push(*action);
|
||||||
close = true;
|
close = true;
|
||||||
}
|
}
|
||||||
|
|
@ -155,8 +151,8 @@ fn render_overflow(ui: &mut egui::Ui, full: egui::Rect, rc: &mut RenderContext,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_palette(ui: &mut egui::Ui, full: egui::Rect, rc: &mut RenderContext, state: &mut MobileState) {
|
fn render_palette(ui: &mut egui::Ui, full: egui::Rect, rc: &mut RenderContext, state: &mut MobileState, pal: &Palette) {
|
||||||
let (backdrop, panel) = open_panel(ui, full, "palette");
|
let (backdrop, panel) = open_panel(ui, full, "palette", pal);
|
||||||
let mut close = backdrop;
|
let mut close = backdrop;
|
||||||
let inner = panel.shrink(8.0);
|
let inner = panel.shrink(8.0);
|
||||||
|
|
||||||
|
|
@ -184,7 +180,7 @@ fn render_palette(ui: &mut egui::Ui, full: egui::Rect, rc: &mut RenderContext, s
|
||||||
if r.bottom() > inner.bottom() {
|
if r.bottom() > inner.bottom() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if command_button(ui, r, label, ("pal", i)) {
|
if command_button(ui, r, label, ("pal", i), pal) {
|
||||||
rc.shared.pending_menu_actions.push(*action);
|
rc.shared.pending_menu_actions.push(*action);
|
||||||
close = true;
|
close = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,13 @@
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
|
||||||
use super::icons;
|
use super::{icons, Palette};
|
||||||
use crate::panes::SharedPaneState;
|
use crate::panes::SharedPaneState;
|
||||||
|
|
||||||
const C_PANEL: egui::Color32 = egui::Color32::from_rgb(0x1f, 0x24, 0x2c);
|
pub fn render(ui: &mut egui::Ui, rect: egui::Rect, shared: &mut SharedPaneState, pal: &Palette) {
|
||||||
const C_LINE: egui::Color32 = egui::Color32::from_rgb(0x36, 0x3d, 0x49);
|
|
||||||
const C_AMBER: egui::Color32 = egui::Color32::from_rgb(0xf4, 0xa3, 0x40);
|
|
||||||
const C_BRIGHT: egui::Color32 = egui::Color32::from_rgb(0xea, 0xee, 0xf3);
|
|
||||||
const C_DARK: egui::Color32 = egui::Color32::from_rgb(0x1b, 0x13, 0x0a);
|
|
||||||
|
|
||||||
pub fn render(ui: &mut egui::Ui, rect: egui::Rect, shared: &mut SharedPaneState) {
|
|
||||||
let painter = ui.painter_at(rect);
|
let painter = ui.painter_at(rect);
|
||||||
painter.rect_filled(rect, 0.0, C_PANEL);
|
painter.rect_filled(rect, 0.0, pal.surface);
|
||||||
painter.hline(rect.x_range(), rect.top(), egui::Stroke::new(1.0, C_LINE));
|
painter.hline(rect.x_range(), rect.top(), egui::Stroke::new(1.0, pal.line));
|
||||||
|
|
||||||
let cy = rect.center().y;
|
let cy = rect.center().y;
|
||||||
|
|
||||||
|
|
@ -24,14 +18,14 @@ pub fn render(ui: &mut egui::Ui, rect: egui::Rect, shared: &mut SharedPaneState)
|
||||||
let btn_center = egui::pos2(rect.left() + 20.0 + btn_r, cy);
|
let btn_center = egui::pos2(rect.left() + 20.0 + btn_r, cy);
|
||||||
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, pal.accent);
|
||||||
let glyph = if *shared.is_playing { icons::PAUSE } else { icons::PLAY };
|
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,
|
||||||
icons::font(16.0),
|
icons::font(16.0),
|
||||||
C_DARK,
|
pal.on_accent,
|
||||||
);
|
);
|
||||||
if btn_resp.clicked() {
|
if btn_resp.clicked() {
|
||||||
*shared.is_playing = !*shared.is_playing;
|
*shared.is_playing = !*shared.is_playing;
|
||||||
|
|
@ -55,7 +49,7 @@ pub fn render(ui: &mut egui::Ui, rect: egui::Rect, shared: &mut SharedPaneState)
|
||||||
egui::Align2::LEFT_CENTER,
|
egui::Align2::LEFT_CENTER,
|
||||||
&tc,
|
&tc,
|
||||||
egui::FontId::monospace(13.0),
|
egui::FontId::monospace(13.0),
|
||||||
C_BRIGHT,
|
pal.text,
|
||||||
);
|
);
|
||||||
let tc_width = 78.0;
|
let tc_width = 78.0;
|
||||||
|
|
||||||
|
|
@ -71,18 +65,18 @@ pub fn render(ui: &mut egui::Ui, rect: egui::Rect, shared: &mut SharedPaneState)
|
||||||
ui.id().with("mobile_transport_scrub"),
|
ui.id().with("mobile_transport_scrub"),
|
||||||
egui::Sense::click_and_drag(),
|
egui::Sense::click_and_drag(),
|
||||||
);
|
);
|
||||||
painter.rect_filled(scrub_rect, 3.0, C_LINE);
|
painter.rect_filled(scrub_rect, 3.0, pal.line);
|
||||||
let frac = (*shared.playback_time / duration).clamp(0.0, 1.0) as f32;
|
let frac = (*shared.playback_time / duration).clamp(0.0, 1.0) as f32;
|
||||||
let filled = egui::Rect::from_min_max(
|
let filled = egui::Rect::from_min_max(
|
||||||
scrub_rect.min,
|
scrub_rect.min,
|
||||||
egui::pos2(scrub_rect.left() + scrub_rect.width() * frac, scrub_rect.bottom()),
|
egui::pos2(scrub_rect.left() + scrub_rect.width() * frac, scrub_rect.bottom()),
|
||||||
);
|
);
|
||||||
painter.rect_filled(filled, 3.0, C_AMBER.gamma_multiply(0.5));
|
painter.rect_filled(filled, 3.0, pal.accent.gamma_multiply(0.5));
|
||||||
let head_x = scrub_rect.left() + scrub_rect.width() * frac;
|
let head_x = scrub_rect.left() + scrub_rect.width() * frac;
|
||||||
painter.vline(
|
painter.vline(
|
||||||
head_x,
|
head_x,
|
||||||
(scrub_rect.top() - 4.0)..=(scrub_rect.bottom() + 4.0),
|
(scrub_rect.top() - 4.0)..=(scrub_rect.bottom() + 4.0),
|
||||||
egui::Stroke::new(2.0, C_AMBER),
|
egui::Stroke::new(2.0, pal.accent),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (scrub_resp.dragged() || scrub_resp.clicked()) && scrub_rect.width() > 0.0 {
|
if (scrub_resp.dragged() || scrub_resp.clicked()) && scrub_rect.width() > 0.0 {
|
||||||
|
|
|
||||||
|
|
@ -519,6 +519,73 @@ impl Theme {
|
||||||
self.resolve(context, ctx).border_color.unwrap_or(fallback)
|
self.resolve(context, ctx).border_color.unwrap_or(fallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Look up a CSS custom property (variable) as a color, respecting the active light/dark mode.
|
||||||
|
/// `name` is given without the leading `--` (e.g. `"bg-surface"`). Dark overrides light.
|
||||||
|
pub fn var(&self, name: &str, ctx: &egui::Context) -> Option<egui::Color32> {
|
||||||
|
let mut vars = self.light_variables.clone();
|
||||||
|
if self.is_dark(ctx) {
|
||||||
|
vars.extend(self.dark_variables.clone());
|
||||||
|
}
|
||||||
|
let raw = vars.get(name)?.clone();
|
||||||
|
parse_color_value(&raw, &vars)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply the theme's core palette variables to egui's global `Visuals`, so the standard egui
|
||||||
|
/// widgets (buttons, text fields, dialogs, pane chrome) share the same colors as the mobile UI.
|
||||||
|
/// Cheap enough to call every frame; respects the active light/dark mode.
|
||||||
|
pub fn apply_to_egui(&self, ctx: &egui::Context) {
|
||||||
|
let is_dark = self.is_dark(ctx);
|
||||||
|
let v = |name: &str, fb: egui::Color32| self.var(name, ctx).unwrap_or(fb);
|
||||||
|
let g = |n: u8| egui::Color32::from_gray(n);
|
||||||
|
|
||||||
|
let text = v("text-primary", if is_dark { g(230) } else { g(20) });
|
||||||
|
let bg_app = v("bg-app", if is_dark { g(42) } else { g(224) });
|
||||||
|
let panel = v("bg-panel", bg_app);
|
||||||
|
let surface = v("bg-surface", panel);
|
||||||
|
let raised = v("bg-surface-raised", surface);
|
||||||
|
let sunken = v("bg-surface-sunken", bg_app);
|
||||||
|
let border = v("border-default", if is_dark { g(68) } else { g(153) });
|
||||||
|
let accent = v("accent", egui::Color32::from_rgb(0x39, 0x6c, 0xd8));
|
||||||
|
let on_accent = v("text-on-accent", egui::Color32::WHITE);
|
||||||
|
let stroke = |c: egui::Color32| egui::Stroke::new(1.0, c);
|
||||||
|
|
||||||
|
let mut visuals = if is_dark { egui::Visuals::dark() } else { egui::Visuals::light() };
|
||||||
|
visuals.panel_fill = panel;
|
||||||
|
visuals.window_fill = panel;
|
||||||
|
visuals.window_stroke = stroke(border);
|
||||||
|
visuals.extreme_bg_color = sunken;
|
||||||
|
visuals.faint_bg_color = surface;
|
||||||
|
visuals.override_text_color = Some(text);
|
||||||
|
visuals.hyperlink_color = accent;
|
||||||
|
|
||||||
|
let w = &mut visuals.widgets;
|
||||||
|
w.noninteractive.bg_fill = panel;
|
||||||
|
w.noninteractive.weak_bg_fill = panel;
|
||||||
|
w.noninteractive.bg_stroke = stroke(border);
|
||||||
|
w.noninteractive.fg_stroke = stroke(text);
|
||||||
|
w.inactive.bg_fill = surface;
|
||||||
|
w.inactive.weak_bg_fill = surface;
|
||||||
|
w.inactive.bg_stroke = stroke(border);
|
||||||
|
w.inactive.fg_stroke = stroke(text);
|
||||||
|
w.hovered.bg_fill = raised;
|
||||||
|
w.hovered.weak_bg_fill = raised;
|
||||||
|
w.hovered.bg_stroke = stroke(border);
|
||||||
|
w.hovered.fg_stroke = stroke(text);
|
||||||
|
w.active.bg_fill = accent;
|
||||||
|
w.active.weak_bg_fill = accent;
|
||||||
|
w.active.bg_stroke = stroke(accent);
|
||||||
|
w.active.fg_stroke = stroke(on_accent);
|
||||||
|
w.open.bg_fill = surface;
|
||||||
|
w.open.weak_bg_fill = surface;
|
||||||
|
w.open.bg_stroke = stroke(border);
|
||||||
|
w.open.fg_stroke = stroke(text);
|
||||||
|
|
||||||
|
visuals.selection.bg_fill = accent.linear_multiply(0.4);
|
||||||
|
visuals.selection.stroke = stroke(on_accent);
|
||||||
|
|
||||||
|
ctx.set_visuals(visuals);
|
||||||
|
}
|
||||||
|
|
||||||
/// Convenience: resolve and extract a dimension with fallback
|
/// Convenience: resolve and extract a dimension with fallback
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn dimension(&self, context: &[&str], ctx: &egui::Context, property: &str, fallback: f32) -> f32 {
|
pub fn dimension(&self, context: &[&str], ctx: &egui::Context, property: &str, fallback: f32) -> f32 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue