Blacksite/crates/editor_ui/src/materials/advanced_preview.rs
Rbanh 53dc1e44d8
Some checks are pending
CI / Format, lint, test, build (push) Waiting to run
feat: add production UI gallery and inspector system
2026-07-18 12:05:26 -04:00

287 lines
8.2 KiB
Rust

//! Non-interactive Penpot v2.3.1 renderer-control preview.
use egui;
use egui_phosphor_icons::icons;
use crate::design_system::typography::TypeRole;
use crate::design_system::{self, ADVANCED_COLLAPSED_HEIGHT, ADVANCED_EXPANDED_HEIGHT};
const PLANNED_TOOLTIP: &str = "Planned — renderer support pending";
pub(super) fn unsupported_advanced_preview(ui: &mut egui::Ui) {
let open_id = ui.make_persistent_id("material_unsupported_advanced_open");
let reveal_id = open_id.with("reveal_complete_body");
let mut open = ui
.ctx()
.data_mut(|data| data.get_persisted::<bool>(open_id))
.unwrap_or(false);
let width = ui.available_width().max(1.0);
let mode = super::layout::ResponsiveMaterialLayout::for_width(width);
let height = if open {
if mode == super::layout::ResponsiveMaterialLayout::Transient {
254.0
} else {
ADVANCED_EXPANDED_HEIGHT
}
} else {
ADVANCED_COLLAPSED_HEIGHT
};
let palette = design_system::palette(ui);
let (rect, _) = ui.allocate_exact_size(egui::vec2(width, height), egui::Sense::hover());
ui.painter().rect(
rect,
5.0,
palette.panel,
egui::Stroke::new(1.0_f32, palette.border),
egui::StrokeKind::Inside,
);
let header = egui::Rect::from_min_size(rect.min, egui::vec2(rect.width(), 30.0));
ui.painter().rect_filled(header, 4.0, palette.panel);
let header_response = ui.interact(header, open_id.with("header"), egui::Sense::click());
if header_response.clicked() {
open = !open;
ui.ctx().data_mut(|data| data.insert_temp(reveal_id, open));
}
header_response.on_hover_text(if open {
"Collapse Advanced preview"
} else {
"Expand Advanced preview"
});
ui.painter().text(
header.left_center() + egui::vec2(12.0, 0.0),
egui::Align2::LEFT_CENTER,
if open {
icons::CARET_DOWN.as_str()
} else {
icons::CARET_RIGHT.as_str()
},
egui::FontId::new(10.0, egui::FontFamily::Name("phosphor-bold".into())),
palette.text_secondary,
);
ui.painter().text(
header.left_center() + egui::vec2(26.0, 0.0),
egui::Align2::LEFT_CENTER,
"Advanced",
TypeRole::Section.font(),
palette.text_secondary,
);
if !open && rect.width() >= 300.0 {
ui.painter().hline(
(header.left() + 82.0)..=(header.right() - 158.0),
header.center().y,
egui::Stroke::new(1.0_f32, palette.border),
);
ui.painter().text(
header.right_center() - egui::vec2(12.0, 0.0),
egui::Align2::RIGHT_CENTER,
PLANNED_TOOLTIP,
TypeRole::Small.font(),
palette.text_muted,
);
}
if open {
match mode {
super::layout::ResponsiveMaterialLayout::Reference => reference_controls(ui, rect),
super::layout::ResponsiveMaterialLayout::Compact => {
compact_reference_controls(ui, rect)
}
super::layout::ResponsiveMaterialLayout::Transient => compact_controls(ui, rect),
}
}
let reveal_complete_body = ui
.ctx()
.data_mut(|data| data.get_temp::<bool>(reveal_id))
.unwrap_or(false);
if open && reveal_complete_body && rect.height() > ADVANCED_COLLAPSED_HEIGHT {
ui.scroll_to_rect(rect, Some(egui::Align::BOTTOM));
ui.ctx().data_mut(|data| data.remove::<bool>(reveal_id));
}
ui.ctx()
.data_mut(|data| data.insert_persisted(open_id, open));
}
fn reference_controls(ui: &mut egui::Ui, rect: egui::Rect) {
disabled_field(ui, rect, 28.0, 42.0, "Culling", "Back");
disabled_field(ui, rect, 28.0, 76.0, "Render Queue", "From Shader");
disabled_toggle(ui, rect, 328.0, 42.0, "Depth Write", true);
disabled_toggle(ui, rect, 328.0, 76.0, "Receive Shadows", true);
planned_note(ui, rect, 28.0, 124.0);
}
fn compact_reference_controls(ui: &mut egui::Ui, rect: egui::Rect) {
disabled_field_at(ui, rect, 20.0, 90.0, 90.0, 42.0, "Culling", "Back");
disabled_field_at(
ui,
rect,
20.0,
90.0,
90.0,
76.0,
"Render Queue",
"From Shader",
);
disabled_toggle_at(
ui,
rect,
196.0,
rect.width() - 38.0,
42.0,
"Depth Write",
true,
);
disabled_toggle_at(
ui,
rect,
196.0,
rect.width() - 38.0,
76.0,
"Receive Shadows",
true,
);
planned_note(ui, rect, 20.0, 124.0);
}
fn compact_controls(ui: &mut egui::Ui, rect: egui::Rect) {
disabled_field(ui, rect, 20.0, 42.0, "Culling", "Back");
disabled_field(ui, rect, 20.0, 78.0, "Render Queue", "From Shader");
disabled_toggle(ui, rect, 20.0, 124.0, "Depth Write", true);
disabled_toggle(ui, rect, 20.0, 160.0, "Receive Shadows", true);
planned_note(ui, rect, 20.0, 210.0);
}
fn disabled_field(ui: &mut egui::Ui, rect: egui::Rect, x: f32, y: f32, label: &str, value: &str) {
disabled_field_at(ui, rect, x, x + 106.0, 150.0, y, label, value);
}
#[expect(
clippy::too_many_arguments,
reason = "explicit Penpot preview geometry"
)]
fn disabled_field_at(
ui: &mut egui::Ui,
rect: egui::Rect,
label_x: f32,
field_x: f32,
field_width: f32,
y: f32,
label: &str,
value: &str,
) {
let palette = design_system::palette(ui);
ui.painter().text(
rect.min + egui::vec2(label_x, y),
egui::Align2::LEFT_CENTER,
label,
TypeRole::Body.font(),
palette.text_muted,
);
let field = egui::Rect::from_min_size(
egui::pos2(rect.left() + field_x, rect.top() + y - 7.0),
egui::vec2(field_width.min(rect.width() - field_x - 8.0).max(1.0), 24.0),
);
paint_disabled_field(ui, field, value);
}
fn disabled_toggle(
ui: &mut egui::Ui,
rect: egui::Rect,
x: f32,
y: f32,
label: &str,
enabled: bool,
) {
disabled_toggle_at(ui, rect, x, x + 106.0, y, label, enabled);
}
fn disabled_toggle_at(
ui: &mut egui::Ui,
rect: egui::Rect,
label_x: f32,
toggle_x: f32,
y: f32,
label: &str,
enabled: bool,
) {
let palette = design_system::palette(ui);
ui.painter().text(
rect.min + egui::vec2(label_x, y),
egui::Align2::LEFT_CENTER,
label,
TypeRole::Body.font(),
palette.text_muted,
);
let toggle = egui::Rect::from_min_size(
egui::pos2(rect.left() + toggle_x, rect.top() + y - 8.0),
egui::vec2(30.0, 16.0),
);
ui.painter().rect(
toggle,
8.0,
palette.control,
egui::Stroke::new(1.0_f32, palette.border),
egui::StrokeKind::Inside,
);
let thumb_x = if enabled {
toggle.right() - 8.0
} else {
toggle.left() + 8.0
};
ui.painter().circle_filled(
egui::pos2(thumb_x, toggle.center().y),
5.0,
palette.text_muted,
);
ui.interact(
toggle,
ui.make_persistent_id(("advanced_toggle", label)),
egui::Sense::hover(),
)
.on_hover_text(PLANNED_TOOLTIP);
}
fn paint_disabled_field(ui: &mut egui::Ui, rect: egui::Rect, value: &str) {
let palette = design_system::palette(ui);
ui.painter().rect(
rect,
4.0,
palette.control,
egui::Stroke::new(1.0_f32, palette.border),
egui::StrokeKind::Inside,
);
ui.painter().text(
rect.left_center() + egui::vec2(10.0, 0.0),
egui::Align2::LEFT_CENTER,
value,
TypeRole::Body.font(),
palette.text_muted,
);
ui.interact(
rect,
ui.make_persistent_id(("advanced_field", value)),
egui::Sense::hover(),
)
.on_hover_text(PLANNED_TOOLTIP);
}
fn planned_note(ui: &egui::Ui, rect: egui::Rect, x: f32, y: f32) {
let palette = design_system::palette(ui);
ui.painter().text(
rect.min + egui::vec2(x, y),
egui::Align2::LEFT_CENTER,
PLANNED_TOOLTIP,
TypeRole::Small.font(),
palette.text_muted,
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn penpot_advanced_preview_matches_v231_heights() {
assert_eq!(ADVANCED_COLLAPSED_HEIGHT, 30.0);
assert_eq!(ADVANCED_EXPANDED_HEIGHT, 178.0);
}
}