mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 13:01:52 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2605261665 |
@@ -318,7 +318,7 @@ fn selected_row_paints_tab_soft_fill_and_accent_text_in_both_layouts() {
|
||||
let mut app = SolveApp::new(Default::default(), Default::default());
|
||||
app.ui.layout = mode;
|
||||
app.ui.tab = tab.into();
|
||||
crate::theme::TabAccent::publish(&app, &ctx);
|
||||
app.publish_tab_accent(&ctx);
|
||||
let accent = crate::theme::TabAccent::for_tab(&Tokens::get(), tab);
|
||||
let mut row_rect = Rect::NOTHING;
|
||||
let out = crate::test_support::frame(&ctx, egui::RawInput::default(), |ui| {
|
||||
@@ -677,7 +677,7 @@ fn collapsed_rail_shares_header_hover_inset_and_expands_from_its_whole_card() {
|
||||
app.ui.tab = "SURFACE".into();
|
||||
app.preferences.timeline_left = true;
|
||||
app.preferences.timeline_left_browser_collapsed = collapsed;
|
||||
crate::theme::TabAccent::publish(&app, &ctx);
|
||||
app.publish_tab_accent(&ctx);
|
||||
let bounds = Rect::from_min_size(Pos2::ZERO, vec2(600.0, 700.0));
|
||||
let draw = |app: &mut SolveApp, events| {
|
||||
crate::test_support::frame(&ctx, egui::RawInput { screen_rect: Some(bounds), events, ..Default::default() }, |ui| {
|
||||
|
||||
@@ -837,6 +837,12 @@ impl Drop for SolveApp {
|
||||
}
|
||||
|
||||
impl SolveApp {
|
||||
/// Publish this frame's tab accent from the open tab, the sketch being edited and the active
|
||||
/// tool or dialog, the three values [`theme::TabAccent::publish`] takes.
|
||||
pub(crate) fn publish_tab_accent(&self, ctx: &egui::Context) {
|
||||
theme::TabAccent::publish(&self.ui.tab, self.session.active_sketch.is_some(), self.tool.is_some() || self.dialog.is_some(), ctx);
|
||||
}
|
||||
|
||||
pub fn new(mut session: Session, services: Services) -> Self {
|
||||
#[cfg(test)]
|
||||
let image_host = Some(solidcraft_engine::render::job::CpuHost::cooperative());
|
||||
@@ -3276,7 +3282,7 @@ impl SolveApp {
|
||||
theme::apply(ctx);
|
||||
self.styled = true;
|
||||
}
|
||||
theme::TabAccent::publish(self, ctx);
|
||||
self.publish_tab_accent(ctx);
|
||||
if !self.fitted {
|
||||
self.fit_view();
|
||||
self.fitted = true;
|
||||
@@ -3520,7 +3526,7 @@ impl SolveApp {
|
||||
if !floating && !self.embedded {
|
||||
toolbar::toolbar(self, ui);
|
||||
}
|
||||
theme::TabAccent::publish(self, ui.ctx());
|
||||
self.publish_tab_accent(ui.ctx());
|
||||
browser::publish_row_prefs(self, ui.ctx());
|
||||
if !floating {
|
||||
agent_chat::show(self, ui);
|
||||
@@ -3573,7 +3579,7 @@ impl SolveApp {
|
||||
self.show_workspace(&mut viewport_ui);
|
||||
shell::paint_top_corners(ui.painter(), full, theme::Tokens::get().app_bar);
|
||||
toolbar::toolbar(self, ui);
|
||||
theme::TabAccent::publish(self, ui.ctx());
|
||||
self.publish_tab_accent(ui.ctx());
|
||||
}
|
||||
if floating && show_timeline && !left_timeline {
|
||||
timeline::timeline(self, ui);
|
||||
|
||||
@@ -975,7 +975,7 @@ impl crate::SolveApp {
|
||||
self.shell.history_dock_space = None;
|
||||
self.shell.history_folding = None;
|
||||
self.shell.history_fold_motion = None;
|
||||
crate::theme::TabAccent::publish(self, ui.ctx());
|
||||
self.publish_tab_accent(ui.ctx());
|
||||
crate::browser::publish_row_prefs(self, ui.ctx());
|
||||
let mut placement = render_domain;
|
||||
self.shell.status_bar = if crate::status_bar::shown(self) {
|
||||
|
||||
@@ -519,7 +519,7 @@ fn stage_catalogs_fill_a_290_by_530_panel_and_keep_two_line_labels_inside_tiles(
|
||||
h.app.preferences.render_browser_side = "left".into();
|
||||
super::super::select_image_method(&mut h.app, method);
|
||||
library::open_category(&mut h.app, kind);
|
||||
crate::theme::TabAccent::publish(&h.app, &h.ctx);
|
||||
h.app.publish_tab_accent(&h.ctx);
|
||||
let document = h.app.session.doc.to_json();
|
||||
let history = (h.app.session.undo.len(), h.app.session.redo.len());
|
||||
// Panel::workspace reserves an 8 pt bottom gap in these bounds.
|
||||
|
||||
+28
-29
@@ -20,7 +20,7 @@ thread_local! {
|
||||
/// must not change one another's palette while a frame or icon texture is being built.
|
||||
static DARK: Cell<bool> = const { Cell::new(true) };
|
||||
/// The colour vision mode, per thread. Each app
|
||||
/// sets it from its own preferences at the start of every frame (`SolveApp::logic`), on the
|
||||
/// sets it from its own preferences at the start of every frame (in its frame logic), on the
|
||||
/// thread that draws it. Tests run many apps side by side on their own threads in one process:
|
||||
/// with one global, a test in a mode changed the colours under another one
|
||||
/// (`hover_stays_off_during_spin_and_animated_views` failed that way). No worker thread reads
|
||||
@@ -580,7 +580,11 @@ pub(crate) fn scoped_tokens(tokens: Tokens) -> impl Drop {
|
||||
}
|
||||
|
||||
/// Temporarily bind a panel's paint palette without publishing a theme change.
|
||||
pub(crate) fn scoped_panel_tokens(tokens: Tokens) -> impl Drop {
|
||||
///
|
||||
/// `Tokens::get_in` returns these tokens first until the returned guard drops, which restores
|
||||
/// the previous override. A host uses it to draw any component in another palette or accent
|
||||
/// (for example another workspace's) without editing the component.
|
||||
pub fn scoped_panel_tokens(tokens: Tokens) -> impl Drop {
|
||||
struct Restore {
|
||||
previous: Option<Tokens>,
|
||||
// The override belongs to this UI thread, so its guard must not move to another one.
|
||||
@@ -791,11 +795,13 @@ impl TabAccent {
|
||||
ctx.data(|d| d.get_temp::<Option<&'static str>>(egui::Id::unique("sc_tab_icon_accent"))).flatten()
|
||||
}
|
||||
|
||||
pub fn publish(app: &crate::SolveApp, ctx: &egui::Context) {
|
||||
/// Publish the accent of `tab` for this frame. `sketch_open` is whether a sketch is being
|
||||
/// edited, `tool_active` whether a tool or dialog is open; both only steer focus painting.
|
||||
pub fn publish(tab: &str, sketch_open: bool, tool_active: bool, ctx: &egui::Context) {
|
||||
let now = ctx.input(|i| i.time);
|
||||
let (published, accent, fading, style_surfaces) = ctx.data_mut(|data| {
|
||||
let state = data.get_temp_mut_or_default::<TabThemeState>(egui::Id::unique(TAB_STATE));
|
||||
let (published, accent, fading) = state.publish(&app.ui.tab, now);
|
||||
let (published, accent, fading) = state.publish(tab, now);
|
||||
let style_surfaces = published.tab_surfaces || state.styled_surfaces;
|
||||
state.styled_surfaces = published.tab_surfaces;
|
||||
(published, accent, fading, style_surfaces)
|
||||
@@ -818,9 +824,9 @@ impl TabAccent {
|
||||
}
|
||||
ctx.data_mut(|d| {
|
||||
d.insert_temp(egui::Id::unique("sc_tab_accent"), accent);
|
||||
d.insert_temp(egui::Id::unique("sc_tab_icon_accent"), icon_accent_for_tab(&app.ui.tab));
|
||||
d.insert_temp(egui::Id::unique("sc_sketch_open"), app.session.active_sketch.is_some());
|
||||
d.insert_temp(egui::Id::unique("sc_tool_accent"), app.tool.is_some() || app.dialog.is_some());
|
||||
d.insert_temp(egui::Id::unique("sc_tab_icon_accent"), icon_accent_for_tab(tab));
|
||||
d.insert_temp(egui::Id::unique("sc_sketch_open"), sketch_open);
|
||||
d.insert_temp(egui::Id::unique("sc_tool_accent"), tool_active);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1958,15 +1964,13 @@ mod tests {
|
||||
#[test]
|
||||
fn tabs_publish_requests_repaints_only_during_fade_and_zero_is_instant() {
|
||||
use crate::theme_files::OptionValue;
|
||||
let mut app = crate::SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default());
|
||||
let ctx = crate::fonts::test_context();
|
||||
let mut state = tabs_state("dark", BTreeMap::new());
|
||||
state.publish("MODEL", 0.0);
|
||||
ctx.data_mut(|d| d.insert_temp(egui::Id::unique(TAB_STATE), state));
|
||||
app.ui.tab = "SKETCH".into();
|
||||
let frame = |time| {
|
||||
let mut out = ctx.run_ui(egui::RawInput { time: Some(time), ..Default::default() }, |ui| {
|
||||
TabAccent::publish(&app, ui.ctx());
|
||||
TabAccent::publish("SKETCH", false, false, ui.ctx());
|
||||
crate::gpu::paint_background(ui.painter(), ui.max_rect(), &Tokens::get());
|
||||
});
|
||||
let colors: Vec<_> = out
|
||||
@@ -2175,7 +2179,7 @@ mod tests {
|
||||
themes.extend(crate::theme_files::builtin().iter().map(|theme| (theme.name.as_str(), crate::theme_files::resolve(theme))));
|
||||
let mut failures = Vec::new();
|
||||
for (name, tokens) in themes {
|
||||
for tab in crate::workspace::TABS.iter().copied().chain(["SKETCH", "MESH", "FORM", "SHEET METAL", "RENDER"]) {
|
||||
for tab in super::TAB_NAMES.iter().copied() {
|
||||
let accent = super::TabAccent::for_tab(&tokens, tab);
|
||||
let ratio = super::contrast_ratio(accent.ink, accent.soft);
|
||||
if ratio < 4.5 {
|
||||
@@ -2209,20 +2213,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn tab_accent_context_is_per_app_and_does_not_change_widget_visuals() {
|
||||
let mut harness = crate::scenario::Harness::new();
|
||||
let app = &mut harness.app;
|
||||
let first = crate::fonts::test_context();
|
||||
let second = crate::fonts::test_context();
|
||||
let before = first.style_of(egui::Theme::Dark).visuals.clone();
|
||||
app.ui.tab = "SURFACE".into();
|
||||
super::TabAccent::publish(app, &first);
|
||||
super::TabAccent::publish("SURFACE", false, false, &first);
|
||||
assert_eq!(super::TabAccent::current(&first).color, super::Tokens::get().surface_fill);
|
||||
assert_eq!(super::TabAccent::current(&second).color, super::Tokens::get().accent);
|
||||
assert_eq!(super::TabAccent::current_icon_accent(&first), Some("surface"));
|
||||
assert_eq!(super::TabAccent::current_icon_accent(&second), None);
|
||||
assert_eq!(first.style_of(egui::Theme::Dark).visuals, before, "dialog OK, checkboxes and status styles must not inherit the tab");
|
||||
app.ui.tab = "MODEL".into();
|
||||
super::TabAccent::publish(app, &first);
|
||||
super::TabAccent::publish("MODEL", false, false, &first);
|
||||
assert_eq!(super::TabAccent::current(&first).soft, super::Tokens::get().accent_soft);
|
||||
assert_eq!(super::TabAccent::current_icon_accent(&first), None);
|
||||
}
|
||||
@@ -2232,7 +2232,6 @@ mod tests {
|
||||
use crate::theme_files::{OptionValue, ThemeFile};
|
||||
for base in ["dark", "light"] {
|
||||
let ctx = crate::fonts::test_context();
|
||||
let mut app = crate::SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default());
|
||||
let file = crate::theme_files::tab_fixture(base);
|
||||
set_dark(file.base.is_dark());
|
||||
set_vision(ColorVision::Off);
|
||||
@@ -2247,19 +2246,19 @@ mod tests {
|
||||
});
|
||||
set_custom(custom)
|
||||
};
|
||||
let publish = |app: &crate::SolveApp, time| {
|
||||
ctx.run_ui(egui::RawInput { time: Some(time), ..Default::default() }, |ui| TabAccent::publish(app, ui.ctx())).textures_delta.clear();
|
||||
let publish = |tab: &str, time| {
|
||||
ctx.run_ui(egui::RawInput { time: Some(time), ..Default::default() }, |ui| TabAccent::publish(tab, false, false, ui.ctx()))
|
||||
.textures_delta
|
||||
.clear();
|
||||
};
|
||||
prepare(&file, Some(true));
|
||||
apply(&ctx);
|
||||
let before = ctx.global_style().visuals.clone();
|
||||
let base_custom = CUSTOM.get();
|
||||
app.ui.tab = "MODEL".into();
|
||||
publish(&app, 0.0);
|
||||
publish("MODEL", 0.0);
|
||||
assert_eq!(ctx.global_style().visuals, before);
|
||||
app.ui.tab = "SKETCH".into();
|
||||
publish(&app, 1.0);
|
||||
publish(&app, 1.12);
|
||||
publish("SKETCH", 1.0);
|
||||
publish("SKETCH", 1.12);
|
||||
let panel = Tokens::get().panel;
|
||||
assert_ne!(panel, before.panel_fill, "exercise an actual animated panel fill");
|
||||
let mut animated = before.clone();
|
||||
@@ -2268,24 +2267,24 @@ mod tests {
|
||||
assert_eq!(ctx.global_style().visuals, animated, "all non-surface widget/selection/status visuals stay unchanged");
|
||||
// Repair either owned fill independently, even when the panel already matches.
|
||||
ctx.global_style_mut(|style| style.visuals.window_fill = before.window_fill);
|
||||
publish(&app, 1.12);
|
||||
publish("SKETCH", 1.12);
|
||||
assert_eq!(ctx.global_style().visuals, animated);
|
||||
|
||||
assert!(!prepare(&file, Some(false)), "toggling surfaces does not change the base Custom tokens");
|
||||
assert_eq!(CUSTOM.get(), base_custom);
|
||||
// Deliberately omit apply(): a full restyle would conceal stale animated fills.
|
||||
publish(&app, 1.13);
|
||||
publish("SKETCH", 1.13);
|
||||
assert_eq!(ctx.global_style().visuals, before, "turning surfaces off must restore both base fills");
|
||||
|
||||
assert!(!prepare(&file, Some(true)));
|
||||
publish(&app, 2.0);
|
||||
publish("SKETCH", 2.0);
|
||||
assert_ne!(ctx.global_style().visuals.panel_fill, before.panel_fill);
|
||||
let mut plain = file.clone();
|
||||
plain.tabs.clear();
|
||||
plain.options.clear();
|
||||
assert!(!prepare(&plain, None), "a no-option theme can retain the exact same base tokens");
|
||||
assert_eq!(CUSTOM.get(), base_custom);
|
||||
publish(&app, 2.1);
|
||||
publish("SKETCH", 2.1);
|
||||
assert_eq!(ctx.global_style().visuals, before, "restoring a no-option theme must release the surface fills");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1216,7 +1216,7 @@ const ELEMENTS: &[Element] = &[
|
||||
let (r, _) = ui.allocate_exact_size(vec2(360.0, 360.0), egui::Sense::hover());
|
||||
let mut app = crate::test_support::sample_app();
|
||||
app.ui.tab = "SURFACE".into();
|
||||
crate::theme::TabAccent::publish(&app, ui.ctx());
|
||||
app.publish_tab_accent(ui.ctx());
|
||||
radial_menu(ui, r.center(), &crate::context_menu::radial_items(&app), Some(3));
|
||||
}),
|
||||
element!("workspace_menu", |ui, _| {
|
||||
@@ -2773,7 +2773,7 @@ fn confirm_radial_and_history_rows_keep_multitone_artwork() {
|
||||
ctx.set_visuals(if dark { egui::Visuals::dark() } else { egui::Visuals::light() });
|
||||
let mut app = crate::SolveApp::new(Default::default(), Default::default());
|
||||
app.ui.tab = tab.into();
|
||||
crate::theme::TabAccent::publish(&app, &ctx);
|
||||
app.publish_tab_accent(&ctx);
|
||||
let bounds = egui::Rect::from_min_size(egui::Pos2::ZERO, vec2(600.0, 360.0));
|
||||
let mut output = ctx.run_ui(egui::RawInput { screen_rect: Some(bounds), ..Default::default() }, |ui| {
|
||||
ui.painter().rect_filled(bounds, 0.0, Tokens::get().panel);
|
||||
|
||||
@@ -888,7 +888,7 @@ fn sketch_previews_selection_band_and_number_focus_follow_sketch_accent() {
|
||||
}
|
||||
h.app.ui.tab = "MODEL".into(); // A sketch remains open when another ribbon tab is selected.
|
||||
h.app.tool = crate::tools::Tool::for_command("sketch.line");
|
||||
crate::theme::TabAccent::publish(&h.app, &h.ctx);
|
||||
h.app.publish_tab_accent(&h.ctx);
|
||||
let output = draw(&h.ctx, |ui| {
|
||||
let r = ui.add(egui::TextEdit::singleline(&mut String::from("20")));
|
||||
r.request_focus();
|
||||
|
||||
Reference in New Issue
Block a user