1 -- primitives for editing drawings
3 require
'drawing_tests'
5 -- All drawings span 100% of some conceptual 'page width' and divide it up
7 function Drawing
.draw(State
, line_index
, y
)
8 local line
= State
.lines
[line_index
]
9 local line_cache
= State
.line_cache
[line_index
]
11 local pmx
,pmy
= App
.mouse_x(), App
.mouse_y()
12 if pmx
< State
.right
and pmy
> line_cache
.starty
and pmy
< line_cache
.starty
+Drawing
.pixels(line
.h
, State
.width
) then
14 love
.graphics
.rectangle('line', State
.left
,line_cache
.starty
, State
.width
,Drawing
.pixels(line
.h
, State
.width
))
15 if icon
[State
.current_drawing_mode
] then
16 icon
[State
.current_drawing_mode
](State
.right
-22, line_cache
.starty
+4)
18 icon
[State
.previous_drawing_mode
](State
.right
-22, line_cache
.starty
+4)
21 if App
.mouse_down(1) and love
.keyboard
.isDown('h') then
22 draw_help_with_mouse_pressed(State
, line_index
)
27 if line
.show_help
then
28 draw_help_without_mouse_pressed(State
, line_index
)
32 local mx
= Drawing
.coord(pmx
-State
.left
, State
.width
)
33 local my
= Drawing
.coord(pmy
-line_cache
.starty
, State
.width
)
35 for _
,shape
in ipairs(line
.shapes
) do
37 if geom
.on_shape(mx
,my
, line
, shape
) then
38 App
.color(Focus_stroke_color
)
40 App
.color(Stroke_color
)
42 Drawing
.draw_shape(line
, shape
, line_cache
.starty
, State
.left
,State
.right
)
45 local function px(x
) return Drawing
.pixels(x
, State
.width
)+State
.left
end
46 local function py(y
) return Drawing
.pixels(y
, State
.width
)+line_cache
.starty
end
47 for i
,p
in ipairs(line
.points
) do
48 if p
.deleted
== nil then
49 if Drawing
.near(p
, mx
,my
, State
.width
) then
50 App
.color(Focus_stroke_color
)
51 love
.graphics
.circle('line', px(p
.x
),py(p
.y
), Same_point_distance
)
53 App
.color(Stroke_color
)
54 love
.graphics
.circle('fill', px(p
.x
),py(p
.y
), 2)
58 local x
,y
= px(p
.x
)+5, py(p
.y
)+5
59 love
.graphics
.print(p
.name
, x
,y
)
60 if State
.current_drawing_mode
== 'name' and i
== line
.pending
.target_point
then
61 -- create a faint red box for the name
62 App
.color(Current_name_background_color
)
65 name_width
= App
.width('m')
67 name_width
= App
.width(p
.name
)
69 love
.graphics
.rectangle('fill', x
,y
, name_width
, State
.line_height
)
74 App
.color(Current_stroke_color
)
75 Drawing
.draw_pending_shape(line
, line_cache
.starty
, State
.left
,State
.right
)
78 function Drawing
.draw_shape(drawing
, shape
, top
, left
,right
)
79 local width
= right
-left
80 local function px(x
) return Drawing
.pixels(x
, width
)+left
end
81 local function py(y
) return Drawing
.pixels(y
, width
)+top
end
82 if shape
.mode
== 'freehand' then
84 for _
,point
in ipairs(shape
.points
) do
86 love
.graphics
.line(px(prev
.x
),py(prev
.y
), px(point
.x
),py(point
.y
))
90 elseif shape
.mode
== 'line' or shape
.mode
== 'manhattan' then
91 local p1
= drawing
.points
[shape
.p1
]
92 local p2
= drawing
.points
[shape
.p2
]
93 love
.graphics
.line(px(p1
.x
),py(p1
.y
), px(p2
.x
),py(p2
.y
))
94 elseif shape
.mode
== 'polygon' or shape
.mode
== 'rectangle' or shape
.mode
== 'square' then
96 for _
,point
in ipairs(shape
.vertices
) do
97 local curr
= drawing
.points
[point
]
99 love
.graphics
.line(px(prev
.x
),py(prev
.y
), px(curr
.x
),py(curr
.y
))
104 local curr
= drawing
.points
[shape
.vertices
[1]]
105 love
.graphics
.line(px(prev
.x
),py(prev
.y
), px(curr
.x
),py(curr
.y
))
106 elseif shape
.mode
== 'circle' then
108 local center
= drawing
.points
[shape
.center
]
109 love
.graphics
.circle('line', px(center
.x
),py(center
.y
), Drawing
.pixels(shape
.radius
, width
))
110 elseif shape
.mode
== 'arc' then
111 local center
= drawing
.points
[shape
.center
]
112 love
.graphics
.arc('line', 'open', px(center
.x
),py(center
.y
), Drawing
.pixels(shape
.radius
, width
), shape
.start_angle
, shape
.end_angle
, 360)
113 elseif shape
.mode
== 'deleted' then
121 function Drawing
.draw_pending_shape(drawing
, top
, left
,right
)
122 local width
= right
-left
123 local pmx
,pmy
= App
.mouse_x(), App
.mouse_y()
124 local function px(x
) return Drawing
.pixels(x
, width
)+left
end
125 local function py(y
) return Drawing
.pixels(y
, width
)+top
end
126 local mx
= Drawing
.coord(pmx
-left
, width
)
127 local my
= Drawing
.coord(pmy
-top
, width
)
128 -- recreate pixels from coords to precisely mimic how the drawing will look
129 -- after mouse_release
130 pmx
,pmy
= px(mx
), py(my
)
131 local shape
= drawing
.pending
132 if shape
.mode
== nil then
134 elseif shape
.mode
== 'freehand' then
135 local shape_copy
= deepcopy(shape
)
136 Drawing
.smoothen(shape_copy
)
137 Drawing
.draw_shape(drawing
, shape_copy
, top
, left
,right
)
138 elseif shape
.mode
== 'line' then
139 if mx
< 0 or mx
>= 256 or my
< 0 or my
>= drawing
.h
then
142 local p1
= drawing
.points
[shape
.p1
]
143 love
.graphics
.line(px(p1
.x
),py(p1
.y
), pmx
,pmy
)
144 elseif shape
.mode
== 'manhattan' then
145 if mx
< 0 or mx
>= 256 or my
< 0 or my
>= drawing
.h
then
148 local p1
= drawing
.points
[shape
.p1
]
149 if math
.abs(mx
-p1
.x
) > math
.abs(my
-p1
.y
) then
150 love
.graphics
.line(px(p1
.x
),py(p1
.y
), pmx
, py(p1
.y
))
152 love
.graphics
.line(px(p1
.x
),py(p1
.y
), px(p1
.x
),pmy
)
154 elseif shape
.mode
== 'polygon' then
155 -- don't close the loop on a pending polygon
157 for _
,point
in ipairs(shape
.vertices
) do
158 local curr
= drawing
.points
[point
]
160 love
.graphics
.line(px(prev
.x
),py(prev
.y
), px(curr
.x
),py(curr
.y
))
164 love
.graphics
.line(px(prev
.x
),py(prev
.y
), pmx
,pmy
)
165 elseif shape
.mode
== 'rectangle' then
166 local first
= drawing
.points
[shape
.vertices
[1]]
167 if #shape
.vertices
== 1 then
168 love
.graphics
.line(px(first
.x
),py(first
.y
), pmx
,pmy
)
171 local second
= drawing
.points
[shape
.vertices
[2]]
172 local thirdx
,thirdy
, fourthx
,fourthy
= Drawing
.complete_rectangle(first
.x
,first
.y
, second
.x
,second
.y
, mx
,my
)
173 love
.graphics
.line(px(first
.x
),py(first
.y
), px(second
.x
),py(second
.y
))
174 love
.graphics
.line(px(second
.x
),py(second
.y
), px(thirdx
),py(thirdy
))
175 love
.graphics
.line(px(thirdx
),py(thirdy
), px(fourthx
),py(fourthy
))
176 love
.graphics
.line(px(fourthx
),py(fourthy
), px(first
.x
),py(first
.y
))
177 elseif shape
.mode
== 'square' then
178 local first
= drawing
.points
[shape
.vertices
[1]]
179 if #shape
.vertices
== 1 then
180 love
.graphics
.line(px(first
.x
),py(first
.y
), pmx
,pmy
)
183 local second
= drawing
.points
[shape
.vertices
[2]]
184 local thirdx
,thirdy
, fourthx
,fourthy
= Drawing
.complete_square(first
.x
,first
.y
, second
.x
,second
.y
, mx
,my
)
185 love
.graphics
.line(px(first
.x
),py(first
.y
), px(second
.x
),py(second
.y
))
186 love
.graphics
.line(px(second
.x
),py(second
.y
), px(thirdx
),py(thirdy
))
187 love
.graphics
.line(px(thirdx
),py(thirdy
), px(fourthx
),py(fourthy
))
188 love
.graphics
.line(px(fourthx
),py(fourthy
), px(first
.x
),py(first
.y
))
189 elseif shape
.mode
== 'circle' then
190 local center
= drawing
.points
[shape
.center
]
191 if mx
< 0 or mx
>= 256 or my
< 0 or my
>= drawing
.h
then
194 local cx
,cy
= px(center
.x
), py(center
.y
)
195 love
.graphics
.circle('line', cx
,cy
, geom
.dist(cx
,cy
, App
.mouse_x(),App
.mouse_y()))
196 elseif shape
.mode
== 'arc' then
197 local center
= drawing
.points
[shape
.center
]
198 if mx
< 0 or mx
>= 256 or my
< 0 or my
>= drawing
.h
then
201 shape
.end_angle
= geom
.angle_with_hint(center
.x
,center
.y
, mx
,my
, shape
.end_angle
)
202 local cx
,cy
= px(center
.x
), py(center
.y
)
203 love
.graphics
.arc('line', 'open', cx
,cy
, Drawing
.pixels(shape
.radius
, width
), shape
.start_angle
, shape
.end_angle
, 360)
204 elseif shape
.mode
== 'move' then
205 -- nothing pending; changes are immediately committed
206 elseif shape
.mode
== 'name' then
207 -- nothing pending; changes are immediately committed
214 function Drawing
.in_drawing(drawing
, line_cache
, x
,y
, left
,right
)
215 if line_cache
.starty
== nil then return false end -- outside current page
216 local width
= right
-left
217 return y
>= line_cache
.starty
and y
< line_cache
.starty
+ Drawing
.pixels(drawing
.h
, width
) and x
>= left
and x
< right
220 function Drawing
.mouse_press(State
, drawing_index
, x
,y
, mouse_button
)
221 local drawing
= State
.lines
[drawing_index
]
222 local line_cache
= State
.line_cache
[drawing_index
]
223 local cx
= Drawing
.coord(x
-State
.left
, State
.width
)
224 local cy
= Drawing
.coord(y
-line_cache
.starty
, State
.width
)
225 if State
.current_drawing_mode
== 'freehand' then
226 drawing
.pending
= {mode
=State
.current_drawing_mode
, points
={{x
=cx
, y
=cy
}}}
227 elseif State
.current_drawing_mode
== 'line' or State
.current_drawing_mode
== 'manhattan' then
228 local j
= Drawing
.find_or_insert_point(drawing
.points
, cx
, cy
, State
.width
)
229 drawing
.pending
= {mode
=State
.current_drawing_mode
, p1
=j
}
230 elseif State
.current_drawing_mode
== 'polygon' or State
.current_drawing_mode
== 'rectangle' or State
.current_drawing_mode
== 'square' then
231 local j
= Drawing
.find_or_insert_point(drawing
.points
, cx
, cy
, State
.width
)
232 drawing
.pending
= {mode
=State
.current_drawing_mode
, vertices
={j
}}
233 elseif State
.current_drawing_mode
== 'circle' then
234 local j
= Drawing
.find_or_insert_point(drawing
.points
, cx
, cy
, State
.width
)
235 drawing
.pending
= {mode
=State
.current_drawing_mode
, center
=j
}
236 elseif State
.current_drawing_mode
== 'move' then
237 -- all the action is in mouse_release
238 elseif State
.current_drawing_mode
== 'name' then
241 print(State
.current_drawing_mode
)
246 -- a couple of operations on drawings need to constantly check the state of the mouse
247 function Drawing
.update(State
)
248 if State
.lines
.current_drawing
== nil then return end
249 local drawing
= State
.lines
.current_drawing
250 local line_cache
= State
.line_cache
[State
.lines
.current_drawing_index
]
251 assert(drawing
.mode
== 'drawing')
252 local pmx
, pmy
= App
.mouse_x(), App
.mouse_y()
253 local mx
= Drawing
.coord(pmx
-State
.left
, State
.width
)
254 local my
= Drawing
.coord(pmy
-line_cache
.starty
, State
.width
)
255 if App
.mouse_down(1) then
256 if Drawing
.in_drawing(drawing
, line_cache
, pmx
,pmy
, State
.left
,State
.right
) then
257 if drawing
.pending
.mode
== 'freehand' then
258 table.insert(drawing
.pending
.points
, {x
=mx
, y
=my
})
259 elseif drawing
.pending
.mode
== 'move' then
260 drawing
.pending
.target_point
.x
= mx
261 drawing
.pending
.target_point
.y
= my
262 Drawing
.relax_constraints(drawing
, drawing
.pending
.target_point_index
)
265 elseif State
.current_drawing_mode
== 'move' then
266 if Drawing
.in_drawing(drawing
, line_cache
, pmx
, pmy
, State
.left
,State
.right
) then
267 drawing
.pending
.target_point
.x
= mx
268 drawing
.pending
.target_point
.y
= my
269 Drawing
.relax_constraints(drawing
, drawing
.pending
.target_point_index
)
276 function Drawing
.relax_constraints(drawing
, p
)
277 for _
,shape
in ipairs(drawing
.shapes
) do
278 if shape
.mode
== 'manhattan' then
279 if shape
.p1
== p
then
281 elseif shape
.p2
== p
then
284 elseif shape
.mode
== 'rectangle' or shape
.mode
== 'square' then
285 for _
,v
in ipairs(shape
.vertices
) do
287 shape
.mode
= 'polygon'
294 function Drawing
.mouse_release(State
, x
,y
, mouse_button
)
295 if State
.current_drawing_mode
== 'move' then
296 State
.current_drawing_mode
= State
.previous_drawing_mode
297 State
.previous_drawing_mode
= nil
298 if State
.lines
.current_drawing
then
299 State
.lines
.current_drawing
.pending
= {}
300 State
.lines
.current_drawing
= nil
302 elseif State
.lines
.current_drawing
then
303 local drawing
= State
.lines
.current_drawing
304 local line_cache
= State
.line_cache
[State
.lines
.current_drawing_index
]
305 if drawing
.pending
then
306 if drawing
.pending
.mode
== nil then
308 elseif drawing
.pending
.mode
== 'freehand' then
309 -- the last point added during update is good enough
310 Drawing
.smoothen(drawing
.pending
)
311 table.insert(drawing
.shapes
, drawing
.pending
)
312 elseif drawing
.pending
.mode
== 'line' then
313 local mx
,my
= Drawing
.coord(x
-State
.left
, State
.width
), Drawing
.coord(y
-line_cache
.starty
, State
.width
)
314 if mx
>= 0 and mx
< 256 and my
>= 0 and my
< drawing
.h
then
315 drawing
.pending
.p2
= Drawing
.find_or_insert_point(drawing
.points
, mx
,my
, State
.width
)
316 table.insert(drawing
.shapes
, drawing
.pending
)
318 elseif drawing
.pending
.mode
== 'manhattan' then
319 local p1
= drawing
.points
[drawing
.pending
.p1
]
320 local mx
,my
= Drawing
.coord(x
-State
.left
, State
.width
), Drawing
.coord(y
-line_cache
.starty
, State
.width
)
321 if mx
>= 0 and mx
< 256 and my
>= 0 and my
< drawing
.h
then
322 if math
.abs(mx
-p1
.x
) > math
.abs(my
-p1
.y
) then
323 drawing
.pending
.p2
= Drawing
.find_or_insert_point(drawing
.points
, mx
, p1
.y
, State
.width
)
325 drawing
.pending
.p2
= Drawing
.find_or_insert_point(drawing
.points
, p1
.x
, my
, State
.width
)
327 local p2
= drawing
.points
[drawing
.pending
.p2
]
328 App
.mouse_move(State
.left
+Drawing
.pixels(p2
.x
, State
.width
), line_cache
.starty
+Drawing
.pixels(p2
.y
, State
.width
))
329 table.insert(drawing
.shapes
, drawing
.pending
)
331 elseif drawing
.pending
.mode
== 'polygon' then
332 local mx
,my
= Drawing
.coord(x
-State
.left
, State
.width
), Drawing
.coord(y
-line_cache
.starty
, State
.width
)
333 if mx
>= 0 and mx
< 256 and my
>= 0 and my
< drawing
.h
then
334 table.insert(drawing
.pending
.vertices
, Drawing
.find_or_insert_point(drawing
.points
, mx
,my
, State
.width
))
335 table.insert(drawing
.shapes
, drawing
.pending
)
337 elseif drawing
.pending
.mode
== 'rectangle' then
338 assert(#drawing
.pending
.vertices
<= 2)
339 if #drawing
.pending
.vertices
== 2 then
340 local mx
,my
= Drawing
.coord(x
-State
.left
, State
.width
), Drawing
.coord(y
-line_cache
.starty
, State
.width
)
341 if mx
>= 0 and mx
< 256 and my
>= 0 and my
< drawing
.h
then
342 local first
= drawing
.points
[drawing
.pending
.vertices
[1]]
343 local second
= drawing
.points
[drawing
.pending
.vertices
[2]]
344 local thirdx
,thirdy
, fourthx
,fourthy
= Drawing
.complete_rectangle(first
.x
,first
.y
, second
.x
,second
.y
, mx
,my
)
345 table.insert(drawing
.pending
.vertices
, Drawing
.find_or_insert_point(drawing
.points
, thirdx
,thirdy
, State
.width
))
346 table.insert(drawing
.pending
.vertices
, Drawing
.find_or_insert_point(drawing
.points
, fourthx
,fourthy
, State
.width
))
347 table.insert(drawing
.shapes
, drawing
.pending
)
350 -- too few points; draw nothing
352 elseif drawing
.pending
.mode
== 'square' then
353 assert(#drawing
.pending
.vertices
<= 2)
354 if #drawing
.pending
.vertices
== 2 then
355 local mx
,my
= Drawing
.coord(x
-State
.left
, State
.width
), Drawing
.coord(y
-line_cache
.starty
, State
.width
)
356 if mx
>= 0 and mx
< 256 and my
>= 0 and my
< drawing
.h
then
357 local first
= drawing
.points
[drawing
.pending
.vertices
[1]]
358 local second
= drawing
.points
[drawing
.pending
.vertices
[2]]
359 local thirdx
,thirdy
, fourthx
,fourthy
= Drawing
.complete_square(first
.x
,first
.y
, second
.x
,second
.y
, mx
,my
)
360 table.insert(drawing
.pending
.vertices
, Drawing
.find_or_insert_point(drawing
.points
, thirdx
,thirdy
, State
.width
))
361 table.insert(drawing
.pending
.vertices
, Drawing
.find_or_insert_point(drawing
.points
, fourthx
,fourthy
, State
.width
))
362 table.insert(drawing
.shapes
, drawing
.pending
)
365 elseif drawing
.pending
.mode
== 'circle' then
366 local mx
,my
= Drawing
.coord(x
-State
.left
, State
.width
), Drawing
.coord(y
-line_cache
.starty
, State
.width
)
367 if mx
>= 0 and mx
< 256 and my
>= 0 and my
< drawing
.h
then
368 local center
= drawing
.points
[drawing
.pending
.center
]
369 drawing
.pending
.radius
= round(geom
.dist(center
.x
,center
.y
, mx
,my
))
370 table.insert(drawing
.shapes
, drawing
.pending
)
372 elseif drawing
.pending
.mode
== 'arc' then
373 local mx
,my
= Drawing
.coord(x
-State
.left
, State
.width
), Drawing
.coord(y
-line_cache
.starty
, State
.width
)
374 if mx
>= 0 and mx
< 256 and my
>= 0 and my
< drawing
.h
then
375 local center
= drawing
.points
[drawing
.pending
.center
]
376 drawing
.pending
.end_angle
= geom
.angle_with_hint(center
.x
,center
.y
, mx
,my
, drawing
.pending
.end_angle
)
377 table.insert(drawing
.shapes
, drawing
.pending
)
379 elseif drawing
.pending
.mode
== 'name' then
382 print(drawing
.pending
.mode
)
385 State
.lines
.current_drawing
.pending
= {}
386 State
.lines
.current_drawing
= nil
391 function Drawing
.keychord_press(State
, chord
)
392 if chord
== 'C-p' and not App
.mouse_down(1) then
393 State
.current_drawing_mode
= 'freehand'
394 elseif App
.mouse_down(1) and chord
== 'l' then
395 State
.current_drawing_mode
= 'line'
396 local _
,drawing
= Drawing
.current_drawing(State
)
397 if drawing
.pending
.mode
== 'freehand' then
398 drawing
.pending
.p1
= Drawing
.find_or_insert_point(drawing
.points
, drawing
.pending
.points
[1].x
, drawing
.pending
.points
[1].y
, State
.width
)
399 elseif drawing
.pending
.mode
== 'polygon' or drawing
.pending
.mode
== 'rectangle' or drawing
.pending
.mode
== 'square' then
400 drawing
.pending
.p1
= drawing
.pending
.vertices
[1]
401 elseif drawing
.pending
.mode
== 'circle' or drawing
.pending
.mode
== 'arc' then
402 drawing
.pending
.p1
= drawing
.pending
.center
404 drawing
.pending
.mode
= 'line'
405 elseif chord
== 'C-l' and not App
.mouse_down(1) then
406 State
.current_drawing_mode
= 'line'
407 elseif App
.mouse_down(1) and chord
== 'm' then
408 State
.current_drawing_mode
= 'manhattan'
409 local drawing
= Drawing
.select_drawing_at_mouse(State
)
410 if drawing
.pending
.mode
== 'freehand' then
411 drawing
.pending
.p1
= Drawing
.find_or_insert_point(drawing
.points
, drawing
.pending
.points
[1].x
, drawing
.pending
.points
[1].y
, State
.width
)
412 elseif drawing
.pending
.mode
== 'line' then
414 elseif drawing
.pending
.mode
== 'polygon' or drawing
.pending
.mode
== 'rectangle' or drawing
.pending
.mode
== 'square' then
415 drawing
.pending
.p1
= drawing
.pending
.vertices
[1]
416 elseif drawing
.pending
.mode
== 'circle' or drawing
.pending
.mode
== 'arc' then
417 drawing
.pending
.p1
= drawing
.pending
.center
419 drawing
.pending
.mode
= 'manhattan'
420 elseif chord
== 'C-m' and not App
.mouse_down(1) then
421 State
.current_drawing_mode
= 'manhattan'
422 elseif chord
== 'C-g' and not App
.mouse_down(1) then
423 State
.current_drawing_mode
= 'polygon'
424 elseif App
.mouse_down(1) and chord
== 'g' then
425 State
.current_drawing_mode
= 'polygon'
426 local _
,drawing
= Drawing
.current_drawing(State
)
427 if drawing
.pending
.mode
== 'freehand' then
428 drawing
.pending
.vertices
= {Drawing
.find_or_insert_point(drawing
.points
, drawing
.pending
.points
[1].x
, drawing
.pending
.points
[1].y
, State
.width
)}
429 elseif drawing
.pending
.mode
== 'line' or drawing
.pending
.mode
== 'manhattan' then
430 if drawing
.pending
.vertices
== nil then
431 drawing
.pending
.vertices
= {drawing
.pending
.p1
}
433 elseif drawing
.pending
.mode
== 'rectangle' or drawing
.pending
.mode
== 'square' then
434 -- reuse existing vertices
435 elseif drawing
.pending
.mode
== 'circle' or drawing
.pending
.mode
== 'arc' then
436 drawing
.pending
.vertices
= {drawing
.pending
.center
}
438 drawing
.pending
.mode
= 'polygon'
439 elseif chord
== 'C-r' and not App
.mouse_down(1) then
440 State
.current_drawing_mode
= 'rectangle'
441 elseif App
.mouse_down(1) and chord
== 'r' then
442 State
.current_drawing_mode
= 'rectangle'
443 local _
,drawing
= Drawing
.current_drawing(State
)
444 if drawing
.pending
.mode
== 'freehand' then
445 drawing
.pending
.vertices
= {Drawing
.find_or_insert_point(drawing
.points
, drawing
.pending
.points
[1].x
, drawing
.pending
.points
[1].y
, State
.width
)}
446 elseif drawing
.pending
.mode
== 'line' or drawing
.pending
.mode
== 'manhattan' then
447 if drawing
.pending
.vertices
== nil then
448 drawing
.pending
.vertices
= {drawing
.pending
.p1
}
450 elseif drawing
.pending
.mode
== 'polygon' or drawing
.pending
.mode
== 'square' then
451 -- reuse existing (1-2) vertices
452 elseif drawing
.pending
.mode
== 'circle' or drawing
.pending
.mode
== 'arc' then
453 drawing
.pending
.vertices
= {drawing
.pending
.center
}
455 drawing
.pending
.mode
= 'rectangle'
456 elseif chord
== 'C-s' and not App
.mouse_down(1) then
457 State
.current_drawing_mode
= 'square'
458 elseif App
.mouse_down(1) and chord
== 's' then
459 State
.current_drawing_mode
= 'square'
460 local _
,drawing
= Drawing
.current_drawing(State
)
461 if drawing
.pending
.mode
== 'freehand' then
462 drawing
.pending
.vertices
= {Drawing
.find_or_insert_point(drawing
.points
, drawing
.pending
.points
[1].x
, drawing
.pending
.points
[1].y
, State
.width
)}
463 elseif drawing
.pending
.mode
== 'line' or drawing
.pending
.mode
== 'manhattan' then
464 if drawing
.pending
.vertices
== nil then
465 drawing
.pending
.vertices
= {drawing
.pending
.p1
}
467 elseif drawing
.pending
.mode
== 'polygon' then
468 while #drawing
.pending
.vertices
> 2 do
469 table.remove(drawing
.pending
.vertices
)
471 elseif drawing
.pending
.mode
== 'rectangle' then
472 -- reuse existing (1-2) vertices
473 elseif drawing
.pending
.mode
== 'circle' or drawing
.pending
.mode
== 'arc' then
474 drawing
.pending
.vertices
= {drawing
.pending
.center
}
476 drawing
.pending
.mode
= 'square'
477 elseif App
.mouse_down(1) and chord
== 'p' and State
.current_drawing_mode
== 'polygon' then
478 local _
,drawing
,line_cache
= Drawing
.current_drawing(State
)
479 local mx
,my
= Drawing
.coord(App
.mouse_x()-State
.left
, State
.width
), Drawing
.coord(App
.mouse_y()-line_cache
.starty
, State
.width
)
480 local j
= Drawing
.find_or_insert_point(drawing
.points
, mx
,my
, State
.width
)
481 table.insert(drawing
.pending
.vertices
, j
)
482 elseif App
.mouse_down(1) and chord
== 'p' and (State
.current_drawing_mode
== 'rectangle' or State
.current_drawing_mode
== 'square') then
483 local _
,drawing
,line_cache
= Drawing
.current_drawing(State
)
484 local mx
,my
= Drawing
.coord(App
.mouse_x()-State
.left
, State
.width
), Drawing
.coord(App
.mouse_y()-line_cache
.starty
, State
.width
)
485 local j
= Drawing
.find_or_insert_point(drawing
.points
, mx
,my
, State
.width
)
486 while #drawing
.pending
.vertices
>= 2 do
487 table.remove(drawing
.pending
.vertices
)
489 table.insert(drawing
.pending
.vertices
, j
)
490 elseif chord
== 'C-o' and not App
.mouse_down(1) then
491 State
.current_drawing_mode
= 'circle'
492 elseif App
.mouse_down(1) and chord
== 'a' and State
.current_drawing_mode
== 'circle' then
493 local _
,drawing
,line_cache
= Drawing
.current_drawing(State
)
494 drawing
.pending
.mode
= 'arc'
495 local mx
,my
= Drawing
.coord(App
.mouse_x()-State
.left
, State
.width
), Drawing
.coord(App
.mouse_y()-line_cache
.starty
, State
.width
)
496 local center
= drawing
.points
[drawing
.pending
.center
]
497 drawing
.pending
.radius
= round(geom
.dist(center
.x
,center
.y
, mx
,my
))
498 drawing
.pending
.start_angle
= geom
.angle(center
.x
,center
.y
, mx
,my
)
499 elseif App
.mouse_down(1) and chord
== 'o' then
500 State
.current_drawing_mode
= 'circle'
501 local _
,drawing
= Drawing
.current_drawing(State
)
502 if drawing
.pending
.mode
== 'freehand' then
503 drawing
.pending
.center
= Drawing
.find_or_insert_point(drawing
.points
, drawing
.pending
.points
[1].x
, drawing
.pending
.points
[1].y
, State
.width
)
504 elseif drawing
.pending
.mode
== 'line' or drawing
.pending
.mode
== 'manhattan' then
505 drawing
.pending
.center
= drawing
.pending
.p1
506 elseif drawing
.pending
.mode
== 'polygon' or drawing
.pending
.mode
== 'rectangle' or drawing
.pending
.mode
== 'square' then
507 drawing
.pending
.center
= drawing
.pending
.vertices
[1]
509 drawing
.pending
.mode
= 'circle'
510 elseif chord
== 'C-u' and not App
.mouse_down(1) then
511 local drawing_index
,drawing
,line_cache
,i
,p
= Drawing
.select_point_at_mouse(State
)
513 if State
.previous_drawing_mode
== nil then
514 State
.previous_drawing_mode
= State
.current_drawing_mode
516 State
.current_drawing_mode
= 'move'
517 drawing
.pending
= {mode
=State
.current_drawing_mode
, target_point
=p
, target_point_index
=i
}
518 State
.lines
.current_drawing_index
= drawing_index
519 State
.lines
.current_drawing
= drawing
521 elseif chord
== 'C-n' and not App
.mouse_down(1) then
522 local drawing_index
,drawing
,line_cache
,point_index
,p
= Drawing
.select_point_at_mouse(State
)
524 if State
.previous_drawing_mode
== nil then
526 State
.previous_drawing_mode
= State
.current_drawing_mode
528 State
.current_drawing_mode
= 'name'
530 drawing
.pending
= {mode
=State
.current_drawing_mode
, target_point
=point_index
}
531 State
.lines
.current_drawing_index
= drawing_index
532 State
.lines
.current_drawing
= drawing
534 elseif chord
== 'C-d' and not App
.mouse_down(1) then
535 local _
,drawing
,_
,i
,p
= Drawing
.select_point_at_mouse(State
)
537 for _
,shape
in ipairs(drawing
.shapes
) do
538 if Drawing
.contains_point(shape
, i
) then
539 if shape
.mode
== 'polygon' then
540 local idx
= table.find(shape
.vertices
, i
)
542 table.remove(shape
.vertices
, idx
)
543 if #shape
.vertices
< 3 then
544 shape
.mode
= 'deleted'
547 shape
.mode
= 'deleted'
551 drawing
.points
[i
].deleted
= true
553 local drawing
,_
,_
,shape
= Drawing
.select_shape_at_mouse(State
)
555 shape
.mode
= 'deleted'
557 elseif chord
== 'C-h' and not App
.mouse_down(1) then
558 local drawing
= Drawing
.select_drawing_at_mouse(State
)
560 drawing
.show_help
= true
562 elseif chord
== 'escape' and App
.mouse_down(1) then
563 local _
,drawing
= Drawing
.current_drawing(State
)
568 function Drawing
.complete_rectangle(firstx
,firsty
, secondx
,secondy
, x
,y
)
569 if firstx
== secondx
then
570 return x
,secondy
, x
,firsty
572 if firsty
== secondy
then
573 return secondx
,y
, firstx
,y
575 local first_slope
= (secondy
-firsty
)/(secondx
-firstx
)
576 -- slope of second edge:
578 -- equation of line containing the second edge:
579 -- y-secondy = -1/first_slope*(x-secondx)
580 -- => 1/first_slope*x + y + (- secondy - secondx/first_slope) = 0
581 -- now we want to find the point on this line that's closest to the mouse pointer.
582 -- https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Line_defined_by_an_equation
583 local a
= 1/first_slope
584 local c
= -secondy
- secondx
/first_slope
585 local thirdx
= round(((x
-a
*y
) - a
*c
) / (a
*a
+ 1))
586 local thirdy
= round((a
*(-x
+ a
*y
) - c
) / (a
*a
+ 1))
587 -- slope of third edge = first_slope
588 -- equation of line containing third edge:
589 -- y - thirdy = first_slope*(x-thirdx)
590 -- => -first_slope*x + y + (-thirdy + thirdx*first_slope) = 0
591 -- now we want to find the point on this line that's closest to the first point
592 local a
= -first_slope
593 local c
= -thirdy
+ thirdx
*first_slope
594 local fourthx
= round(((firstx
-a
*firsty
) - a
*c
) / (a
*a
+ 1))
595 local fourthy
= round((a
*(-firstx
+ a
*firsty
) - c
) / (a
*a
+ 1))
596 return thirdx
,thirdy
, fourthx
,fourthy
599 function Drawing
.complete_square(firstx
,firsty
, secondx
,secondy
, x
,y
)
600 -- use x,y only to decide which side of the first edge to complete the square on
601 local deltax
= secondx
-firstx
602 local deltay
= secondy
-firsty
603 local thirdx
= secondx
+deltay
604 local thirdy
= secondy
-deltax
605 if not geom
.same_side(firstx
,firsty
, secondx
,secondy
, thirdx
,thirdy
, x
,y
) then
608 thirdx
= secondx
+deltay
609 thirdy
= secondy
-deltax
611 local fourthx
= firstx
+deltay
612 local fourthy
= firsty
-deltax
613 return thirdx
,thirdy
, fourthx
,fourthy
616 function Drawing
.current_drawing(State
)
617 local x
, y
= App
.mouse_x(), App
.mouse_y()
618 for drawing_index
,drawing
in ipairs(State
.lines
) do
619 if drawing
.mode
== 'drawing' then
620 local line_cache
= State
.line_cache
[drawing_index
]
621 if Drawing
.in_drawing(drawing
, line_cache
, x
,y
, State
.left
,State
.right
) then
622 return drawing_index
,drawing
,line_cache
629 function Drawing
.select_shape_at_mouse(State
)
630 for drawing_index
,drawing
in ipairs(State
.lines
) do
631 if drawing
.mode
== 'drawing' then
632 local x
, y
= App
.mouse_x(), App
.mouse_y()
633 local line_cache
= State
.line_cache
[drawing_index
]
634 if Drawing
.in_drawing(drawing
, line_cache
, x
,y
, State
.left
,State
.right
) then
635 local mx
,my
= Drawing
.coord(x
-State
.left
, State
.width
), Drawing
.coord(y
-line_cache
.starty
, State
.width
)
636 for i
,shape
in ipairs(drawing
.shapes
) do
638 if geom
.on_shape(mx
,my
, drawing
, shape
) then
639 return drawing
,line_cache
,i
,shape
647 function Drawing
.select_point_at_mouse(State
)
648 for drawing_index
,drawing
in ipairs(State
.lines
) do
649 if drawing
.mode
== 'drawing' then
650 local x
, y
= App
.mouse_x(), App
.mouse_y()
651 local line_cache
= State
.line_cache
[drawing_index
]
652 if Drawing
.in_drawing(drawing
, line_cache
, x
,y
, State
.left
,State
.right
) then
653 local mx
,my
= Drawing
.coord(x
-State
.left
, State
.width
), Drawing
.coord(y
-line_cache
.starty
, State
.width
)
654 for i
,point
in ipairs(drawing
.points
) do
656 if Drawing
.near(point
, mx
,my
, State
.width
) then
657 return drawing_index
,drawing
,line_cache
,i
,point
665 function Drawing
.select_drawing_at_mouse(State
)
666 for drawing_index
,drawing
in ipairs(State
.lines
) do
667 if drawing
.mode
== 'drawing' then
668 local x
, y
= App
.mouse_x(), App
.mouse_y()
669 local line_cache
= State
.line_cache
[drawing_index
]
670 if Drawing
.in_drawing(drawing
, line_cache
, x
,y
, State
.left
,State
.right
) then
677 function Drawing
.contains_point(shape
, p
)
678 if shape
.mode
== 'freehand' then
680 elseif shape
.mode
== 'line' or shape
.mode
== 'manhattan' then
681 return shape
.p1
== p
or shape
.p2
== p
682 elseif shape
.mode
== 'polygon' or shape
.mode
== 'rectangle' or shape
.mode
== 'square' then
683 return table.find(shape
.vertices
, p
)
684 elseif shape
.mode
== 'circle' then
685 return shape
.center
== p
686 elseif shape
.mode
== 'arc' then
687 return shape
.center
== p
688 -- ugh, how to support angles
689 elseif shape
.mode
== 'deleted' then
697 function Drawing
.smoothen(shape
)
698 assert(shape
.mode
== 'freehand')
700 for i
=2,#shape
.points
-1 do
701 local a
= shape
.points
[i
-1]
702 local b
= shape
.points
[i
]
703 local c
= shape
.points
[i
+1]
704 b
.x
= round((a
.x
+ b
.x
+ c
.x
)/3)
705 b
.y
= round((a
.y
+ b
.y
+ c
.y
)/3)
711 return math
.floor(num
+.5)
714 function Drawing
.find_or_insert_point(points
, x
,y
, width
)
715 -- check if UI would snap the two points together
716 for i
,point
in ipairs(points
) do
717 if Drawing
.near(point
, x
,y
, width
) then
721 table.insert(points
, {x
=x
, y
=y
})
725 function Drawing
.near(point
, x
,y
, width
)
726 local px
,py
= Drawing
.pixels(x
, width
),Drawing
.pixels(y
, width
)
727 local cx
,cy
= Drawing
.pixels(point
.x
, width
), Drawing
.pixels(point
.y
, width
)
728 return (cx
-px
)*(cx
-px
) + (cy
-py
)*(cy
-py
) < Same_point_distance
*Same_point_distance
731 function Drawing
.pixels(n
, width
) -- parts to pixels
732 return math
.floor(n
*width
/256)
734 function Drawing
.coord(n
, width
) -- pixels to parts
735 return math
.floor(n
*256/width
)
738 function table.find(h
, x
)
739 for k
,v
in pairs(h
) do