287 lines
8.2 KiB
Rust
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);
|
|
}
|
|
}
|