refactor(ui): adopt iced color picker

Remove local Pin/PickList wrappers and the ACI grid.\nBlock viewport input behind the picker and load iced_aw glyphs.
This commit is contained in:
Hakan Seven 2026-07-30 15:19:58 +03:00
commit c9c3d3b1bf
25 changed files with 289 additions and 255 deletions

View file

@ -79,11 +79,12 @@ pub(super) fn viewport_controls<'a>(
};
// Render-mode picker, restyled borderless so the outer chip frames it.
let picker = crate::ui::pick_list(
render_modes,
let picker = iced::widget::pick_list(
Some(RenderModeChoice(render_mode)),
|c| Message::SetRenderMode(c.0),
render_modes,
|value| value.to_string(),
)
.on_select(|c| Message::SetRenderMode(c.0))
.text_size(11)
.padding([4, 6])
.style(move |theme: &Theme, _| {

View file

@ -788,10 +788,10 @@ impl OpenCADStudio {
.into();
// Pin the bar to the active model tile's top-left corner so it
// follows the active panel in a tiled layout.
let bar_layer = crate::ui::pin_at(
iced::Point::new(rect.x, rect.y),
let bar_layer = iced::widget::pin(
container(adaptive).width(iced::Length::Fixed(rect.width.max(1.0))),
);
)
.position(iced::Point::new(rect.x.max(0.0), rect.y.max(0.0)));
viewport_stack = viewport_stack.push(bar_layer);
}
@ -840,7 +840,8 @@ impl OpenCADStudio {
},
..Default::default()
});
let border_layer = crate::ui::pin_at(iced::Point::new(x, y), border_frame);
let border_layer =
iced::widget::pin(border_frame).position(iced::Point::new(x, y));
viewport_stack = viewport_stack.push(border_layer);
let vp_mode = tab
@ -866,10 +867,10 @@ impl OpenCADStudio {
])
.report_width0(self.render_bar_w.clone())
.into();
let picker_layer = crate::ui::pin_at(
iced::Point::new(x + 4.0, y + 4.0),
let picker_layer = iced::widget::pin(
container(adaptive).width(iced::Length::Fixed(rect.width.max(1.0))),
);
)
.position(iced::Point::new(x + 4.0, y + 4.0));
viewport_stack = viewport_stack.push(picker_layer);
// Hide the ViewCube first — before the render bar — when they collide.
@ -877,10 +878,8 @@ impl OpenCADStudio {
let cube_x = (rect.x + rect.width - VIEWCUBE_HIT_SIZE - VIEWCUBE_PAD).max(0.0);
let cube_y = (rect.y + VIEWCUBE_PAD).max(0.0);
let controls = crate::ui::pin_at(
iced::Point::new(cube_x, cube_y),
viewcube_nav_controls(),
);
let controls = iced::widget::pin(viewcube_nav_controls())
.position(iced::Point::new(cube_x, cube_y));
viewport_stack = viewport_stack.push(controls);
let ucs_current = tab
@ -896,13 +895,14 @@ impl OpenCADStudio {
.map(|u| u.name.clone())
.filter(|n| !n.is_empty())
.collect();
let picker = crate::ui::pin_at(
iced::Point::new(
let picker = iced::widget::pin(iced::widget::opaque(viewcube_ucs_picker(
ucs_current,
ucs_names,
)))
.position(iced::Point::new(
cube_x + VIEWCUBE_HIT_SIZE * 0.5 - UCS_PICKER_W * 0.5,
cube_y + VIEWCUBE_HIT_SIZE + 6.0,
),
iced::widget::opaque(viewcube_ucs_picker(ucs_current, ucs_names)),
);
));
viewport_stack = viewport_stack.push(picker);
}
}
@ -917,10 +917,8 @@ impl OpenCADStudio {
let cube_y = (rect.y + VIEWCUBE_PAD).max(0.0);
// Cube hit area + nav controls (home / roll / nudge) as one layer.
let controls = crate::ui::pin_at(
iced::Point::new(cube_x, cube_y),
viewcube_nav_controls(),
);
let controls = iced::widget::pin(viewcube_nav_controls())
.position(iced::Point::new(cube_x, cube_y));
viewport_stack = viewport_stack.push(controls);
// WCS / named-UCS selector under the cube.
@ -937,13 +935,14 @@ impl OpenCADStudio {
.map(|u| u.name.clone())
.filter(|n| !n.is_empty())
.collect();
let picker = crate::ui::pin_at(
iced::Point::new(
let picker = iced::widget::pin(iced::widget::opaque(viewcube_ucs_picker(
ucs_current,
ucs_names,
)))
.position(iced::Point::new(
cube_x + VIEWCUBE_HIT_SIZE * 0.5 - UCS_PICKER_W * 0.5,
cube_y + VIEWCUBE_HIT_SIZE + 6.0,
),
iced::widget::opaque(viewcube_ucs_picker(ucs_current, ucs_names)),
);
));
viewport_stack = viewport_stack.push(picker);
}
@ -1554,21 +1553,24 @@ impl OpenCADStudio {
}
None => composed.into(),
};
// The colour picker is a nested modal: it stacks over whichever dialog
// (style editor, properties, …) requested it.
if self.color_pick_target.is_some() {
crate::ui::modal::modal(
base,
"Select Color",
iced::widget::container(crate::ui::color_select::color_grid_window(
Message::ColorWindowPick,
))
.width(iced::Length::Fit.max(420.0))
.height(iced::Length::Fit.max(470.0)),
// iced_aw owns the colour-picker overlay and keeps it above whichever
// application modal requested it.
if let Some((_, current)) = self.color_pick_target.as_ref() {
let initial = crate::ui::properties::acad_color_display(*current).0;
let modal_base =
crate::ui::modal::backdrop(base, Message::CloseColorPicker);
iced_aw::ColorPicker::new(
true,
initial,
modal_base,
Message::CloseColorPicker,
iced::Vector::ZERO,
crate::ui::modal::ModalOptions::MOVABLE_FIXED,
|color| {
Message::ColorWindowPick(
crate::ui::color_select::iced_to_acad_color(color),
)
},
)
.into()
} else {
base
}

View file

@ -982,9 +982,12 @@ fn save_as_dialog_window<'a>(filename: &'a str, format: &'a str) -> Element<'a,
items.push(
row![
label("Format:").width(70),
crate::ui::pick_list(crate::io::SAVE_FORMAT_OPTIONS, sel_fmt, |s: &str| {
Message::SaveDialogFormatChanged(s.to_string())
})
iced::widget::pick_list(
sel_fmt,
crate::io::SAVE_FORMAT_OPTIONS,
|value| value.to_string(),
)
.on_select(|s: &str| Message::SaveDialogFormatChanged(s.to_string()))
.width(Fit),
]
.align_y(iced::Alignment::Center)

View file

@ -9,7 +9,9 @@ pub(super) fn position_canvas_overlay<'a>(
anchor: iced::Point,
panel: Element<'a, Message>,
) -> Element<'a, Message> {
crate::ui::pin_at(anchor, iced::widget::opaque(panel))
iced::widget::pin(iced::widget::opaque(panel))
.position(iced::Point::new(anchor.x.max(0.0), anchor.y.max(0.0)))
.into()
}
// ── In-place MText editor overlay ───────────────────────────────────────────
@ -369,7 +371,12 @@ pub(super) fn mtext_editor_overlay<'a>(
} else {
styles
};
let style_pl = crate::ui::pick_list(style_opts, Some(ed.style.clone()), Message::MTextStyle)
let style_pl = iced::widget::pick_list(
Some(ed.style.clone()),
style_opts,
|value| value.to_string(),
)
.on_select(Message::MTextStyle)
.text_size(11)
.width(iced::Length::Fixed(96.0));
let font_sel = if ed.font.trim().is_empty() {
@ -377,14 +384,15 @@ pub(super) fn mtext_editor_overlay<'a>(
} else {
ed.font.clone()
};
let font_pl = crate::ui::pick_list(
let font_pl = iced::widget::pick_list(
Some(font_sel),
MTEXT_FONTS
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>(),
Some(font_sel),
Message::MTextFont,
|value| value.to_string(),
)
.on_select(Message::MTextFont)
.text_size(11)
.width(iced::Length::Fixed(120.0));
// Same colour picker as the Properties panel (named swatches + "More…" full
@ -398,7 +406,10 @@ pub(super) fn mtext_editor_overlay<'a>(
},
Message::MTextColorChanged,
Message::MTextColorPickerToggle,
Message::OpenColorWindow(crate::app::ColorPickTarget::MText),
Message::OpenColorWindow(
crate::app::ColorPickTarget::MText,
acadrust::types::Color::from_index(ed.color_aci as i16),
),
))
.width(iced::Length::Fixed(150.0));
@ -443,11 +454,12 @@ pub(super) fn mtext_editor_overlay<'a>(
.width(width);
// ── Row 2: oblique / width / char-spacing · align · line spacing · OK ─
let justify = crate::ui::pick_list(
JustifyChoice::ALL,
let justify = iced::widget::pick_list(
Some(JustifyChoice(ed.attachment)),
|c| Message::MTextJustify(c.0),
JustifyChoice::ALL,
|value| value.to_string(),
)
.on_select(|c| Message::MTextJustify(c.0))
.text_size(11)
.width(iced::Length::Fixed(112.0));
let row2 = row![
@ -1008,7 +1020,12 @@ pub(super) fn qselect_overlay<'a>(
Space::new().height(10),
row![
label("Object type:"),
crate::ui::pick_list(type_options, Some(type_sel), |s: String| {
iced::widget::pick_list(
Some(type_sel),
type_options,
|value| value.to_string(),
)
.on_select(|s: String| {
if s == QSELECT_ANY_TYPE {
Message::QSelectSetType(None)
} else {
@ -1022,17 +1039,18 @@ pub(super) fn qselect_overlay<'a>(
Space::new().height(6),
row![
label("Property:"),
crate::ui::pick_list(
prop_options,
iced::widget::pick_list(
Some(prop_sel),
|p: crate::app::QSelectPropertyChoice| {
prop_options,
|value| value.to_string(),
)
.on_select(|p: crate::app::QSelectPropertyChoice| {
if p.field.is_empty() {
Message::QSelectSetProperty(None)
} else {
Message::QSelectSetProperty(Some(p))
}
}
)
})
.width(Fill),
]
.align_y(iced::Alignment::Center)
@ -1040,11 +1058,12 @@ pub(super) fn qselect_overlay<'a>(
Space::new().height(6),
row![
label("Operator:"),
crate::ui::pick_list(
op_options,
iced::widget::pick_list(
Some(state.operator),
Message::QSelectSetOperator
op_options,
|value| value.to_string(),
)
.on_select(Message::QSelectSetOperator)
.width(Fill),
]
.align_y(iced::Alignment::Center)

View file

@ -16,7 +16,9 @@ use iced::{Background, Border, Element, Theme};
/// Place `el` at pixel offset (x, y) inside a fill layer (top-left origin).
fn vc_place<'a>(x: f32, y: f32, el: Element<'a, Message>) -> Element<'a, Message> {
crate::ui::pin_at(iced::Point::new(x, y), el)
iced::widget::pin(el)
.position(iced::Point::new(x.max(0.0), y.max(0.0)))
.into()
}
/// Borderless square icon button used by the ViewCube nav controls.
@ -155,7 +157,8 @@ pub(super) fn viewcube_ucs_picker<'a>(current: String, names: Vec<String>) -> El
} else {
current
};
crate::ui::pick_list(options, Some(selected), Message::SetViewcubeUcs)
iced::widget::pick_list(Some(selected), options, |value| value.to_string())
.on_select(Message::SetViewcubeUcs)
.text_size(11)
.padding([2, 6])
// Fixed width so the caller can centre it under the cube centre with a