diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index adeb902a9..f3a4e0f3b 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -81,6 +81,13 @@ impl OpenCADStudio { pub fn view_main(&self) -> Element<'_, Message> { let i = self.active_tab; let tab = &self.tabs[i]; + let theme_text = self.active_theme.extended_palette().background.base.text; + let viewcube_text_color = [ + theme_text.r, + theme_text.g, + theme_text.b, + theme_text.a, + ]; let is_paper = tab.scene.current_layout != "Model"; // Adaptive corner widgets: the ViewCube shows only while the active // viewport is wide enough to hold it *beside* the render-mode bar, whose @@ -137,6 +144,7 @@ impl OpenCADStudio { &tab.scene, viewcube_visible, tab.render_mode, + viewcube_text_color, )) .width(Fill) .height(Fill) @@ -172,6 +180,7 @@ impl OpenCADStudio { show_viewcube, render_mode, idx, + viewcube_text_color, )) .width(Fill) .height(Fill), diff --git a/src/app/view/overlay.rs b/src/app/view/overlay.rs index 134044722..a9ba0d98b 100644 --- a/src/app/view/overlay.rs +++ b/src/app/view/overlay.rs @@ -841,6 +841,7 @@ pub(super) fn snap_override_overlay(pos: iced::Point) -> Element<'static, Messag theme.extended_palette().primary.weak.color )), border: Border::default(), + text_color: theme.extended_palette().background.base.text, ..Default::default() }) .padding(2); diff --git a/src/app/view/viewcube.rs b/src/app/view/viewcube.rs index e566bbe65..25d0640a7 100644 --- a/src/app/view/viewcube.rs +++ b/src/app/view/viewcube.rs @@ -42,6 +42,7 @@ fn vc_btn<'a>(content: Element<'a, Message>, size: f32, msg: Message) -> Element radius: 3.0.into(), ..Default::default() }, + text_color: theme.extended_palette().background.base.text, ..Default::default() }) .into() diff --git a/src/scene/pipeline/viewcube.rs b/src/scene/pipeline/viewcube.rs index 2105ea23f..74f3d0b83 100644 --- a/src/scene/pipeline/viewcube.rs +++ b/src/scene/pipeline/viewcube.rs @@ -576,6 +576,7 @@ impl ViewCubeText { vp_w: u32, vp_h: u32, cube_px: u32, + text_color: [f32; 4], ) { let (vw, vh) = (vp_w as f32, vp_h as f32); let cube_half = cube_px as f32 * VIEWCUBE_SCALE; @@ -637,7 +638,12 @@ impl ViewCubeText { continue; } let alpha = ((dot - 0.12) / 0.88).clamp(0.0, 1.0); - let color = [1.0, 1.0, 1.0, alpha]; + let color = [ + text_color[0], + text_color[1], + text_color[2], + text_color[3] * alpha, + ]; let u = Vec3::from(FACE_U[fi]); let v = Vec3::from(FACE_V[fi]); let center = face_n; // unit normal = face surface centre (distance E) @@ -714,7 +720,12 @@ impl ViewCubeText { } else { 0.5 }; - let color = [1.0, 1.0, 1.0, alpha]; + let color = [ + text_color[0], + text_color[1], + text_color[2], + text_color[3] * alpha, + ]; let (u0, v0, u1, v1) = glyph_uv(gi, self.atlas_w, self.atlas_h); let corner = |lx: f32, ly: f32| center + Vec3::X * lx + Vec3::Y * ly; let tl = project_world(corner(-CARD_GW * 0.5, CARD_GH * 0.5)); @@ -1232,6 +1243,7 @@ impl ViewCubePipeline { vp_w: u32, vp_h: u32, hover: Option, + text_color: [f32; 4], ) { queue.write_buffer( &self.uniform_buffer, @@ -1245,7 +1257,15 @@ impl ViewCubePipeline { )), ); self.text - .update(queue, cam_rotation, compass_rotation, vp_w, vp_h, self.cube_px); + .update( + queue, + cam_rotation, + compass_rotation, + vp_w, + vp_h, + self.cube_px, + text_color, + ); } pub fn ensure_depth_texture(&mut self, device: &wgpu::Device, size: Size) { diff --git a/src/scene/view/render.rs b/src/scene/view/render.rs index f1266acae..af93772bf 100644 --- a/src/scene/view/render.rs +++ b/src/scene/view/render.rs @@ -152,6 +152,8 @@ pub struct Primitive { pub(in crate::scene) viewports: Vec, /// Background color used to clear each viewport's MSAA buffer. pub(in crate::scene) bg_color: [f32; 4], + /// Active Iced theme text colour for GPU-rendered ViewCube labels. + pub(in crate::scene) viewcube_text_color: [f32; 4], /// One input-to-render sample, carried only when PERF tracing is enabled. pub(in crate::scene) nav_perf: Option, } @@ -327,6 +329,7 @@ impl shader::Primitive for Primitive { (vp.screen_rect.width * bounds.width) as u32, (vp.screen_rect.height * bounds.height) as u32, vp.hover_region, + self.viewcube_text_color, ); } continue; @@ -874,6 +877,7 @@ impl shader::Primitive for Primitive { (vp.screen_rect.width * bounds.width) as u32, (vp.screen_rect.height * bounds.height) as u32, vp.hover_region, + self.viewcube_text_color, ); } } @@ -1689,6 +1693,7 @@ impl Scene { model_render_mode: acadrust::entities::ViewportRenderMode, _hover_region: Option, show_viewcube: bool, + viewcube_text_color: [f32; 4], ) -> Primitive { let nav_build_started = iced::time::Instant::now(); let perf_nav = self.take_nav_perf(); @@ -1720,6 +1725,7 @@ impl Scene { Primitive { viewports, bg_color, + viewcube_text_color, nav_perf: perf_nav, } } @@ -1736,6 +1742,7 @@ impl Scene { tile_idx: usize, model_render_mode: acadrust::entities::ViewportRenderMode, show_viewcube: bool, + viewcube_text_color: [f32; 4], ) -> Primitive { let hover_region = self.viewcube_hover.get(); let canvas = (bounds.width.max(1.0), bounds.height.max(1.0)); @@ -1745,6 +1752,7 @@ impl Scene { return Primitive { viewports: vec![], bg_color, + viewcube_text_color, nav_perf: None, }; }; @@ -1792,6 +1800,7 @@ impl Scene { Primitive { viewports, bg_color, + viewcube_text_color, nav_perf: perf_nav, } } diff --git a/src/scene/view/viewport_pane.rs b/src/scene/view/viewport_pane.rs index a3cf1a044..07d87b868 100644 --- a/src/scene/view/viewport_pane.rs +++ b/src/scene/view/viewport_pane.rs @@ -23,6 +23,9 @@ pub struct ViewportPane<'a> { /// per `pane_grid` pane, filling its own bounds). `None` → the unified /// full-canvas path (paper layout, or the whole-canvas Model fallback). pub pane: Option, + /// Active Iced theme text colour, forwarded to the GPU-rendered ViewCube + /// face and compass labels. + pub viewcube_text_color: [f32; 4], } impl<'a> ViewportPane<'a> { @@ -30,12 +33,14 @@ impl<'a> ViewportPane<'a> { scene: &'a Scene, show_viewcube: bool, render_mode: acadrust::entities::ViewportRenderMode, + viewcube_text_color: [f32; 4], ) -> Self { Self { scene, show_viewcube, render_mode, pane: None, + viewcube_text_color, } } @@ -46,12 +51,14 @@ impl<'a> ViewportPane<'a> { show_viewcube: bool, render_mode: acadrust::entities::ViewportRenderMode, tile_idx: usize, + viewcube_text_color: [f32; 4], ) -> Self { Self { scene, show_viewcube, render_mode, pane: Some(tile_idx), + viewcube_text_color, } } } @@ -74,12 +81,14 @@ impl<'a, Msg: std::fmt::Debug + Clone> shader::Program for ViewportPane<'a> idx, self.render_mode, self.show_viewcube, + self.viewcube_text_color, ), None => self.scene.build_viewports( bounds, self.render_mode, state.hover_region, self.show_viewcube, + self.viewcube_text_color, ), } } diff --git a/src/ui/color_select.rs b/src/ui/color_select.rs index 41c659f4a..bb2af84be 100644 --- a/src/ui/color_select.rs +++ b/src/ui/color_select.rs @@ -140,6 +140,21 @@ pub fn color_selector<'a>( }) } +fn list_row_style(theme: &Theme, status: button::Status) -> button::Style { + let palette = theme.extended_palette(); + let hovered = matches!(status, button::Status::Hovered); + let text_color = if hovered { + palette.background.strong.text + } else { + palette.background.base.text + }; + button::Style { + background: hovered.then_some(Background::Color(palette.background.strong.color)), + text_color, + ..Default::default() + } +} + /// The colour list shown inside a picker popup: named ACI colours (with /// swatches) plus a "More…" entry that opens the full palette window. Shared by /// `color_selector` and the ribbon's colour overlay. @@ -156,12 +171,7 @@ pub fn color_list<'a>( .align_y(iced::Center), ) .on_press(on_select(color)) - .style(|theme: &Theme, status| button::Style { - background: matches!(status, button::Status::Hovered).then_some( - Background::Color(theme.extended_palette().background.strong.color) - ), - ..Default::default() - }) + .style(list_row_style) .padding([2, 4]) .width(Length::Fill) .into() @@ -180,12 +190,7 @@ pub fn color_list<'a>( list = list.push( button(text("More…").size(11)) .on_press(on_more) - .style(|theme: &Theme, status| button::Style { - background: matches!(status, button::Status::Hovered).then_some( - Background::Color(theme.extended_palette().background.strong.color) - ), - ..Default::default() - }) + .style(list_row_style) .padding([2, 4]) .width(Length::Fill), ); @@ -236,6 +241,7 @@ pub fn color_grid_window(on_pick: impl Fn(AcadColor) -> Message) -> Element<'sta }, radius: 1.0.into(), }, + text_color: theme.extended_palette().background.base.text, ..Default::default() }) .padding(0), diff --git a/src/ui/properties.rs b/src/ui/properties.rs index d7708e5f8..dadfdfdaa 100644 --- a/src/ui/properties.rs +++ b/src/ui/properties.rs @@ -951,6 +951,7 @@ pub fn color_picker_dropdown<'a>( }, radius: 2.0.into(), }, + text_color: theme.extended_palette().background.base.text, ..Default::default() }) .padding(0), @@ -1033,6 +1034,7 @@ pub fn color_picker_dropdown<'a>( }, radius: 1.0.into(), }, + text_color: theme.extended_palette().background.base.text, ..Default::default() }) .padding(0), diff --git a/src/ui/window/layers.rs b/src/ui/window/layers.rs index 1aa5aabec..40583c844 100644 --- a/src/ui/window/layers.rs +++ b/src/ui/window/layers.rs @@ -475,6 +475,48 @@ impl LayerPanel { // ── Sorting helpers ───────────────────────────────────────────────────────── +fn layer_cell_button_style( + theme: &Theme, + status: button::Status, + is_selected: bool, + index: usize, +) -> button::Style { + let palette = theme.extended_palette(); + let highlighted = matches!(status, button::Status::Hovered); + let pair = if highlighted { + palette.background.strong + } else if is_selected { + palette.primary.weak + } else if index % 2 == 0 { + palette.background.base + } else { + palette.background.weak + }; + button::Style { + background: highlighted.then_some(Background::Color(pair.color)), + text_color: pair.text, + ..Default::default() + } +} + +fn layer_header_button_style(theme: &Theme, status: button::Status) -> button::Style { + let palette = theme.extended_palette(); + let highlighted = matches!( + status, + button::Status::Hovered | button::Status::Pressed + ); + let pair = if highlighted { + palette.background.strong + } else { + palette.background.weak + }; + button::Style { + background: highlighted.then_some(Background::Color(pair.color)), + text_color: pair.text, + ..Default::default() + } +} + /// Packed RGB key for ordering colours deterministically by hue-ish bytes. fn color_sort_key(c: Color) -> u32 { let q = |v: f32| (v.clamp(0.0, 1.0) * 255.0).round() as u32; @@ -502,16 +544,7 @@ fn sortable_header<'a>( } button(content) .on_press(Message::LayerSort(col)) - .style(|theme: &Theme, status| button::Style { - background: matches!( - status, - button::Status::Hovered | button::Status::Pressed - ) - .then_some(Background::Color( - theme.extended_palette().background.strong.color - )), - ..Default::default() - }) + .style(layer_header_button_style) .padding(Padding { top: 0.0, bottom: 0.0, @@ -655,11 +688,8 @@ fn layer_row<'a>( let svg_btn = |bytes: &'static [u8], on_press: Message| -> Element<'a, Message> { button(crate::ui::icons::semantic(bytes, ICON_SZ)) .on_press(on_press) - .style(|theme: &Theme, status| button::Style { - background: matches!(status, button::Status::Hovered).then_some( - Background::Color(theme.extended_palette().background.strong.color) - ), - ..Default::default() + .style(move |theme: &Theme, status| { + layer_cell_button_style(theme, status, is_selected, index) }) .padding(Padding { top: COMBO_PAD_V, @@ -704,11 +734,8 @@ fn layer_row<'a>( .size(FONT_SZ), ) .on_press(Message::LayerRenameStart(index)) - .style(|theme: &Theme, status| button::Style { - background: matches!(status, button::Status::Hovered).then_some( - Background::Color(theme.extended_palette().background.strong.color) - ), - ..Default::default() + .style(move |theme: &Theme, status| { + layer_cell_button_style(theme, status, is_selected, index) }) .padding(Padding { top: COMBO_PAD_V,