1use crate::{
10 EditTextBox, RenameBlockType, RenamePin, RenameTitle, RetypePin,
11 rename_pin::Field,
12 shape::{
13 ShapeId,
14 pin::{PinSide, slot},
15 },
16 tool::{Tool, Transition},
17 widget::drawing::Drawing,
18};
19use blockworx_doc::{
20 geometry::PinSlot,
21 id::{BlockId, PinId, TextId},
22};
23
24#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
27pub enum EditTarget {
28 Title(ShapeId),
29 BlockType(BlockId),
30 PinName(PinId),
31 PinType(PinId),
32 PinTag(PinId),
33 Text(TextId),
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::Title(id)];
52 if !data.shape_owner_locked(id) {
53 targets.push(EditTarget::BlockType(block));
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, target: EditTarget) -> Option<Tool> {
79 match target {
80 EditTarget::Title(shape) => RenameTitle::new_with_shape(data, shape).map(Into::into),
81 EditTarget::BlockType(block) => RenameBlockType::new_with_rect(data, block).map(Into::into),
82 EditTarget::PinName(p) => RenamePin::new_with_anchor(data, p, Field::Name).map(Into::into),
83 EditTarget::PinType(p) => RetypePin::new_with_anchor(data, p).map(Into::into),
84 EditTarget::PinTag(p) => RenamePin::new_with_anchor(data, p, Field::Tag).map(Into::into),
85 EditTarget::Text(text) => EditTextBox::new_for(data, text).map(Into::into),
86 }
87}
88
89pub fn advance(data: &Drawing, block: BlockId, current: EditTarget) -> Option<Transition> {
91 let next = next_target(data, block, current)?;
92 tool_for_target(data, next).map(Transition::SwitchTool)
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::path::Scope;
99 use crate::widget::test_fixtures::{self as fx, Scene};
100 use blockworx_doc::{
101 fixtures::{block_id, pin_id},
102 opcode::OpCodes,
103 };
104 use blockworx_geom::{Rect, pos2};
105
106 fn block_120x300(n: u32) -> OpCodes {
107 fx::block_in(
108 n,
109 Scope::Root,
110 Rect::from_min_max(pos2(0.0, 0.0), pos2(120.0, 300.0)),
111 )
112 }
113
114 #[test]
115 fn cycle_is_name_type_then_west_pins_then_east_with_side_aware_field_order() {
116 let block = block_id(1);
117 let (e_top, w_bot, w_top, e_bot) = (pin_id(2), pin_id(3), pin_id(4), pin_id(5));
119 let mut scene = Scene::new(vec![
120 block_120x300(1),
121 fx::pin(2, 1, PinSide::East, 0),
122 fx::pin(3, 1, PinSide::West, 1),
123 fx::pin(4, 1, PinSide::West, 0),
124 fx::pin(5, 1, PinSide::East, 1),
125 ]);
126 let data = scene.drawing();
127
128 let mut expected = vec![
129 EditTarget::Title(ShapeId::Rect(block)),
130 EditTarget::BlockType(block),
131 ];
132 for p in [w_top, w_bot] {
134 expected.extend([
135 EditTarget::PinTag(p),
136 EditTarget::PinName(p),
137 EditTarget::PinType(p),
138 ]);
139 }
140 for p in [e_top, e_bot] {
142 expected.extend([
143 EditTarget::PinName(p),
144 EditTarget::PinTag(p),
145 EditTarget::PinType(p),
146 ]);
147 }
148 assert_eq!(edit_cycle(&data, block), expected);
149 }
150
151 #[test]
152 fn next_target_wraps_from_last_pin_field_back_to_name() {
153 let (block, p) = (block_id(1), pin_id(2));
154 let mut scene = Scene::new(vec![block_120x300(1), fx::pin(2, 1, PinSide::West, 0)]);
155 let data = scene.drawing();
156 assert_eq!(
158 next_target(&data, block, EditTarget::Title(ShapeId::Rect(block))),
159 Some(EditTarget::BlockType(block))
160 );
161 assert_eq!(
163 next_target(&data, block, EditTarget::BlockType(block)),
164 Some(EditTarget::PinTag(p))
165 );
166 assert_eq!(
167 next_target(&data, block, EditTarget::PinTag(p)),
168 Some(EditTarget::PinName(p))
169 );
170 assert_eq!(
172 next_target(&data, block, EditTarget::PinType(p)),
173 Some(EditTarget::Title(ShapeId::Rect(block)))
174 );
175 }
176
177 #[test]
178 fn locked_block_drops_type_from_the_cycle() {
179 let block = block_id(1);
180 let mut scene = Scene::new(vec![block_120x300(1), fx::locked(1)]);
181 {
182 let data = scene.drawing();
183 assert!(!edit_cycle(&data, block).contains(&EditTarget::BlockType(block)));
186 assert_eq!(
187 edit_cycle(&data, block),
188 vec![EditTarget::Title(ShapeId::Rect(block))]
189 );
190 }
191 scene.apply(vec![fx::unlocked(1)]);
193 let data = scene.drawing();
194 assert_eq!(
195 next_target(&data, block, EditTarget::Title(ShapeId::Rect(block))),
196 Some(EditTarget::BlockType(block))
197 );
198 }
199}