feat(selection): highlight opacity and extra width (#1823)

Options > Selection gains "Selection highlight opacity" and "Selection
highlight extra width". Below full opacity the object's own colour
shows through the highlight. The selection overlay draws with its own
copy of the frame uniforms: transparency on, and every line widened by
the extra width. Drawings opened at startup now get the selection
colour settings too; they used to keep the defaults until a setting
changed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-16 01:07:23 +03:00
1 parent 977135fbbd
commit 38d81bf838
38 files changed
+247 -89

No files matched your search

+2
View File
@@ -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 }
+2
View File
@@ -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 }
+2
View File
@@ -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 }
+2
View File
@@ -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 }
+2
View File
@@ -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 }
+2
View File
@@ -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
+2
View File
@@ -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 }
+2
View File
@@ -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 }
+2
View File
@@ -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 }
+2
View File
@@ -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 }
+2
View File
@@ -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 }
+2
View File
@@ -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 }
+2
View File
@@ -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 }
+2
View File
@@ -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 }
+2
View File
@@ -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 = ສີຈຸດກຣິບທີ່ບໍ່ໄດ້ເລືອກ
+2
View File
@@ -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 }
+2
View File
@@ -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 }
+2
View File
@@ -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 }
+2
View File
@@ -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 }
+2
View File
@@ -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 }
+2
View File
@@ -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 }
+2
View File
@@ -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 }
+14 -9
View File
@@ -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
}
}
+2
View File
@@ -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).
+9 -1
View File
@@ -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);
}
+14
View File
@@ -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);
+4
View File
@@ -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")),
+3
View File
@@ -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,
+73 -71
View File
@@ -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::<Uniforms>() 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(..));
+4 -2
View File
@@ -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],
+6
View File
@@ -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<NavPerfSample>,
}
@@ -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];
+7 -1
View File
@@ -8,7 +8,7 @@ struct Uniforms {
lineweight_scale: f32,
view_rot: mat4x4<f32>,
eye_high: vec3<f32>,
_pad_eh: f32,
highlight_widen: f32,
eye_low: vec3<f32>,
_pad_el: f32,
}
@@ -63,7 +63,13 @@ struct VertexOut {
@location(9) @interpolate(flat) cap_ends: vec3<f32>,
}
// 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);
}
+7 -1
View File
@@ -27,7 +27,7 @@ struct Uniforms {
lineweight_scale: f32,
view_rot: mat4x4<f32>,
eye_high: vec3<f32>,
_pad_eh: f32,
highlight_widen: f32,
eye_low: vec3<f32>,
_pad_el: f32,
}
@@ -87,7 +87,13 @@ struct VertexOut {
@location(9) @interpolate(flat) cap_ends: vec3<f32>,
}
// 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);
}
+7 -1
View File
@@ -20,7 +20,7 @@ struct Uniforms {
// Relative-to-eye (double-single)
view_rot: mat4x4<f32>,
eye_high: vec3<f32>,
_pad_eh: f32,
highlight_widen: f32,
eye_low: vec3<f32>,
_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;
+7 -1
View File
@@ -15,7 +15,7 @@ struct Uniforms {
// Relative-to-eye (double-single)
view_rot: mat4x4<f32>,
eye_high: vec3<f32>,
_pad_eh: f32,
highlight_widen: f32,
eye_low: vec3<f32>,
_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;
+7 -1
View File
@@ -37,7 +37,7 @@ struct Uniforms {
// jitter on pan / zoom / rotate.
view_rot: mat4x4<f32>,
eye_high: vec3<f32>,
_pad_eh: f32,
highlight_widen: f32,
eye_low: vec3<f32>,
_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); }
+7 -1
View File
@@ -15,7 +15,7 @@ struct Uniforms {
lineweight_scale: f32,
view_rot: mat4x4<f32>,
eye_high: vec3<f32>,
_pad_eh: f32,
highlight_widen: f32,
eye_low: vec3<f32>,
_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);
}
+32
View File
@@ -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),