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:
parent
3ab8dc3ca5
commit
c9c3d3b1bf
25 changed files with 289 additions and 255 deletions
|
|
@ -465,8 +465,8 @@ pub(super) struct OpenCADStudio {
|
|||
/// OS window Id of the primary application window.
|
||||
main_window: Option<window::Id>,
|
||||
// ── Floating panel windows ────────────────────────────────────────────
|
||||
/// Standalone "Select Color" palette window + the field it targets.
|
||||
color_pick_target: Option<ColorPickTarget>,
|
||||
/// Active `iced_aw` colour picker: destination plus its initial colour.
|
||||
color_pick_target: Option<(ColorPickTarget, AcadColor)>,
|
||||
/// The open in-canvas modal dialog, if any (Plan B: shared overlay instead
|
||||
/// of OS windows).
|
||||
active_modal: Option<ModalKind>,
|
||||
|
|
@ -2503,11 +2503,11 @@ pub enum Message {
|
|||
DsToggle(DsField),
|
||||
/// Toggle the expanded colour palette for a DimStyle colour field.
|
||||
DsColorMore(DsField),
|
||||
/// Open the standalone palette window targeting the given field.
|
||||
OpenColorWindow(ColorPickTarget),
|
||||
/// Close the nested colour-picker modal without choosing (Plan B).
|
||||
/// Open the `iced_aw` colour picker for a field and its current colour.
|
||||
OpenColorWindow(ColorPickTarget, AcadColor),
|
||||
/// Close the colour picker without choosing.
|
||||
CloseColorPicker,
|
||||
/// A colour was chosen in the standalone palette window.
|
||||
/// A colour was chosen in the `iced_aw` picker.
|
||||
ColorWindowPick(acadrust::types::Color),
|
||||
/// Set a block/linetype Handle field on the selected dim style from a
|
||||
/// dropdown of available block-records / linetypes (by name).
|
||||
|
|
@ -3177,6 +3177,7 @@ pub fn run() -> iced::Result {
|
|||
}
|
||||
})
|
||||
.theme(|state: &OpenCADStudio, _| state.active_theme.clone())
|
||||
.font(iced_aw::ICED_AW_FONT_BYTES)
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
@ -3206,5 +3207,6 @@ pub fn run_web() -> iced::Result {
|
|||
.subscription(OpenCADStudio::subscription)
|
||||
.title(|_state: &OpenCADStudio| "Open CAD Studio".to_string())
|
||||
.theme(|state: &OpenCADStudio| state.active_theme.clone())
|
||||
.font(iced_aw::ICED_AW_FONT_BYTES)
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2120,6 +2120,9 @@ impl OpenCADStudio {
|
|||
Message::PaneClicked(pane) => self.on_pane_clicked(pane),
|
||||
Message::PaneDragged(ev) => self.on_pane_dragged(ev),
|
||||
Message::PaneMove(idx, local) => {
|
||||
if self.color_pick_target.is_some() {
|
||||
return Task::none();
|
||||
}
|
||||
let p = self.pane_canvas_point(idx, local);
|
||||
// While dragging a pane, just track the cursor (no focus swap or
|
||||
// snap) so the drop target reads cleanly.
|
||||
|
|
@ -5744,8 +5747,8 @@ impl OpenCADStudio {
|
|||
};
|
||||
Task::none()
|
||||
}
|
||||
Message::OpenColorWindow(target) => {
|
||||
self.color_pick_target = Some(target);
|
||||
Message::OpenColorWindow(target, color) => {
|
||||
self.color_pick_target = Some((target, color));
|
||||
self.ds_color_open = None;
|
||||
self.mls_color_open = None;
|
||||
self.ts_color_open = None;
|
||||
|
|
@ -5753,8 +5756,7 @@ impl OpenCADStudio {
|
|||
let i = self.active_tab;
|
||||
self.tabs[i].properties.color_picker_open = false;
|
||||
self.tabs[i].layers.color_picker_row = None;
|
||||
// Shown as a nested modal over the active dialog (Plan B):
|
||||
// `color_pick_target.is_some()` drives the overlay in view_main.
|
||||
// `color_pick_target.is_some()` drives the iced_aw overlay.
|
||||
Task::none()
|
||||
}
|
||||
Message::CloseColorPicker => {
|
||||
|
|
|
|||
|
|
@ -960,7 +960,7 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
|
|||
|
||||
pub(super) fn on_color_window_pick(&mut self, color: acadrust::types::Color) -> Task<Message> {
|
||||
let s = crate::ui::color_select::color_to_aci_string(color);
|
||||
let edit = match self.color_pick_target.take() {
|
||||
let edit = match self.color_pick_target.take().map(|(target, _)| target) {
|
||||
Some(crate::app::ColorPickTarget::DimStyle(f)) => Some(Message::DsEdit(f, s)),
|
||||
Some(crate::app::ColorPickTarget::MLeader(f)) => {
|
||||
Some(Message::MLeaderStyleEdit { field: f, value: s })
|
||||
|
|
@ -986,10 +986,14 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
|
|||
}
|
||||
Some(crate::app::ColorPickTarget::Layer(idx)) => {
|
||||
self.tabs[self.active_tab].layers.selected = Some(idx);
|
||||
match color {
|
||||
acadrust::types::Color::Index(i) => Some(Message::LayerColorSet(i)),
|
||||
_ => None,
|
||||
}
|
||||
let index = match color {
|
||||
acadrust::types::Color::Index(i) => i,
|
||||
acadrust::types::Color::Rgb { r, g, b } => {
|
||||
crate::ui::color_select::nearest_aci(r, g, b)
|
||||
}
|
||||
_ => 7,
|
||||
};
|
||||
Some(Message::LayerColorSet(index))
|
||||
}
|
||||
Some(crate::app::ColorPickTarget::LayerState(idx)) => {
|
||||
Some(Message::LayerStateEditorLayerColor(idx, color))
|
||||
|
|
|
|||
|
|
@ -435,6 +435,10 @@ impl OpenCADStudio {
|
|||
}
|
||||
|
||||
pub(super) fn on_cursor_moved(&mut self, p: Point) -> Task<Message> {
|
||||
if self.color_pick_target.is_some() {
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
// `p` is relative to the ViewCube hit area's top-left. Map
|
||||
// it back to full-canvas coordinates so ViewportClick's
|
||||
// hit-test lines up. The hit area sits in the top-right of
|
||||
|
|
@ -498,7 +502,7 @@ impl OpenCADStudio {
|
|||
// leaks through the stack to the pane mouse_area beneath and
|
||||
// would track the crosshair over the dropdown's empty areas.
|
||||
// Drop the move here instead. (#227)
|
||||
if self.ribbon.open_dropdown.is_some() {
|
||||
if self.ribbon.open_dropdown.is_some() || self.color_pick_target.is_some() {
|
||||
return Task::none();
|
||||
}
|
||||
let i = self.active_tab;
|
||||
|
|
|
|||
|
|
@ -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, _| {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue