1use crate::{
10 shape::{
11 ShapeId,
12 pin::{PinSide, slot},
13 },
14 tools::{
15 RenameBlockType, RenamePin, RenameTitle, RetypePin,
16 rename_pin::Field,
17 tool::{Action, Tool},
18 },
19 widget::drawing::Drawing,
20};
21use blockworx_doc::{
22 geometry::PinSlot,
23 id::{BlockId, PinId},
24};
25
26#[derive(Clone, Copy, PartialEq, Eq, Debug)]
28pub enum EditTarget {
29 Name,
30 Type,
31 PinName(PinId),
32 PinType(PinId),
33 PinTag(PinId),
34}
35
36pub fn edit_cycle(data: &Drawing<'_>, block: BlockId) -> Vec<EditTarget> {
44 let id = ShapeId::Rect(block);
45 let Some(shape) = data.shape(id) else {
46 return Vec::new();
47 };
48 let mut pins: Vec<(PinId, PinSlot)> = Vec::new();
49 shape.with_pins(|pin_id, pin| pins.push((pin_id, slot(pin))));
50 pins.sort_by_key(|(_, slot)| (slot.side != PinSide::West, slot.offset));
51 let mut targets = vec![EditTarget::Name];
52 if !data.shape_owner_locked(id) {
53 targets.push(EditTarget::Type);
54 }
55 for (pin_id, slot) in pins {
56 let (name, tag, ty) = (
57 EditTarget::PinName(pin_id),
58 EditTarget::PinTag(pin_id),
59 EditTarget::PinType(pin_id),
60 );
61 match slot.side {
62 PinSide::West => targets.extend([tag, name, ty]),
63 PinSide::East => targets.extend([name, tag, ty]),
64 }
65 }
66 targets
67}
68
69pub fn next_target(data: &Drawing<'_>, block: BlockId, current: EditTarget) -> Option<EditTarget> {
71 let cycle = edit_cycle(data, block);
72 let idx = cycle.iter().position(|t| *t == current)?;
73 Some(cycle[(idx + 1) % cycle.len()])
74}
75
76pub fn tool_for_target(data: &Drawing, block: BlockId, target: EditTarget) -> Option<Tool> {
78 match target {
79 EditTarget::Name => RenameTitle::new_with_shape(data, ShapeId::Rect(block)).map(Into::into),
80 EditTarget::Type => RenameBlockType::new_with_rect(data, block).map(Into::into),
81 EditTarget::PinName(p) => RenamePin::new_with_anchor(data, p, Field::Name).map(Into::into),
82 EditTarget::PinType(p) => RetypePin::new_with_anchor(data, p).map(Into::into),
83 EditTarget::PinTag(p) => RenamePin::new_with_anchor(data, p, Field::Tag).map(Into::into),
84 }
85}
86
87pub fn advance(data: &Drawing, block: BlockId, current: EditTarget) -> Option<Action> {
89 let next = next_target(data, block, current)?;
90 tool_for_target(data, block, next).map(Action::SwitchTool)
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use crate::path::Scope;
97 use crate::widget::test_fixtures::{self as fx, Scene};
98 use blockworx_doc::{
99 fixtures::{block_id, pin_id},
100 opcode::OpCodes,
101 };
102 use blockworx_geom::{Rect, pos2};
103
104 fn block_120x300(n: u32) -> OpCodes {
105 fx::block_in(
106 n,
107 Scope::Root,
108 Rect::from_min_max(pos2(0.0, 0.0), pos2(120.0, 300.0)),
109 )
110 }
111
112 #[test]
113 fn cycle_is_name_type_then_west_pins_then_east_with_side_aware_field_order() {
114 let block = block_id(1);
115 let (e_top, w_bot, w_top, e_bot) = (pin_id(2), pin_id(3), pin_id(4), pin_id(5));
117 let mut scene = Scene::new(vec![
118 block_120x300(1),
119 fx::pin(2, 1, PinSide::East, 0),
120 fx::pin(3, 1, PinSide::West, 1),
121 fx::pin(4, 1, PinSide::West, 0),
122 fx::pin(5, 1, PinSide::East, 1),
123 ]);
124 let data = scene.drawing();
125
126 let mut expected = vec![EditTarget::Name, EditTarget::Type];
127 for p in [w_top, w_bot] {
129 expected.extend([
130 EditTarget::PinTag(p),
131 EditTarget::PinName(p),
132 EditTarget::PinType(p),
133 ]);
134 }
135 for p in [e_top, e_bot] {
137 expected.extend([
138 EditTarget::PinName(p),
139 EditTarget::PinTag(p),
140 EditTarget::PinType(p),
141 ]);
142 }
143 assert_eq!(edit_cycle(&data, block), expected);
144 }
145
146 #[test]
147 fn next_target_wraps_from_last_pin_field_back_to_name() {
148 let (block, p) = (block_id(1), pin_id(2));
149 let mut scene = Scene::new(vec![block_120x300(1), fx::pin(2, 1, PinSide::West, 0)]);
150 let data = scene.drawing();
151 assert_eq!(
153 next_target(&data, block, EditTarget::Name),
154 Some(EditTarget::Type)
155 );
156 assert_eq!(
158 next_target(&data, block, EditTarget::Type),
159 Some(EditTarget::PinTag(p))
160 );
161 assert_eq!(
162 next_target(&data, block, EditTarget::PinTag(p)),
163 Some(EditTarget::PinName(p))
164 );
165 assert_eq!(
167 next_target(&data, block, EditTarget::PinType(p)),
168 Some(EditTarget::Name)
169 );
170 }
171
172 #[test]
173 fn locked_block_drops_type_from_the_cycle() {
174 let block = block_id(1);
175 let mut scene = Scene::new(vec![block_120x300(1), fx::locked(1)]);
176 {
177 let data = scene.drawing();
178 assert!(!edit_cycle(&data, block).contains(&EditTarget::Type));
181 assert_eq!(edit_cycle(&data, block), vec![EditTarget::Name]);
182 }
183 scene.apply(vec![fx::unlocked(1)]);
185 let data = scene.drawing();
186 assert_eq!(
187 next_target(&data, block, EditTarget::Name),
188 Some(EditTarget::Type)
189 );
190 }
191}