1use crate::{
14 EditTextBox, RenameTitle,
15 edit::create,
16 names::ToolName,
17 rename_pin::{Field, RenamePin},
18 shape::ShapeId,
19 tool::{Action, Tool, Transition},
20 widget::drawing::Drawing,
21};
22use blockworx_geom::Pos2;
23use blockworx_paint::{Event, Interaction};
24
25pub fn on_click(tool: ToolName, interaction: &Interaction) -> Option<Transition> {
27 match interaction.event {
28 Some(Event::Clicked { pos }) => Some(Action::StampTool { tool, at: pos }.into()),
29 _ => None,
30 }
31}
32
33pub fn stamp(data: &mut Drawing<'_>, tool: ToolName, at: Pos2) -> Option<Tool> {
43 let settles_on = match tool {
44 ToolName::NewBlock => {
45 let box_ = create::stamped_block(at);
46 let block = data.add_block(box_.min, box_.max);
47 RenameTitle::action(data, ShapeId::Rect(block))
48 }
49 ToolName::NewArea => {
50 let box_ = create::stamped_area(at);
51 let area = data.add_area(box_.min, box_.max);
52 RenameTitle::action(data, area)
53 }
54 ToolName::AddText => {
55 let text = data.add_text_box(at);
56 EditTextBox::action(data, text)
57 }
58 ToolName::AddPin => {
59 let marker = crate::new_pin::marker_at(&crate::new_pin::markers_in_scope(data), at)?;
60 crate::new_pin::add_pin(data, marker)?
61 }
62 ToolName::AddPort if data.current_locked() => Transition::default(),
63 ToolName::AddPort => {
64 let anchor = data.add_port_auto_named(create::stamped_port(at));
65 RenamePin::action(data, anchor, Field::Name)
66 }
67 _ => return None,
68 };
69 match settles_on {
70 Transition::SwitchTool(tool) => Some(tool),
71 Transition::Action(_) => None,
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use crate::path::Scope;
81 use crate::widget::test_fixtures::{self as fx, Scene};
82 use blockworx_geom::{Rect, pos2};
83
84 fn scene() -> Scene {
86 Scene::new(vec![fx::block_in(
87 1,
88 Scope::Root,
89 Rect::from_min_max(pos2(0.0, 0.0), pos2(600.0, 400.0)),
90 )])
91 .inside(blockworx_doc::fixtures::block_id(1))
92 }
93
94 fn stamped(tool: ToolName) -> Vec<String> {
95 let mut scene = scene();
96 scene
97 .authored(|data| stamp(data, tool, pos2(200.0, 200.0)))
98 .1
99 }
100
101 #[test]
104 fn every_creator_stamps_its_own_default() {
105 for (tool, wrote) in [
106 (ToolName::NewBlock, "block"),
107 (ToolName::NewArea, "area"),
108 (ToolName::AddText, "text"),
109 (ToolName::AddPort, "pin"),
110 ] {
111 let narrated = stamped(tool);
112 assert!(
113 narrated.iter().any(|line| line.starts_with(wrote)),
114 "dropping {tool:?} authored no {wrote}; it authored {narrated:?}",
115 );
116 }
117 }
118
119 #[test]
123 fn a_drop_that_cannot_finish_writes_nothing() {
124 for tool in [ToolName::Route, ToolName::NewImage, ToolName::Select] {
125 let narrated = stamped(tool);
126 assert!(
127 narrated.is_empty(),
128 "dropping {tool:?} authored {narrated:?}",
129 );
130 }
131 }
132
133 #[test]
136 fn a_read_only_session_stamps_nothing() {
137 use crate::commands::apply_scripted;
138 for tool in [
139 ToolName::NewBlock,
140 ToolName::NewArea,
141 ToolName::AddText,
142 ToolName::AddPort,
143 ] {
144 let drop = |mut scene: Scene| {
145 scene
146 .authored(|data| {
147 apply_scripted(
148 Action::StampTool {
149 tool,
150 at: pos2(200.0, 200.0),
151 }
152 .into(),
153 data,
154 )
155 })
156 .1
157 };
158 assert!(
159 !drop(scene()).is_empty(),
160 "a writable session refused to stamp {tool:?}, so refusing it below proves nothing",
161 );
162 assert!(
163 drop(scene().read_only()).is_empty(),
164 "a read-only session stamped {tool:?}",
165 );
166 }
167 }
168
169 #[test]
172 fn a_port_dropped_on_a_locked_level_writes_nothing() {
173 let locked = |lock: bool| {
174 let mut ops = vec![fx::block_in(
175 1,
176 Scope::Root,
177 Rect::from_min_max(pos2(0.0, 0.0), pos2(600.0, 400.0)),
178 )];
179 if lock {
180 ops.push(fx::locked(1));
181 }
182 Scene::new(ops).inside(blockworx_doc::fixtures::block_id(1))
183 };
184 let mut unlocked = locked(false);
185 assert!(
186 !unlocked
187 .authored(|data| stamp(data, ToolName::AddPort, pos2(200.0, 200.0)))
188 .1
189 .is_empty(),
190 "an unlocked level refused the port, so refusing it below proves nothing",
191 );
192 let mut scene = locked(true);
193 assert!(
194 scene.drawing().current_locked(),
195 "precondition: the level is locked"
196 );
197 let (settles_on, narrated) =
198 scene.authored(|data| stamp(data, ToolName::AddPort, pos2(200.0, 200.0)));
199 assert!(
200 narrated.is_empty(),
201 "a locked level took a port: {narrated:?}"
202 );
203 assert!(
204 matches!(settles_on, Some(Tool::Select(_))),
205 "a refused port did not settle on Select",
206 );
207 }
208
209 #[test]
212 fn a_stamped_block_is_named_and_opens_its_editor() {
213 let mut scene = scene();
214 let at = pos2(200.0, 200.0);
215 let armed = scene.commit(|data| stamp(data, ToolName::NewBlock, at));
216 assert!(
217 matches!(armed, Some(Tool::RenameTitle(_))),
218 "a stamped block did not open its name editor",
219 );
220 let drawing = scene.drawing();
221 let titles: Vec<String> = drawing
222 .shapes()
223 .filter_map(|(_, shape)| shape.title().map(|t| t.name.to_owned()))
224 .collect();
225 assert_eq!(titles, vec!["Block 1".to_owned()]);
226 }
227
228 #[test]
231 fn a_stamped_block_lands_on_the_grid_under_the_drop() {
232 let mut scene = scene();
233 let at = pos2(203.0, 197.0);
235 scene.commit(|data| stamp(data, ToolName::NewBlock, at));
236 let drawing = scene.drawing();
237 let placed = drawing
238 .shapes()
239 .map(|(_, shape)| shape.gui_rect())
240 .next()
241 .expect("the stamp landed a shape");
242 let wanted = create::stamped_block(at);
243 assert_eq!(placed.min, wanted.min);
244 assert_ne!(
245 wanted.min, at,
246 "precondition: the drop point is off the grid, so the snap moved it",
247 );
248 }
249}