1use crate::{
16 edit::create,
17 shape::ShapeId,
18 tools::{
19 EditTextBox, RenameTitle,
20 names::ToolName,
21 rename_pin::{Field, RenamePin},
22 tool::{Action, Tool},
23 },
24 widget::drawing::Drawing,
25};
26use blockworx_geom::Pos2;
27
28pub fn stamp(data: &mut Drawing<'_>, tool: ToolName, at: Pos2) -> Option<Tool> {
36 let settles_on = match tool {
37 ToolName::NewBlock => {
38 let box_ = create::stamped_block(at);
39 let block = data.add_block(box_.min, box_.max);
40 RenameTitle::action(data, ShapeId::Rect(block))
41 }
42 ToolName::NewArea => {
43 let box_ = create::stamped_area(at);
44 let area = data.add_area(box_.min, box_.max);
45 RenameTitle::action(data, area)
46 }
47 ToolName::AddText => {
48 let text = data.add_text_box(at);
49 EditTextBox::action(data, text)
50 }
51 ToolName::AddPort => {
52 let anchor = data.add_port_auto_named(create::stamped_port(at));
53 RenamePin::action(data, anchor, Field::Name)
54 }
55 _ => return None,
56 };
57 match settles_on {
58 Action::SwitchTool(tool) => Some(tool),
59 _ => None,
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use crate::path::Scope;
69 use crate::widget::test_fixtures::{self as fx, Scene};
70 use blockworx_geom::{Rect, pos2};
71
72 fn scene() -> Scene {
74 Scene::new(vec![fx::block_in(
75 1,
76 Scope::Root,
77 Rect::from_min_max(pos2(0.0, 0.0), pos2(600.0, 400.0)),
78 )])
79 .inside(blockworx_doc::fixtures::block_id(1))
80 }
81
82 fn stamped(tool: ToolName) -> Vec<String> {
83 let mut scene = scene();
84 scene
85 .authored(|data| stamp(data, tool, pos2(200.0, 200.0)))
86 .1
87 }
88
89 #[test]
92 fn every_creator_stamps_its_own_default() {
93 for (tool, wrote) in [
94 (ToolName::NewBlock, "block"),
95 (ToolName::NewArea, "area"),
96 (ToolName::AddText, "text"),
97 (ToolName::AddPort, "pin"),
98 ] {
99 let narrated = stamped(tool);
100 assert!(
101 narrated.iter().any(|line| line.starts_with(wrote)),
102 "dropping {tool:?} authored no {wrote}; it authored {narrated:?}",
103 );
104 }
105 }
106
107 #[test]
111 fn a_drop_that_cannot_finish_writes_nothing() {
112 for tool in [ToolName::Route, ToolName::NewImage, ToolName::Select] {
113 let narrated = stamped(tool);
114 assert!(
115 narrated.is_empty(),
116 "dropping {tool:?} authored {narrated:?}",
117 );
118 }
119 }
120
121 #[test]
125 fn an_image_drop_is_handed_back_carrying_its_drop_point() {
126 use crate::tools::commands::{ScriptedApply, apply_scripted};
127 let mut scene = scene();
128 let at = pos2(200.0, 200.0);
129 let (outcome, narrated) = scene.authored(|data| {
130 apply_scripted(
131 Action::StampTool {
132 tool: ToolName::NewImage,
133 at,
134 },
135 data,
136 )
137 });
138 assert!(
139 narrated.is_empty(),
140 "the image drop wrote {narrated:?} before anything was picked",
141 );
142 match outcome {
143 ScriptedApply::NeedsApp(handed_back) => assert!(
144 matches!(
145 *handed_back,
146 Action::StampTool {
147 tool: ToolName::NewImage,
148 at: point,
149 } if point == at
150 ),
151 "the drop point did not travel with the request",
152 ),
153 ScriptedApply::Applied(_) => panic!("an image was placed with no file picked"),
154 }
155 }
156
157 #[test]
160 fn a_read_only_session_stamps_nothing() {
161 use crate::tools::commands::apply_scripted;
162 for tool in [
163 ToolName::NewBlock,
164 ToolName::NewArea,
165 ToolName::AddText,
166 ToolName::AddPort,
167 ] {
168 let drop = |mut scene: Scene| {
169 scene
170 .authored(|data| {
171 apply_scripted(
172 Action::StampTool {
173 tool,
174 at: pos2(200.0, 200.0),
175 },
176 data,
177 )
178 })
179 .1
180 };
181 assert!(
182 !drop(scene()).is_empty(),
183 "a writable session refused to stamp {tool:?}, so refusing it below proves nothing",
184 );
185 assert!(
186 drop(scene().read_only()).is_empty(),
187 "a read-only session stamped {tool:?}",
188 );
189 }
190 }
191
192 #[test]
195 fn a_stamped_block_is_named_and_opens_its_editor() {
196 let mut scene = scene();
197 let at = pos2(200.0, 200.0);
198 let armed = scene.commit(|data| stamp(data, ToolName::NewBlock, at));
199 assert!(
200 matches!(armed, Some(Tool::RenameTitle(_))),
201 "a stamped block did not open its name editor",
202 );
203 let drawing = scene.drawing();
204 let titles: Vec<String> = drawing
205 .shapes()
206 .filter_map(|(_, shape)| shape.title().map(|t| t.name.to_owned()))
207 .collect();
208 assert_eq!(titles, vec!["Block 1".to_owned()]);
209 }
210
211 #[test]
214 fn a_stamped_block_lands_on_the_grid_under_the_drop() {
215 let mut scene = scene();
216 let at = pos2(203.0, 197.0);
218 scene.commit(|data| stamp(data, ToolName::NewBlock, at));
219 let drawing = scene.drawing();
220 let placed = drawing
221 .shapes()
222 .map(|(_, shape)| shape.gui_rect())
223 .next()
224 .expect("the stamp landed a shape");
225 let wanted = create::stamped_block(at);
226 assert_eq!(placed.min, wanted.min);
227 assert_ne!(
228 wanted.min, at,
229 "precondition: the drop point is off the grid, so the snap moved it",
230 );
231 }
232}