diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index 54486f961..e135b89d3 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -1625,6 +1625,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 80e2c2fb4..ce46a93f6 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -1612,6 +1612,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index f33b4c5b7..85a433500 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -1612,6 +1612,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 49488dc70..5bf936ee4 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -1613,6 +1613,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 2271ec48d..acf6e2b58 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -1614,6 +1614,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index fc6f231cd..236b34ea8 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -1613,6 +1613,8 @@ common = .window-selection-color = Window selection color .crossing-selection-color = Crossing selection color .selection-highlight-color = Selection highlight color + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = Grip Settings .grip-size = Grip size .unselected-grip-color = Unselected grip color diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 70d2c59a0..842c132a1 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -1613,6 +1613,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index e4a72c32f..419dfaa62 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -1612,6 +1612,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 02d85d828..738a361e4 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -1613,6 +1613,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 58492873c..ad0634ff0 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -1601,6 +1601,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 0a3492b63..2d788cda8 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -1618,6 +1618,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 228cd874d..73f7fc2a1 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -1612,6 +1612,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index be823af57..8c496cfa7 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -1603,6 +1603,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index ec8f98f83..5828cb7cd 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -1612,6 +1612,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/lo-LA/opencadstudio.ftl b/locales/lo-LA/opencadstudio.ftl index 786bbc3e1..9cbd0f602 100644 --- a/locales/lo-LA/opencadstudio.ftl +++ b/locales/lo-LA/opencadstudio.ftl @@ -1618,6 +1618,8 @@ common = .window-selection-color = ສີການເລືອກແບບ Window .crossing-selection-color = ສີການເລືອກແບບ Crossing .selection-highlight-color = ສີການເນັ້ນການເລືອກ + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = ການຕັ້ງຄ່າຈຸດກຣິບ .grip-size = ຂະໜາດຈຸດກຣິບ .unselected-grip-color = ສີຈຸດກຣິບທີ່ບໍ່ໄດ້ເລືອກ diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 308cba371..0e833a32c 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -1611,6 +1611,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index d0bc3dc97..a8c680f87 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -1612,6 +1612,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 6ca88dd6a..ee9061395 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -1613,6 +1613,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index 10a5f642d..f432c23e7 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -1616,6 +1616,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 189759229..d9fd70437 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -1599,6 +1599,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 9751345d2..5c90421f8 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -1600,6 +1600,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 8febd85e8..2e7b7164b 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -1612,6 +1612,8 @@ common = .window-selection-color = { common.selection }: { common.window }: { common.color } .crossing-selection-color = { common.selection }: { common.crossing }: { common.color } .selection-highlight-color = { common.selection }: { common.highlight }: { common.color } + .selection-highlight-opacity = Selection highlight opacity + .selection-highlight-extra-width = Selection highlight extra width .grip-settings = { common.grips }: { properties.settings } .grip-size = { common.grips }: { common.size } .unselected-grip-color = { common.grips }: { common.unselected }: { common.color } diff --git a/src/app/config.rs b/src/app/config.rs index 408fa6679..3f8b8f534 100644 --- a/src/app/config.rs +++ b/src/app/config.rs @@ -357,6 +357,11 @@ pub struct ModelSpaceThemeConfig { pub selection_crossing_color: u8, /// SELECTIONEFFECTCOLOR: ACI index for selected entities highlight (0 = Theme Primary, 1..=255 = ACI). pub selection_highlight_color: u8, + /// Selection highlight opacity in percent (10–100); below 100 the + /// object's own colour shows through. + pub selection_highlight_opacity: u8, + /// Extra screen pixels the selection highlight is drawn wider (0–6). + pub selection_highlight_widen: u8, /// SELECTIONEFFECT: whether selected objects glow with solid highlight (true) or dash (false). pub selection_effect: bool, /// SELECTIONPREVIEW bitmask: 1 = idle, 2 = during a command, 3 = both. @@ -386,6 +391,8 @@ impl Default for ModelSpaceThemeConfig { selection_window_color: 0, selection_crossing_color: 0, selection_highlight_color: 0, + selection_highlight_opacity: 100, + selection_highlight_widen: 0, selection_effect: true, selection_preview: 3, grip_size: 5, @@ -437,15 +444,13 @@ impl ModelSpaceThemeConfig { /// Resolve active selection highlight overlay color as [f32; 4]. pub fn resolve_selection_color(&self) -> [f32; 4] { - if self.selection_highlight_color == 0 { - crate::scene::model::wire_model::WireModel::SELECTED - } else if let Some((r, g, b)) = - codec::types::aci_table::aci_to_rgb(self.selection_highlight_color) - { - [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0] - } else { - crate::scene::model::wire_model::WireModel::SELECTED - } + let mut color = codec::types::aci_table::aci_to_rgb(self.selection_highlight_color) + .filter(|_| self.selection_highlight_color != 0) + .map_or(crate::scene::model::wire_model::WireModel::SELECTED, |(r, g, b)| { + [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0] + }); + color[3] = f32::from(self.selection_highlight_opacity.clamp(10, 100)) / 100.0; + color } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 163dabb90..f81649499 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2674,6 +2674,8 @@ pub enum Message { SelectionAreaToggled(bool), /// Change Selection Area opacity percentage (0–100, SELECTIONAREAOPACITY). SelectionOpacityChanged(u8), + SelectionHighlightOpacityChanged(u8), + SelectionHighlightWidenChanged(u8), /// Change Window selection color ACI index (0 = Theme Primary, 1..=255 = ACI). SelectionWindowColorChanged(u8), /// Change Crossing selection color ACI index (0 = Theme Success, 1..=255 = ACI). diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 5a8ba9737..fecfbb10f 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -1278,6 +1278,9 @@ impl OpenCADStudio { return; } tab.scene.model_lineweight_scale = self.lineweight_display_scale as f32 / 100.0; + tab.scene.selection_color = self.model_space.resolve_selection_color(); + tab.scene.selection_effect = self.model_space.selection_effect; + tab.scene.selection_widen = f32::from(self.model_space.selection_highlight_widen.min(6)); if bg.is_none() && paper_bg.is_none() { return; } @@ -1551,6 +1554,7 @@ impl OpenCADStudio { let paper_bg = self.model_space.resolve_paper_bg(); let sel_color = self.model_space.resolve_selection_color(); let sel_effect = self.model_space.selection_effect; + let sel_widen = f32::from(self.model_space.selection_highlight_widen.min(6)); self.default_bg_color = Some(model_bg); self.default_paper_bg_color = Some(paper_bg); @@ -1563,10 +1567,14 @@ impl OpenCADStudio { tab.scene.paper_bg_color = paper_bg; tab.scene.selection_color = sel_color; tab.scene.selection_effect = sel_effect; + let old_sel_widen = std::mem::replace(&mut tab.scene.selection_widen, sel_widen); tab.bg_color = Some(model_bg); tab.paper_bg_color = Some(paper_bg); - if old_sel_color != sel_color || old_sel_effect != sel_effect { + if old_sel_color != sel_color + || old_sel_effect != sel_effect + || old_sel_widen != sel_widen + { tab.scene.selection_generation = tab.scene.selection_generation.wrapping_add(1); } diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index ed713554c..b0e36bbe5 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -9175,6 +9175,20 @@ impl OpenCADStudio { Task::none() } + Message::SelectionHighlightOpacityChanged(opacity) => { + self.model_space.selection_highlight_opacity = opacity.clamp(10, 100); + self.sync_model_space_theme(false); + self.persist_settings_if_changed(); + Task::none() + } + + Message::SelectionHighlightWidenChanged(widen) => { + self.model_space.selection_highlight_widen = widen.min(6); + self.sync_model_space_theme(false); + self.persist_settings_if_changed(); + Task::none() + } + Message::GripSizeChanged(size) => { self.model_space.grip_size = size.clamp(1, 25); self.sync_model_space_theme(false); diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index e367c7b5c..33b411619 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -5183,6 +5183,10 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Window selection color" => Some(("common", "window-selection-color")), "Crossing selection color" => Some(("common", "crossing-selection-color")), "Selection highlight color" => Some(("common", "selection-highlight-color")), + "Selection highlight opacity" => Some(("common", "selection-highlight-opacity")), + "Selection highlight extra width" => { + Some(("common", "selection-highlight-extra-width")) + } "Grip Settings" => Some(("common", "grip-settings")), "Grip size" => Some(("common", "grip-size")), "Unselected grip color" => Some(("common", "unselected-grip-color")), diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 5a070a2d8..d90117db5 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -2204,6 +2204,8 @@ pub struct Scene { pub selection_color: [f32; 4], /// Whether selection visual effect (glow/highlight) is enabled (SELECTIONEFFECT). pub selection_effect: bool, + /// Extra screen pixels the selection highlight is drawn wider. + pub selection_widen: f32, /// Whether entity transparency is honoured on screen. When false the /// wire shader forces every line opaque (a uniform toggle, no retessellate). pub transparency_display: bool, @@ -2827,6 +2829,7 @@ impl Scene { constraint_hover_refs: Vec::new(), constraint_hover_wires: Vec::new(), selection_color: crate::scene::model::wire_model::WireModel::SELECTED, + selection_widen: 0.0, selection_effect: true, transparency_display: true, model_lineweight_scale: 1.0, diff --git a/src/scene/pipeline/mod.rs b/src/scene/pipeline/mod.rs index f669d3267..466877e15 100644 --- a/src/scene/pipeline/mod.rs +++ b/src/scene/pipeline/mod.rs @@ -182,6 +182,10 @@ pub struct Pipeline { uniform_buffer: wgpu::Buffer, frame_bgl: wgpu::BindGroupLayout, uniform_bind_group: wgpu::BindGroup, + /// The selection overlay's copy of the frame uniforms: transparency on, + /// so a translucent highlight colour shows, and lines widened. + selection_uniform_buffer: wgpu::Buffer, + selection_uniform_bind_group: wgpu::BindGroup, shadow_uniform_bind_group: wgpu::BindGroup, background_texture: wgpu::Texture, environment_texture: wgpu::Texture, @@ -414,6 +418,37 @@ pub struct Pipeline { pub slot_id: u64, } +/// The views and samplers the frame bind group carries beside its uniforms. +struct FrameViews<'a> { + background: &'a wgpu::TextureView, + environment: &'a wgpu::TextureView, + background_sampler: &'a wgpu::Sampler, + shadow: &'a wgpu::TextureView, + shadow_sampler: &'a wgpu::Sampler, +} + +fn frame_bind_group( + device: &wgpu::Device, + layout: &wgpu::BindGroupLayout, + uniforms: &wgpu::Buffer, + views: &FrameViews<'_>, +) -> wgpu::BindGroup { + let entry = |binding, resource| wgpu::BindGroupEntry { binding, resource }; + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("viewer.bind_group"), + layout, + entries: &[ + entry(0, uniforms.as_entire_binding()), + entry(1, wgpu::BindingResource::TextureView(views.background)), + entry(2, wgpu::BindingResource::Sampler(views.background_sampler)), + entry(3, wgpu::BindingResource::TextureView(views.environment)), + entry(4, wgpu::BindingResource::Sampler(views.background_sampler)), + entry(5, wgpu::BindingResource::TextureView(views.shadow)), + entry(6, wgpu::BindingResource::Sampler(views.shadow_sampler)), + ], + }) +} + impl Pipeline { pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, format: wgpu::TextureFormat) -> Self { // ── Shared frame uniform buffer (view_proj etc.) ─────────────────── @@ -423,6 +458,12 @@ impl Pipeline { usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); + let selection_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("viewer.selection_uniform_buffer"), + size: std::mem::size_of::() as u64, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); // Bind group layout 0 — shared by wire and hatch pipelines. let frame_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { @@ -563,40 +604,17 @@ impl Pipeline { background_texture.create_view(&wgpu::TextureViewDescriptor::default()); let environment_view = environment_texture.create_view(&wgpu::TextureViewDescriptor::default()); - let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("viewer.bind_group"), - layout: &frame_bgl, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: uniform_buffer.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::TextureView(&background_view), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: wgpu::BindingResource::Sampler(&background_sampler), - }, - wgpu::BindGroupEntry { - binding: 3, - resource: wgpu::BindingResource::TextureView(&environment_view), - }, - wgpu::BindGroupEntry { - binding: 4, - resource: wgpu::BindingResource::Sampler(&background_sampler), - }, - wgpu::BindGroupEntry { - binding: 5, - resource: wgpu::BindingResource::TextureView(&shadow_fallback_view), - }, - wgpu::BindGroupEntry { - binding: 6, - resource: wgpu::BindingResource::Sampler(&shadow_sampler), - }, - ], - }); + let frame_views = FrameViews { + background: &background_view, + environment: &environment_view, + background_sampler: &background_sampler, + shadow: &shadow_fallback_view, + shadow_sampler: &shadow_sampler, + }; + let uniform_bind_group = + frame_bind_group(device, &frame_bgl, &uniform_buffer, &frame_views); + let selection_uniform_bind_group = + frame_bind_group(device, &frame_bgl, &selection_uniform_buffer, &frame_views); let shadow_uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("shadow.frame_bind_group"), layout: &shadow_frame_bgl, @@ -2390,6 +2408,8 @@ impl Pipeline { uniform_buffer, frame_bgl, uniform_bind_group, + selection_uniform_buffer, + selection_uniform_bind_group, shadow_uniform_bind_group, background_texture, environment_texture, @@ -3926,40 +3946,18 @@ analytic={:.1} regular={:.1} blocks={:.1}", let environment_view = self .environment_texture .create_view(&wgpu::TextureViewDescriptor::default()); - self.uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("viewer.bind_group"), - layout: &self.frame_bgl, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: self.uniform_buffer.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::TextureView(&background_view), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: wgpu::BindingResource::Sampler(&self.background_sampler), - }, - wgpu::BindGroupEntry { - binding: 3, - resource: wgpu::BindingResource::TextureView(&environment_view), - }, - wgpu::BindGroupEntry { - binding: 4, - resource: wgpu::BindingResource::Sampler(&self.background_sampler), - }, - wgpu::BindGroupEntry { - binding: 5, - resource: wgpu::BindingResource::TextureView(self.shadow_view()), - }, - wgpu::BindGroupEntry { - binding: 6, - resource: wgpu::BindingResource::Sampler(&self.shadow_sampler), - }, - ], - }); + let views = FrameViews { + background: &background_view, + environment: &environment_view, + background_sampler: &self.background_sampler, + shadow: self.shadow_view(), + shadow_sampler: &self.shadow_sampler, + }; + let frame = frame_bind_group(device, &self.frame_bgl, &self.uniform_buffer, &views); + let selection = + frame_bind_group(device, &self.frame_bgl, &self.selection_uniform_buffer, &views); + self.uniform_bind_group = frame; + self.selection_uniform_bind_group = selection; } /// Allocate or drop the shadow target to match `shadow_enabled`. @@ -3995,7 +3993,11 @@ analytic={:.1} regular={:.1} blocks={:.1}", if self.shadow_full.is_none() { uniforms.shadow_params[0] = 0.0; } + let mut selection = uniforms; + selection.transparency_enable = 1.0; + uniforms.highlight_widen = 0.0; queue.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniforms)); + queue.write_buffer(&self.selection_uniform_buffer, 0, bytemuck::bytes_of(&selection)); } pub fn upload_background_images( @@ -5120,7 +5122,7 @@ analytic={:.1} regular={:.1} blocks={:.1}", multiview_mask: None, }); pass.set_viewport(raster.x, raster.y, raster.width, raster.height, 0.0, 1.0); - pass.set_bind_group(0, &self.uniform_bind_group, &[]); + pass.set_bind_group(0, &self.selection_uniform_bind_group, &[]); pass.set_stencil_reference(stencil_ref); if !self.gpu_selected_wires.is_empty() { pass.set_pipeline(if occluded { @@ -5160,7 +5162,7 @@ analytic={:.1} regular={:.1} blocks={:.1}", } else { &self.circle_xray_pipeline }); - pass.set_bind_group(0, &self.uniform_bind_group, &[]); + pass.set_bind_group(0, &self.selection_uniform_bind_group, &[]); for cg in &self.gpu_selected_circles { if cg.instance_count > 0 { pass.set_vertex_buffer(0, cg.instance_buffer.slice(..)); @@ -5174,7 +5176,7 @@ analytic={:.1} regular={:.1} blocks={:.1}", } else { &self.ellipse_xray_pipeline }); - pass.set_bind_group(0, &self.uniform_bind_group, &[]); + pass.set_bind_group(0, &self.selection_uniform_bind_group, &[]); for eg in &self.gpu_selected_ellipses { if eg.instance_count > 0 { pass.set_vertex_buffer(0, eg.instance_buffer.slice(..)); diff --git a/src/scene/pipeline/uniforms.rs b/src/scene/pipeline/uniforms.rs index 06cb0cc1e..fe84756e9 100644 --- a/src/scene/pipeline/uniforms.rs +++ b/src/scene/pipeline/uniforms.rs @@ -39,7 +39,9 @@ pub struct Uniforms { // jitter. pub view_rot: glam::Mat4, pub eye_high: [f32; 3], - pub _pad_eh: f32, + /// Extra screen pixels added to every line width. Only the selection + /// overlay pass sees a non-zero value (`Pipeline::upload_uniforms`). + pub highlight_widen: f32, pub eye_low: [f32; 3], pub _pad_el: f32, @@ -121,7 +123,7 @@ impl Uniforms { lineweight_scale: 1.0, view_rot: camera.view_proj_rte(bounds), eye_high, - _pad_eh: 0.0, + highlight_widen: 0.0, eye_low, _pad_el: 0.0, light_position_type: [[0.0; 4]; 4], diff --git a/src/scene/view/render.rs b/src/scene/view/render.rs index c42d93184..bf035050f 100644 --- a/src/scene/view/render.rs +++ b/src/scene/view/render.rs @@ -270,6 +270,8 @@ pub struct Primitive { pub(in crate::scene) selection_color: [f32; 4], /// Whether selection visual effect (glow/highlight) is enabled. pub(in crate::scene) selection_effect: bool, + /// Extra screen pixels the selection highlight is drawn wider. + pub(in crate::scene) selection_widen: f32, /// One input-to-render sample, carried only when PERF tracing is enabled. pub(in crate::scene) nav_perf: Option, } @@ -4298,6 +4300,7 @@ impl Scene { viewcube_text_color, selection_color: self.selection_color, selection_effect: self.selection_effect, + selection_widen: self.selection_widen, nav_perf: perf_nav, } } @@ -4330,6 +4333,7 @@ impl Scene { viewcube_text_color, selection_color: self.selection_color, selection_effect: self.selection_effect, + selection_widen: self.selection_widen, nav_perf: None, }; }; @@ -4392,6 +4396,7 @@ impl Scene { viewcube_text_color, selection_color: self.selection_color, selection_effect: self.selection_effect, + selection_widen: self.selection_widen, nav_perf: perf_nav, } } @@ -4688,6 +4693,7 @@ impl Scene { uniforms.viewport_size = [visible_w, visible_h]; uniforms.flat_shade = if flags.flat_shade { 1.0 } else { 0.0 }; uniforms.transparency_enable = if self.transparency_display { 1.0 } else { 0.0 }; + uniforms.highlight_widen = self.selection_widen; uniforms.viewport_background = display.background.base; uniforms.view_tone = [display.brightness, display.contrast, 0.0, 0.0]; uniforms.background_top = display.background.colors[0]; diff --git a/src/shaders/block_wire.wgsl b/src/shaders/block_wire.wgsl index 8399a2c2f..cca43637f 100644 --- a/src/shaders/block_wire.wgsl +++ b/src/shaders/block_wire.wgsl @@ -8,7 +8,7 @@ struct Uniforms { lineweight_scale: f32, view_rot: mat4x4, eye_high: vec3, - _pad_eh: f32, + highlight_widen: f32, eye_low: vec3, _pad_el: f32, } @@ -63,7 +63,13 @@ struct VertexOut { @location(9) @interpolate(flat) cap_ends: vec3, } +// The selection pass widens highlighted lines by `highlight_widen` pixels; +// every other pass uploads zero. fn resolve_hw(taper_ratio: f32, world_hw: f32, px_hw: f32) -> f32 { + return resolve_hw_base(taper_ratio, world_hw, px_hw) + u.highlight_widen * 0.5; +} + +fn resolve_hw_base(taper_ratio: f32, world_hw: f32, px_hw: f32) -> f32 { if taper_ratio > 0.0 { return max((taper_ratio * world_hw) / u.world_per_pixel, 0.5); } diff --git a/src/shaders/block_wire_storage.wgsl b/src/shaders/block_wire_storage.wgsl index ebb5a7ea6..e79268b23 100644 --- a/src/shaders/block_wire_storage.wgsl +++ b/src/shaders/block_wire_storage.wgsl @@ -27,7 +27,7 @@ struct Uniforms { lineweight_scale: f32, view_rot: mat4x4, eye_high: vec3, - _pad_eh: f32, + highlight_widen: f32, eye_low: vec3, _pad_el: f32, } @@ -87,7 +87,13 @@ struct VertexOut { @location(9) @interpolate(flat) cap_ends: vec3, } +// The selection pass widens highlighted lines by `highlight_widen` pixels; +// every other pass uploads zero. fn resolve_hw(taper_ratio: f32, world_hw: f32, px_hw: f32) -> f32 { + return resolve_hw_base(taper_ratio, world_hw, px_hw) + u.highlight_widen * 0.5; +} + +fn resolve_hw_base(taper_ratio: f32, world_hw: f32, px_hw: f32) -> f32 { if taper_ratio > 0.0 { return max((taper_ratio * world_hw) / u.world_per_pixel, 0.5); } diff --git a/src/shaders/circle.wgsl b/src/shaders/circle.wgsl index ef6bbf99d..c923c112f 100644 --- a/src/shaders/circle.wgsl +++ b/src/shaders/circle.wgsl @@ -20,7 +20,7 @@ struct Uniforms { // Relative-to-eye (double-single) view_rot: mat4x4, eye_high: vec3, - _pad_eh: f32, + highlight_widen: f32, eye_low: vec3, _pad_el: f32, } @@ -56,7 +56,13 @@ struct VertexOut { @location(10) @interpolate(flat) is_world: f32, } +// The selection pass widens highlighted lines by `highlight_widen` pixels; +// every other pass uploads zero. fn resolve_px_hw(px_hw: f32) -> f32 { + return resolve_px_hw_base(px_hw) + u.highlight_widen * 0.5; +} + +fn resolve_px_hw_base(px_hw: f32) -> f32 { var display_hw = max(px_hw * u.lineweight_scale, 0.5); if u.lineweight_scale < 0.0 { let scale = -u.lineweight_scale; diff --git a/src/shaders/ellipse.wgsl b/src/shaders/ellipse.wgsl index 2fbe20ab5..ee6bc13e4 100644 --- a/src/shaders/ellipse.wgsl +++ b/src/shaders/ellipse.wgsl @@ -15,7 +15,7 @@ struct Uniforms { // Relative-to-eye (double-single) view_rot: mat4x4, eye_high: vec3, - _pad_eh: f32, + highlight_widen: f32, eye_low: vec3, _pad_el: f32, } @@ -50,7 +50,13 @@ struct VertexOut { @location(9) @interpolate(flat) end_param: f32, } +// The selection pass widens highlighted lines by `highlight_widen` pixels; +// every other pass uploads zero. fn resolve_hw(px_hw: f32) -> f32 { + return resolve_hw_base(px_hw) + u.highlight_widen * 0.5; +} + +fn resolve_hw_base(px_hw: f32) -> f32 { var display_hw = max(px_hw * u.lineweight_scale, 0.5); if u.lineweight_scale < 0.0 { let scale = -u.lineweight_scale; diff --git a/src/shaders/wire.wgsl b/src/shaders/wire.wgsl index 4d312896f..40c0d5730 100644 --- a/src/shaders/wire.wgsl +++ b/src/shaders/wire.wgsl @@ -37,7 +37,7 @@ struct Uniforms { // jitter on pan / zoom / rotate. view_rot: mat4x4, eye_high: vec3, - _pad_eh: f32, + highlight_widen: f32, eye_low: vec3, _pad_el: f32, } @@ -98,7 +98,13 @@ struct VertexOut { // Half-width of one segment end: a tapered band's own end width wins, then a // constant world-unit band, then the screen-pixel lineweight (LWDISPLAY off // collapses to a hairline). +// The selection pass widens highlighted lines by `highlight_widen` pixels; +// every other pass uploads zero. fn resolve_hw(taper: f32, world_hw: f32, px_hw: f32) -> f32 { + return resolve_hw_base(taper, world_hw, px_hw) + u.highlight_widen * 0.5; +} + +fn resolve_hw_base(taper: f32, world_hw: f32, px_hw: f32) -> f32 { if taper >= 0.0 { return max(taper / u.world_per_pixel, 0.5); } if world_hw > 0.0 { return max(world_hw / u.world_per_pixel, 0.5); } if world_hw < 0.0 { return max(-world_hw, 0.5); } diff --git a/src/shaders/wire_indexed.wgsl b/src/shaders/wire_indexed.wgsl index 162fdbcd2..e7b7722fe 100644 --- a/src/shaders/wire_indexed.wgsl +++ b/src/shaders/wire_indexed.wgsl @@ -15,7 +15,7 @@ struct Uniforms { lineweight_scale: f32, view_rot: mat4x4, eye_high: vec3, - _pad_eh: f32, + highlight_widen: f32, eye_low: vec3, _pad_el: f32, } @@ -79,7 +79,13 @@ struct VertexOut { // Half-width of one segment end: a tapered band's own end width wins, then a // constant world-unit band, then the screen-pixel lineweight (LWDISPLAY off // collapses to a hairline). +// The selection pass widens highlighted lines by `highlight_widen` pixels; +// every other pass uploads zero. fn resolve_hw(taper_ratio: f32, world_hw: f32, px_hw: f32) -> f32 { + return resolve_hw_base(taper_ratio, world_hw, px_hw) + u.highlight_widen * 0.5; +} + +fn resolve_hw_base(taper_ratio: f32, world_hw: f32, px_hw: f32) -> f32 { if taper_ratio > 0.0 { return max((taper_ratio * world_hw) / u.world_per_pixel, 0.5); } diff --git a/src/ui/window/options.rs b/src/ui/window/options.rs index 87318baba..a28174dea 100644 --- a/src/ui/window/options.rs +++ b/src/ui/window/options.rs @@ -1120,6 +1120,38 @@ pub fn view_window<'a>( ] .spacing(10) .align_y(iced::Center), + Space::new().height(10), + row![ + text(crate::t!("Selection highlight opacity")).size(12).width(140), + slider( + 10..=100, + model_space.selection_highlight_opacity.clamp(10, 100), + Message::SelectionHighlightOpacityChanged, + ) + .step(5) + .width(Fill), + text(format!("{}%", model_space.selection_highlight_opacity.clamp(10, 100))) + .size(11) + .width(44), + ] + .spacing(10) + .align_y(iced::Center), + Space::new().height(10), + row![ + text(crate::t!("Selection highlight extra width")).size(12).width(140), + slider( + 0..=6, + model_space.selection_highlight_widen.min(6), + Message::SelectionHighlightWidenChanged, + ) + .step(1) + .width(Fill), + text(format!("{} px", model_space.selection_highlight_widen.min(6))) + .size(11) + .width(44), + ] + .spacing(10) + .align_y(iced::Center), Space::new().height(24), text(crate::t!("Preview")).size(15), Space::new().height(10),