forked from 1b-agent/opencadstudio-upstream
feat(ui): show color swatch and layer in selection cycling popup
Improve the selection-cycling list box so overlapping objects can be identified at a glance: - Add a color swatch per row showing the object's effective drawn color - Show the layer name right-aligned next to the element type - Size the popup to fit its content with consistent padding Widen render_style_for_viewport visibility from scene-internal to crate-wide so the view layer resolves the same color the renderer uses.
This commit is contained in:
1 parent
452749f58c
commit
6bf84d8f5e
3 files changed
+93
-16
No files matched your search
+15
-5
@@ -1435,13 +1435,23 @@ impl OpenCADStudio {
|
|||||||
// Selection-cycling list box: pick among overlapping objects.
|
// Selection-cycling list box: pick among overlapping objects.
|
||||||
if let Some((pt, cands)) = &self.cycle_candidates {
|
if let Some((pt, cands)) = &self.cycle_candidates {
|
||||||
if !tab.is_start {
|
if !tab.is_start {
|
||||||
let items: Vec<(acadrust::Handle, String)> = cands
|
let items: Vec<crate::ui::popup::cycle_popup::CycleCandidate> = cands
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|&h| {
|
.filter_map(|&h| {
|
||||||
tab.scene
|
tab.scene.document.get_entity(h).map(|e| {
|
||||||
.document
|
let style = crate::scene::view::render::render_style_for_viewport(
|
||||||
.get_entity(h)
|
&tab.scene.document,
|
||||||
.map(|e| (h, crate::entities::traits::entity_type_name(e).to_string()))
|
e,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
crate::ui::popup::cycle_popup::CycleCandidate {
|
||||||
|
handle: h,
|
||||||
|
type_name: crate::entities::traits::entity_type_name(e)
|
||||||
|
.to_string(),
|
||||||
|
layer: e.common().layer.clone(),
|
||||||
|
color: style.0,
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
if !items.is_empty() {
|
if !items.is_empty() {
|
||||||
|
|||||||
@@ -2802,7 +2802,7 @@ fn viewport_override(
|
|||||||
.find_map(|(handle, value)| (handle == viewport).then_some(value))
|
.find_map(|(handle, value)| (handle == viewport).then_some(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(in crate::scene) fn render_style_for_viewport(
|
pub(crate) fn render_style_for_viewport(
|
||||||
document: &CadDocument,
|
document: &CadDocument,
|
||||||
e: &EntityType,
|
e: &EntityType,
|
||||||
viewport: Option<Handle>,
|
viewport: Option<Handle>,
|
||||||
|
|||||||
+77
-10
@@ -1,26 +1,43 @@
|
|||||||
//! Selection-cycling list box — shown at the cursor when a click lands on
|
//! Selection-cycling list box — shown at the cursor when a click lands on
|
||||||
//! two or more overlapping objects. Each row names a candidate; clicking it
|
//! two or more overlapping objects. Each row shows the object's drawn color
|
||||||
//! adds that object to the current selection. Clicking outside dismisses it.
|
//! swatch, its type, and (right-aligned) its layer; clicking a row adds that
|
||||||
|
//! object to the current selection. Clicking outside dismisses it.
|
||||||
|
|
||||||
use iced::widget::{button, column, container, mouse_area, opaque, text};
|
use iced::alignment::Horizontal;
|
||||||
use iced::{Element, Fill, Length};
|
use iced::widget::{button, column, container, mouse_area, opaque, row, rule, space, text};
|
||||||
|
use iced::{border::Radius, color, Element, Fill, Length, Theme};
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
|
|
||||||
|
/// Row content for one overlapping candidate.
|
||||||
|
pub struct CycleCandidate {
|
||||||
|
pub handle: acadrust::Handle,
|
||||||
|
pub type_name: String,
|
||||||
|
pub layer: String,
|
||||||
|
/// The color the object is drawn with (layer-inherited when ByLayer).
|
||||||
|
pub color: [f32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
/// Full-canvas overlay: the list box anchored at `anchor` (canvas
|
/// Full-canvas overlay: the list box anchored at `anchor` (canvas
|
||||||
/// coordinates) plus a transparent click-catcher that cancels.
|
/// coordinates) plus a transparent click-catcher that cancels.
|
||||||
pub fn cycle_popup_overlay(
|
pub fn cycle_popup_overlay(
|
||||||
anchor: iced::Point,
|
anchor: iced::Point,
|
||||||
items: Vec<(acadrust::Handle, String)>,
|
items: Vec<CycleCandidate>,
|
||||||
) -> Element<'static, Message> {
|
) -> Element<'static, Message> {
|
||||||
|
// The type column and the layer column get one shared width each (the
|
||||||
|
// widest entry of their kind, clamped) so the separators line up.
|
||||||
|
let type_w = clamp_col_width(items.iter().map(|c| c.type_name.chars().count()), 11.0, 40.0, 96.0);
|
||||||
|
let layer_w =
|
||||||
|
clamp_col_width(items.iter().map(|c| c.layer.chars().count()), 10.0, 28.0, 84.0);
|
||||||
|
|
||||||
let rows: Vec<Element<'static, Message>> = items
|
let rows: Vec<Element<'static, Message>> = items
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(handle, label)| item_row(handle, label))
|
.map(|c| item_row(c.handle, c.type_name, c.layer, c.color, type_w, layer_w))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let panel = container(column(rows))
|
let panel = container(column(rows))
|
||||||
.style(container::bordered_box)
|
.style(container::bordered_box)
|
||||||
.width(Length::Fixed(150.0));
|
.width(Length::Fixed(type_w + layer_w + 42.0));
|
||||||
|
|
||||||
let positioned = iced::widget::pin(opaque(panel))
|
let positioned = iced::widget::pin(opaque(panel))
|
||||||
.position(iced::Point::new(anchor.x.max(0.0), anchor.y.max(0.0)));
|
.position(iced::Point::new(anchor.x.max(0.0), anchor.y.max(0.0)));
|
||||||
@@ -28,13 +45,63 @@ pub fn cycle_popup_overlay(
|
|||||||
mouse_area(positioned).on_press(Message::CycleCancel).into()
|
mouse_area(positioned).on_press(Message::CycleCancel).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn item_row(handle: acadrust::Handle, label: String) -> Element<'static, Message> {
|
/// Column width for the widest label, using a per-glyph estimate with a
|
||||||
let content = text(label).size(11).align_y(iced::Center);
|
/// minimum so short labels don't collapse and a maximum so long ones clip.
|
||||||
|
fn clamp_col_width(labels: impl Iterator<Item = usize>, glyph_w: f32, min: f32, max: f32) -> f32 {
|
||||||
|
let widest = labels.max().unwrap_or(0) as f32 * glyph_w;
|
||||||
|
widest.clamp(min, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn item_row(
|
||||||
|
handle: acadrust::Handle,
|
||||||
|
type_name: String,
|
||||||
|
layer: String,
|
||||||
|
color: [f32; 4],
|
||||||
|
type_w: f32,
|
||||||
|
layer_w: f32,
|
||||||
|
) -> Element<'static, Message> {
|
||||||
|
let swatch = container(
|
||||||
|
space::Space::new()
|
||||||
|
.width(Length::Fixed(9.0))
|
||||||
|
.height(Length::Fixed(9.0)),
|
||||||
|
)
|
||||||
|
.style(move |_| container::Style {
|
||||||
|
background: Some(iced::Background::Color(iced::Color::from_rgba(
|
||||||
|
color[0], color[1], color[2], color[3],
|
||||||
|
))),
|
||||||
|
border: iced::Border {
|
||||||
|
color: color!(0x6b6b6b),
|
||||||
|
width: 1.0,
|
||||||
|
radius: Radius::from(2.0),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let content = row![
|
||||||
|
swatch,
|
||||||
|
container(text(type_name).size(11))
|
||||||
|
.width(Length::Fixed(type_w))
|
||||||
|
.align_y(iced::alignment::Vertical::Center),
|
||||||
|
container(rule::vertical(1)).height(Length::Fixed(12.0)),
|
||||||
|
container(
|
||||||
|
text(layer)
|
||||||
|
.size(10)
|
||||||
|
.align_x(Horizontal::Right)
|
||||||
|
.style(|theme: &Theme| iced::widget::text::Style {
|
||||||
|
color: Some(theme.palette().background.base.text.scale_alpha(0.72)),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.width(Length::Fixed(layer_w))
|
||||||
|
.align_y(iced::alignment::Vertical::Center),
|
||||||
|
]
|
||||||
|
.spacing(6)
|
||||||
|
.align_y(iced::Center);
|
||||||
|
|
||||||
let btn = button(content)
|
let btn = button(content)
|
||||||
.on_press(Message::CycleSelect(handle))
|
.on_press(Message::CycleSelect(handle))
|
||||||
.style(button::subtle)
|
.style(button::subtle)
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([4, 10]);
|
.padding([3, 8]);
|
||||||
// Highlight the underlying object while the cursor is over this row.
|
// Highlight the underlying object while the cursor is over this row.
|
||||||
mouse_area(btn)
|
mouse_area(btn)
|
||||||
.on_enter(Message::CycleHover(Some(handle)))
|
.on_enter(Message::CycleHover(Some(handle)))
|
||||||
|
|||||||
Reference in new issue
Block a user