diff --git a/src/modules/draw/modify/offset.rs b/src/modules/draw/modify/offset.rs index 7cf95a265..519edcef4 100644 --- a/src/modules/draw/modify/offset.rs +++ b/src/modules/draw/modify/offset.rs @@ -783,9 +783,6 @@ enum Step { /// pre-selection when OFFSET starts with objects selected (#422). targets: Vec, locked: Option, - /// True when the targets came from the pre-selection: the commit ends - /// the command instead of looping back for another pick. - from_selection: bool, /// Keep the side-pick step active and use each new result as the source /// for the next offset. multiple: bool, @@ -796,6 +793,10 @@ pub struct OffsetCommand { step: Step, all_entities: Vec, entity_index: ModifyEntityIndex, + /// Live entity supplied by the scene before an object-pick is handled. + /// Unlike the command's opening snapshot, this also includes objects + /// created by earlier offsets while the command remains active. + picked: Option, /// Pre-selected offsettable objects (pick-first, #422); consumed when the /// distance step resolves. preselected: Vec, @@ -822,6 +823,7 @@ impl OffsetCommand { step: Step::Distance, all_entities, entity_index, + picked: None, preselected: Vec::new(), } } @@ -834,6 +836,7 @@ impl OffsetCommand { step: Step::Distance, all_entities, entity_index, + picked: None, preselected: targets, } } @@ -848,7 +851,6 @@ impl OffsetCommand { self.step = Step::PickSide { targets: std::mem::take(&mut self.preselected), locked, - from_selection: true, multiple: false, }; } @@ -931,6 +933,14 @@ impl CadCommand for OffsetCommand { matches!(self.step, Step::SelectObject { .. }) } + fn inject_before_entity_pick(&self) -> bool { + true + } + + fn inject_picked_entity(&mut self, entity: EntityType) { + self.picked = Some(entity); + } + fn on_entity_pick(&mut self, handle: Handle, _pt: DVec3) -> CmdResult { let locked = match &self.step { Step::SelectObject { locked } => *locked, @@ -940,9 +950,11 @@ impl CadCommand for OffsetCommand { return CmdResult::NeedPoint; } - let entity = self - .entity_index.get(&self.all_entities, handle) - .cloned(); + let entity = self.picked.take().or_else(|| { + self.entity_index + .get(&self.all_entities, handle) + .cloned() + }); // Accept every type compute_offsets can offset — including XLine (#296), // and Ellipse/Spline whose offset functions existed but weren't reachable. @@ -951,7 +963,6 @@ impl CadCommand for OffsetCommand { self.step = Step::PickSide { targets: vec![e], locked, - from_selection: false, multiple: false, }; CmdResult::NeedPoint @@ -1044,13 +1055,12 @@ impl CadCommand for OffsetCommand { } fn on_point(&mut self, pt: DVec3) -> CmdResult { - let (locked, targets, from_selection, multiple) = match &self.step { + let (locked, targets, multiple) = match &self.step { Step::PickSide { locked, targets, - from_selection, multiple, - } => (*locked, targets.clone(), *from_selection, *multiple), + } => (*locked, targets.clone(), *multiple), _ => return CmdResult::NeedPoint, }; // Each target offsets by its own through-distance (or the locked @@ -1073,7 +1083,6 @@ impl CadCommand for OffsetCommand { self.step = Step::PickSide { targets: news.clone(), locked, - from_selection, multiple: true, }; return if news.len() == 1 { @@ -1082,17 +1091,13 @@ impl CadCommand for OffsetCommand { CmdResult::CommitEntities(news) }; } - if from_selection { - // Pre-selection commit ends the command in one undo step. - return CmdResult::ReplaceMany(vec![], news); - } // Classic loop (#418): commit this offset and go back to the object // pick at the same distance, until Enter / Esc finishes. self.step = Step::SelectObject { locked }; if news.len() == 1 { CmdResult::CommitEntity(news.pop().unwrap()) } else { - CmdResult::ReplaceMany(vec![], news) + CmdResult::CommitEntities(news) } } diff --git a/src/ui/icons.rs b/src/ui/icons.rs index 2f72b7402..2566b1830 100644 --- a/src/ui/icons.rs +++ b/src/ui/icons.rs @@ -449,6 +449,20 @@ pub fn themed_primary<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, .into() } +/// Render an icon with the foreground chosen for a weak primary surface. +pub fn themed_primary_weak_text<'a, M: 'a>( + bytes: &'static [u8], + size: f32, +) -> Element<'a, M> { + svg(svg::Handle::from_memory(bytes)) + .width(size) + .height(size) + .style(|theme: &Theme, _| svg::Style { + color: Some(theme.palette().primary.weak.text), + }) + .into() +} + /// Render a positive-state chrome icon with the active Iced theme's success color. pub fn themed_success<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, M> { svg(svg::Handle::from_memory(bytes)) @@ -569,8 +583,8 @@ pub fn themed_arrow_left<'a, M: 'a>(size: f32) -> Element<'a, M> { themed(TRI_LEFT, size) } -pub fn themed_primary_arrow_down<'a, M: 'a>(size: f32) -> Element<'a, M> { - themed_primary(TRI_DOWN, size) +pub fn themed_primary_weak_arrow_down<'a, M: 'a>(size: f32) -> Element<'a, M> { + themed_primary_weak_text(TRI_DOWN, size) } pub fn themed_secondary_arrow_down<'a, M: 'a>(size: f32) -> Element<'a, M> { diff --git a/src/ui/statusbar/mod.rs b/src/ui/statusbar/mod.rs index aacf0195f..5bb612e7d 100644 --- a/src/ui/statusbar/mod.rs +++ b/src/ui/statusbar/mod.rs @@ -636,7 +636,7 @@ fn tip_node<'a>(content: Element<'a, Message>, body: Element<'a, Message>) -> El /// call site already wraps it with. fn toggle_pill(icon: &'static [u8], active: bool, msg: Message) -> Element<'static, Message> { let icon = if active { - crate::ui::icons::themed_primary(icon, 17.0) + crate::ui::icons::themed_primary_weak_text(icon, 17.0) } else { crate::ui::icons::themed_secondary(icon, 17.0) }; @@ -646,12 +646,12 @@ fn toggle_pill(icon: &'static [u8], active: bool, msg: Message) -> Element<'stat let mut style = button::subtle(theme, status); if active { let palette = theme.palette(); - style.background = Some(Background::Color(match status { - button::Status::Hovered => palette.primary.base.color, - _ => palette.primary.weak.color, - })); + style.background = Some(Background::Color(palette.primary.weak.color)); style.text_color = palette.primary.weak.text; - style.border.color = palette.primary.base.color; + style.border.color = match status { + button::Status::Hovered => palette.primary.strong.color, + _ => palette.primary.base.color, + }; style.border.width = 1.0; } style @@ -724,7 +724,7 @@ fn polar_pill<'a>( }; let polar_icon = if active { - crate::ui::icons::themed_primary(crate::ui::icons::ST_POLAR, 17.0) + crate::ui::icons::themed_primary_weak_text(crate::ui::icons::ST_POLAR, 17.0) } else { crate::ui::icons::themed_secondary(crate::ui::icons::ST_POLAR, 17.0) }; @@ -735,7 +735,7 @@ fn polar_pill<'a>( let palette = theme.palette(); text::Style { color: Some(if active { - palette.primary.base.color + palette.primary.weak.text } else { palette.background.base.text.scale_alpha(0.72) }), @@ -759,7 +759,7 @@ fn polar_pill<'a>( menu_tip( mouse_area( container(if active { - crate::ui::icons::themed_primary_arrow_down(9.0) + crate::ui::icons::themed_primary_weak_arrow_down(9.0) } else { crate::ui::icons::themed_secondary_arrow_down(9.0) }) @@ -789,7 +789,7 @@ fn osnap_btn<'a>( ) -> Element<'a, Message> { let on = active || snap_enabled; let snap_icon = if on { - crate::ui::icons::themed_primary(crate::ui::icons::ST_OSNAP, 17.0) + crate::ui::icons::themed_primary_weak_text(crate::ui::icons::ST_OSNAP, 17.0) } else { crate::ui::icons::themed_secondary(crate::ui::icons::ST_OSNAP, 17.0) }; @@ -804,7 +804,7 @@ fn osnap_btn<'a>( menu_tip( mouse_area( container(if on { - crate::ui::icons::themed_primary_arrow_down(9.0) + crate::ui::icons::themed_primary_weak_arrow_down(9.0) } else { crate::ui::icons::themed_secondary_arrow_down(9.0) }) diff --git a/src/ui/window/alias_editor.rs b/src/ui/window/alias_editor.rs index 34180999a..b3da9f8aa 100644 --- a/src/ui/window/alias_editor.rs +++ b/src/ui/window/alias_editor.rs @@ -64,7 +64,7 @@ pub fn view_window( .size(13) .padding([3, 6]) .width(sizing.width); - let del = button(crate::ui::icons::themed_danger(crate::ui::icons::CLOSE, 12.0)) + let del = button(crate::ui::icons::themed_danger_text(crate::ui::icons::CLOSE, 12.0)) .on_press(Message::AliasEditorRemove(idx)) .padding([2, 6]) .style(button::danger);