new fork: rip out drawing support
This commit is contained in:
parent
974d17ffc0
commit
9d792a203b
51
README.md
51
README.md
|
@ -1,11 +1,12 @@
|
|||
# Plain text with lines
|
||||
# An editor for plain text.
|
||||
|
||||
Not very useful by itself, but it's a fork of [lines.love](http://akkartik.name/lines.html)
|
||||
that you can take in other directions besides line drawings, while easily
|
||||
sharing patches between forks.
|
||||
|
||||
An editor for plain text where you can also seamlessly insert line drawings.
|
||||
Designed above all to be easy to modify and give you early warning if your
|
||||
modifications break something.
|
||||
|
||||
http://akkartik.name/lines.html
|
||||
|
||||
## Invocation
|
||||
|
||||
To run from the terminal, [pass this directory to LÖVE](https://love2d.org/wiki/Getting_Started#Running_Games),
|
||||
|
@ -13,13 +14,13 @@ optionally with a file path to edit.
|
|||
|
||||
Alternatively, turn it into a .love file you can double-click on:
|
||||
```
|
||||
$ zip -r /tmp/lines.love *.lua
|
||||
$ zip -r /tmp/text.love *.lua
|
||||
```
|
||||
|
||||
By default, lines.love reads/writes the file `lines.txt` in your default
|
||||
By default, it reads/writes the file `lines.txt` in your default
|
||||
user/home directory (`https://love2d.org/wiki/love.filesystem.getUserDirectory`).
|
||||
|
||||
To open a different file, drop it on the lines.love window.
|
||||
To open a different file, drop it on the app window.
|
||||
|
||||
## Keyboard shortcuts
|
||||
|
||||
|
@ -35,7 +36,7 @@ For shortcuts while editing drawings, consult the online help. Either:
|
|||
* click on a drawing to start a stroke and then press and hold `h` to see your
|
||||
options at any point during a stroke.
|
||||
|
||||
lines.love has been exclusively tested so far with a US keyboard layout. If
|
||||
Exclusively tested so far with a US keyboard layout. If
|
||||
you use a different layout, please let me know if things worked, or if you
|
||||
found anything amiss: http://akkartik.name/contact
|
||||
|
||||
|
@ -46,8 +47,7 @@ found anything amiss: http://akkartik.name/contact
|
|||
* No support yet for right-to-left languages.
|
||||
|
||||
* Undo/redo may be sluggish in large files. Large files may grow sluggish in
|
||||
other ways. lines.love works well in all circumstances with files under
|
||||
50KB.
|
||||
other ways. Works well in all circumstances with files under 50KB.
|
||||
|
||||
* If you kill the process, say by force-quitting because things things get
|
||||
sluggish, you can lose data.
|
||||
|
@ -80,28 +80,19 @@ found anything amiss: http://akkartik.name/contact
|
|||
|
||||
## Mirrors and Forks
|
||||
|
||||
Updates to lines.love can be downloaded from the following mirrors in addition
|
||||
to the website above:
|
||||
* https://github.com/akkartik/lines.love
|
||||
* https://repo.or.cz/lines.love.git
|
||||
* https://codeberg.org/akkartik/lines.love
|
||||
* https://tildegit.org/akkartik/lines.love
|
||||
* https://git.tilde.institute/akkartik/lines.love
|
||||
* https://git.sr.ht/~akkartik/lines.love
|
||||
* https://notabug.org/akkartik/lines.love
|
||||
* https://pagure.io/lines.love
|
||||
This repo is a fork of lines.love at [http://akkartik.name/lines.html](http://akkartik.name/lines.html).
|
||||
Updates to it can be downloaded from the following mirrors:
|
||||
|
||||
Forks of lines.love are encouraged. If you show me your fork, I'll link to it
|
||||
here.
|
||||
* https://codeberg.org/akkartik/text.love
|
||||
* https://repo.or.cz/text.love.git
|
||||
* https://tildegit.org/akkartik/text.love
|
||||
* https://git.tilde.institute/akkartik/text.love
|
||||
* https://git.sr.ht/~akkartik/text.love
|
||||
* https://notabug.org/akkartik/text.love
|
||||
* https://github.com/akkartik/text.love
|
||||
* https://pagure.io/text.love
|
||||
|
||||
* https://github.com/akkartik/lines-polygon-experiment -- an experiment that
|
||||
uses separate shortcuts for regular polygons. `ctrl+3` for triangles,
|
||||
`ctrl+4` for squares, etc.
|
||||
|
||||
## Associated tools
|
||||
|
||||
* https://codeberg.org/akkartik/lines2md exports lines.love files to Markdown
|
||||
and (non-editable) SVG.
|
||||
Further forks are encouraged. If you show me your fork, I'll link to it here.
|
||||
|
||||
## Feedback
|
||||
|
||||
|
|
750
drawing.lua
750
drawing.lua
|
@ -1,750 +0,0 @@
|
|||
-- primitives for editing drawings
|
||||
Drawing = {}
|
||||
require 'drawing_tests'
|
||||
|
||||
-- All drawings span 100% of some conceptual 'page width' and divide it up
|
||||
-- into 256 parts.
|
||||
function Drawing.draw(State, line_index, y)
|
||||
local line = State.lines[line_index]
|
||||
local line_cache = State.line_cache[line_index]
|
||||
line_cache.starty = y
|
||||
local pmx,pmy = App.mouse_x(), App.mouse_y()
|
||||
if pmx < State.right and pmy > line_cache.starty and pmy < line_cache.starty+Drawing.pixels(line.h, State.width) then
|
||||
App.color(Icon_color)
|
||||
love.graphics.rectangle('line', State.left,line_cache.starty, State.width,Drawing.pixels(line.h, State.width))
|
||||
if icon[State.current_drawing_mode] then
|
||||
icon[State.current_drawing_mode](State.right-22, line_cache.starty+4)
|
||||
else
|
||||
icon[State.previous_drawing_mode](State.right-22, line_cache.starty+4)
|
||||
end
|
||||
|
||||
if App.mouse_down(1) and love.keyboard.isDown('h') then
|
||||
draw_help_with_mouse_pressed(State, line_index)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if line.show_help then
|
||||
draw_help_without_mouse_pressed(State, line_index)
|
||||
return
|
||||
end
|
||||
|
||||
local mx = Drawing.coord(pmx-State.left, State.width)
|
||||
local my = Drawing.coord(pmy-line_cache.starty, State.width)
|
||||
|
||||
for _,shape in ipairs(line.shapes) do
|
||||
assert(shape)
|
||||
if geom.on_shape(mx,my, line, shape) then
|
||||
App.color(Focus_stroke_color)
|
||||
else
|
||||
App.color(Stroke_color)
|
||||
end
|
||||
Drawing.draw_shape(line, shape, line_cache.starty, State.left,State.right)
|
||||
end
|
||||
|
||||
local function px(x) return Drawing.pixels(x, State.width)+State.left end
|
||||
local function py(y) return Drawing.pixels(y, State.width)+line_cache.starty end
|
||||
for i,p in ipairs(line.points) do
|
||||
if p.deleted == nil then
|
||||
if Drawing.near(p, mx,my, State.width) then
|
||||
App.color(Focus_stroke_color)
|
||||
love.graphics.circle('line', px(p.x),py(p.y), Same_point_distance)
|
||||
else
|
||||
App.color(Stroke_color)
|
||||
love.graphics.circle('fill', px(p.x),py(p.y), 2)
|
||||
end
|
||||
if p.name then
|
||||
-- TODO: clip
|
||||
local x,y = px(p.x)+5, py(p.y)+5
|
||||
love.graphics.print(p.name, x,y)
|
||||
if State.current_drawing_mode == 'name' and i == line.pending.target_point then
|
||||
-- create a faint red box for the name
|
||||
App.color(Current_name_background_color)
|
||||
local name_text
|
||||
-- TODO: avoid computing name width on every repaint
|
||||
if p.name == '' then
|
||||
name_text = State.em
|
||||
else
|
||||
name_text = App.newText(love.graphics.getFont(), p.name)
|
||||
end
|
||||
love.graphics.rectangle('fill', x,y, App.width(name_text), State.line_height)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
App.color(Current_stroke_color)
|
||||
Drawing.draw_pending_shape(line, line_cache.starty, State.left,State.right)
|
||||
end
|
||||
|
||||
function Drawing.draw_shape(drawing, shape, top, left,right)
|
||||
local width = right-left
|
||||
local function px(x) return Drawing.pixels(x, width)+left end
|
||||
local function py(y) return Drawing.pixels(y, width)+top end
|
||||
if shape.mode == 'freehand' then
|
||||
local prev = nil
|
||||
for _,point in ipairs(shape.points) do
|
||||
if prev then
|
||||
love.graphics.line(px(prev.x),py(prev.y), px(point.x),py(point.y))
|
||||
end
|
||||
prev = point
|
||||
end
|
||||
elseif shape.mode == 'line' or shape.mode == 'manhattan' then
|
||||
local p1 = drawing.points[shape.p1]
|
||||
local p2 = drawing.points[shape.p2]
|
||||
love.graphics.line(px(p1.x),py(p1.y), px(p2.x),py(p2.y))
|
||||
elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
|
||||
local prev = nil
|
||||
for _,point in ipairs(shape.vertices) do
|
||||
local curr = drawing.points[point]
|
||||
if prev then
|
||||
love.graphics.line(px(prev.x),py(prev.y), px(curr.x),py(curr.y))
|
||||
end
|
||||
prev = curr
|
||||
end
|
||||
-- close the loop
|
||||
local curr = drawing.points[shape.vertices[1]]
|
||||
love.graphics.line(px(prev.x),py(prev.y), px(curr.x),py(curr.y))
|
||||
elseif shape.mode == 'circle' then
|
||||
-- TODO: clip
|
||||
local center = drawing.points[shape.center]
|
||||
love.graphics.circle('line', px(center.x),py(center.y), Drawing.pixels(shape.radius, width))
|
||||
elseif shape.mode == 'arc' then
|
||||
local center = drawing.points[shape.center]
|
||||
love.graphics.arc('line', 'open', px(center.x),py(center.y), Drawing.pixels(shape.radius, width), shape.start_angle, shape.end_angle, 360)
|
||||
elseif shape.mode == 'deleted' then
|
||||
-- ignore
|
||||
else
|
||||
print(shape.mode)
|
||||
assert(false)
|
||||
end
|
||||
end
|
||||
|
||||
function Drawing.draw_pending_shape(drawing, top, left,right)
|
||||
local width = right-left
|
||||
local pmx,pmy = App.mouse_x(), App.mouse_y()
|
||||
local function px(x) return Drawing.pixels(x, width)+left end
|
||||
local function py(y) return Drawing.pixels(y, width)+top end
|
||||
local mx = Drawing.coord(pmx-left, width)
|
||||
local my = Drawing.coord(pmy-top, width)
|
||||
-- recreate pixels from coords to precisely mimic how the drawing will look
|
||||
-- after mouse_released
|
||||
pmx,pmy = px(mx), py(my)
|
||||
local shape = drawing.pending
|
||||
if shape.mode == nil then
|
||||
-- nothing pending
|
||||
elseif shape.mode == 'freehand' then
|
||||
local shape_copy = deepcopy(shape)
|
||||
Drawing.smoothen(shape_copy)
|
||||
Drawing.draw_shape(drawing, shape_copy, top, left,right)
|
||||
elseif shape.mode == 'line' then
|
||||
if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then
|
||||
return
|
||||
end
|
||||
local p1 = drawing.points[shape.p1]
|
||||
love.graphics.line(px(p1.x),py(p1.y), pmx,pmy)
|
||||
elseif shape.mode == 'manhattan' then
|
||||
if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then
|
||||
return
|
||||
end
|
||||
local p1 = drawing.points[shape.p1]
|
||||
if math.abs(mx-p1.x) > math.abs(my-p1.y) then
|
||||
love.graphics.line(px(p1.x),py(p1.y), pmx, py(p1.y))
|
||||
else
|
||||
love.graphics.line(px(p1.x),py(p1.y), px(p1.x),pmy)
|
||||
end
|
||||
elseif shape.mode == 'polygon' then
|
||||
-- don't close the loop on a pending polygon
|
||||
local prev = nil
|
||||
for _,point in ipairs(shape.vertices) do
|
||||
local curr = drawing.points[point]
|
||||
if prev then
|
||||
love.graphics.line(px(prev.x),py(prev.y), px(curr.x),py(curr.y))
|
||||
end
|
||||
prev = curr
|
||||
end
|
||||
love.graphics.line(px(prev.x),py(prev.y), pmx,pmy)
|
||||
elseif shape.mode == 'rectangle' then
|
||||
local first = drawing.points[shape.vertices[1]]
|
||||
if #shape.vertices == 1 then
|
||||
love.graphics.line(px(first.x),py(first.y), pmx,pmy)
|
||||
return
|
||||
end
|
||||
local second = drawing.points[shape.vertices[2]]
|
||||
local thirdx,thirdy, fourthx,fourthy = Drawing.complete_rectangle(first.x,first.y, second.x,second.y, mx,my)
|
||||
love.graphics.line(px(first.x),py(first.y), px(second.x),py(second.y))
|
||||
love.graphics.line(px(second.x),py(second.y), px(thirdx),py(thirdy))
|
||||
love.graphics.line(px(thirdx),py(thirdy), px(fourthx),py(fourthy))
|
||||
love.graphics.line(px(fourthx),py(fourthy), px(first.x),py(first.y))
|
||||
elseif shape.mode == 'square' then
|
||||
local first = drawing.points[shape.vertices[1]]
|
||||
if #shape.vertices == 1 then
|
||||
love.graphics.line(px(first.x),py(first.y), pmx,pmy)
|
||||
return
|
||||
end
|
||||
local second = drawing.points[shape.vertices[2]]
|
||||
local thirdx,thirdy, fourthx,fourthy = Drawing.complete_square(first.x,first.y, second.x,second.y, mx,my)
|
||||
love.graphics.line(px(first.x),py(first.y), px(second.x),py(second.y))
|
||||
love.graphics.line(px(second.x),py(second.y), px(thirdx),py(thirdy))
|
||||
love.graphics.line(px(thirdx),py(thirdy), px(fourthx),py(fourthy))
|
||||
love.graphics.line(px(fourthx),py(fourthy), px(first.x),py(first.y))
|
||||
elseif shape.mode == 'circle' then
|
||||
local center = drawing.points[shape.center]
|
||||
if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then
|
||||
return
|
||||
end
|
||||
local cx,cy = px(center.x), py(center.y)
|
||||
love.graphics.circle('line', cx,cy, geom.dist(cx,cy, App.mouse_x(),App.mouse_y()))
|
||||
elseif shape.mode == 'arc' then
|
||||
local center = drawing.points[shape.center]
|
||||
if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then
|
||||
return
|
||||
end
|
||||
shape.end_angle = geom.angle_with_hint(center.x,center.y, mx,my, shape.end_angle)
|
||||
local cx,cy = px(center.x), py(center.y)
|
||||
love.graphics.arc('line', 'open', cx,cy, Drawing.pixels(shape.radius, width), shape.start_angle, shape.end_angle, 360)
|
||||
elseif shape.mode == 'move' then
|
||||
-- nothing pending; changes are immediately committed
|
||||
elseif shape.mode == 'name' then
|
||||
-- nothing pending; changes are immediately committed
|
||||
else
|
||||
print(shape.mode)
|
||||
assert(false)
|
||||
end
|
||||
end
|
||||
|
||||
function Drawing.in_drawing(drawing, line_cache, x,y, left,right)
|
||||
if line_cache.starty == nil then return false end -- outside current page
|
||||
local width = right-left
|
||||
return y >= line_cache.starty and y < line_cache.starty + Drawing.pixels(drawing.h, width) and x >= left and x < right
|
||||
end
|
||||
|
||||
function Drawing.mouse_pressed(State, drawing_index, x,y, button)
|
||||
local drawing = State.lines[drawing_index]
|
||||
local line_cache = State.line_cache[drawing_index]
|
||||
local cx = Drawing.coord(x-State.left, State.width)
|
||||
local cy = Drawing.coord(y-line_cache.starty, State.width)
|
||||
if State.current_drawing_mode == 'freehand' then
|
||||
drawing.pending = {mode=State.current_drawing_mode, points={{x=cx, y=cy}}}
|
||||
elseif State.current_drawing_mode == 'line' or State.current_drawing_mode == 'manhattan' then
|
||||
local j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width)
|
||||
drawing.pending = {mode=State.current_drawing_mode, p1=j}
|
||||
elseif State.current_drawing_mode == 'polygon' or State.current_drawing_mode == 'rectangle' or State.current_drawing_mode == 'square' then
|
||||
local j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width)
|
||||
drawing.pending = {mode=State.current_drawing_mode, vertices={j}}
|
||||
elseif State.current_drawing_mode == 'circle' then
|
||||
local j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width)
|
||||
drawing.pending = {mode=State.current_drawing_mode, center=j}
|
||||
elseif State.current_drawing_mode == 'move' then
|
||||
-- all the action is in mouse_released
|
||||
elseif State.current_drawing_mode == 'name' then
|
||||
-- nothing
|
||||
else
|
||||
print(State.current_drawing_mode)
|
||||
assert(false)
|
||||
end
|
||||
end
|
||||
|
||||
-- a couple of operations on drawings need to constantly check the state of the mouse
|
||||
function Drawing.update(State)
|
||||
if State.lines.current_drawing == nil then return end
|
||||
local drawing = State.lines.current_drawing
|
||||
local line_cache = State.line_cache[State.lines.current_drawing_index]
|
||||
assert(drawing.mode == 'drawing')
|
||||
local pmx, pmy = App.mouse_x(), App.mouse_y()
|
||||
local mx = Drawing.coord(pmx-State.left, State.width)
|
||||
local my = Drawing.coord(pmy-line_cache.starty, State.width)
|
||||
if App.mouse_down(1) then
|
||||
if Drawing.in_drawing(drawing, line_cache, pmx,pmy, State.left,State.right) then
|
||||
if drawing.pending.mode == 'freehand' then
|
||||
table.insert(drawing.pending.points, {x=mx, y=my})
|
||||
elseif drawing.pending.mode == 'move' then
|
||||
drawing.pending.target_point.x = mx
|
||||
drawing.pending.target_point.y = my
|
||||
Drawing.relax_constraints(drawing, drawing.pending.target_point_index)
|
||||
end
|
||||
end
|
||||
elseif State.current_drawing_mode == 'move' then
|
||||
if Drawing.in_drawing(drawing, line_cache, pmx, pmy, State.left,State.right) then
|
||||
drawing.pending.target_point.x = mx
|
||||
drawing.pending.target_point.y = my
|
||||
Drawing.relax_constraints(drawing, drawing.pending.target_point_index)
|
||||
end
|
||||
else
|
||||
-- do nothing
|
||||
end
|
||||
end
|
||||
|
||||
function Drawing.relax_constraints(drawing, p)
|
||||
for _,shape in ipairs(drawing.shapes) do
|
||||
if shape.mode == 'manhattan' then
|
||||
if shape.p1 == p then
|
||||
shape.mode = 'line'
|
||||
elseif shape.p2 == p then
|
||||
shape.mode = 'line'
|
||||
end
|
||||
elseif shape.mode == 'rectangle' or shape.mode == 'square' then
|
||||
for _,v in ipairs(shape.vertices) do
|
||||
if v == p then
|
||||
shape.mode = 'polygon'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Drawing.mouse_released(State, x,y, button)
|
||||
if State.current_drawing_mode == 'move' then
|
||||
State.current_drawing_mode = State.previous_drawing_mode
|
||||
State.previous_drawing_mode = nil
|
||||
if State.lines.current_drawing then
|
||||
State.lines.current_drawing.pending = {}
|
||||
State.lines.current_drawing = nil
|
||||
end
|
||||
elseif State.lines.current_drawing then
|
||||
local drawing = State.lines.current_drawing
|
||||
local line_cache = State.line_cache[State.lines.current_drawing_index]
|
||||
if drawing.pending then
|
||||
if drawing.pending.mode == nil then
|
||||
-- nothing pending
|
||||
elseif drawing.pending.mode == 'freehand' then
|
||||
-- the last point added during update is good enough
|
||||
Drawing.smoothen(drawing.pending)
|
||||
table.insert(drawing.shapes, drawing.pending)
|
||||
elseif drawing.pending.mode == 'line' then
|
||||
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
|
||||
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
|
||||
drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
|
||||
table.insert(drawing.shapes, drawing.pending)
|
||||
end
|
||||
elseif drawing.pending.mode == 'manhattan' then
|
||||
local p1 = drawing.points[drawing.pending.p1]
|
||||
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
|
||||
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
|
||||
if math.abs(mx-p1.x) > math.abs(my-p1.y) then
|
||||
drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx, p1.y, State.width)
|
||||
else
|
||||
drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, p1.x, my, State.width)
|
||||
end
|
||||
local p2 = drawing.points[drawing.pending.p2]
|
||||
App.mouse_move(State.left+Drawing.pixels(p2.x, State.width), line_cache.starty+Drawing.pixels(p2.y, State.width))
|
||||
table.insert(drawing.shapes, drawing.pending)
|
||||
end
|
||||
elseif drawing.pending.mode == 'polygon' then
|
||||
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
|
||||
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
|
||||
table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, mx,my, State.width))
|
||||
table.insert(drawing.shapes, drawing.pending)
|
||||
end
|
||||
elseif drawing.pending.mode == 'rectangle' then
|
||||
assert(#drawing.pending.vertices <= 2)
|
||||
if #drawing.pending.vertices == 2 then
|
||||
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
|
||||
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
|
||||
local first = drawing.points[drawing.pending.vertices[1]]
|
||||
local second = drawing.points[drawing.pending.vertices[2]]
|
||||
local thirdx,thirdy, fourthx,fourthy = Drawing.complete_rectangle(first.x,first.y, second.x,second.y, mx,my)
|
||||
table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, thirdx,thirdy, State.width))
|
||||
table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, fourthx,fourthy, State.width))
|
||||
table.insert(drawing.shapes, drawing.pending)
|
||||
end
|
||||
else
|
||||
-- too few points; draw nothing
|
||||
end
|
||||
elseif drawing.pending.mode == 'square' then
|
||||
assert(#drawing.pending.vertices <= 2)
|
||||
if #drawing.pending.vertices == 2 then
|
||||
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
|
||||
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
|
||||
local first = drawing.points[drawing.pending.vertices[1]]
|
||||
local second = drawing.points[drawing.pending.vertices[2]]
|
||||
local thirdx,thirdy, fourthx,fourthy = Drawing.complete_square(first.x,first.y, second.x,second.y, mx,my)
|
||||
table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, thirdx,thirdy, State.width))
|
||||
table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, fourthx,fourthy, State.width))
|
||||
table.insert(drawing.shapes, drawing.pending)
|
||||
end
|
||||
end
|
||||
elseif drawing.pending.mode == 'circle' then
|
||||
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
|
||||
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
|
||||
local center = drawing.points[drawing.pending.center]
|
||||
drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my))
|
||||
table.insert(drawing.shapes, drawing.pending)
|
||||
end
|
||||
elseif drawing.pending.mode == 'arc' then
|
||||
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
|
||||
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
|
||||
local center = drawing.points[drawing.pending.center]
|
||||
drawing.pending.end_angle = geom.angle_with_hint(center.x,center.y, mx,my, drawing.pending.end_angle)
|
||||
table.insert(drawing.shapes, drawing.pending)
|
||||
end
|
||||
elseif drawing.pending.mode == 'name' then
|
||||
-- drop it
|
||||
else
|
||||
print(drawing.pending.mode)
|
||||
assert(false)
|
||||
end
|
||||
State.lines.current_drawing.pending = {}
|
||||
State.lines.current_drawing = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Drawing.keychord_pressed(State, chord)
|
||||
if chord == 'C-p' and not App.mouse_down(1) then
|
||||
State.current_drawing_mode = 'freehand'
|
||||
elseif App.mouse_down(1) and chord == 'l' then
|
||||
State.current_drawing_mode = 'line'
|
||||
local _,drawing = Drawing.current_drawing(State)
|
||||
if drawing.pending.mode == 'freehand' then
|
||||
drawing.pending.p1 = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)
|
||||
elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then
|
||||
drawing.pending.p1 = drawing.pending.vertices[1]
|
||||
elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
|
||||
drawing.pending.p1 = drawing.pending.center
|
||||
end
|
||||
drawing.pending.mode = 'line'
|
||||
elseif chord == 'C-l' and not App.mouse_down(1) then
|
||||
State.current_drawing_mode = 'line'
|
||||
elseif App.mouse_down(1) and chord == 'm' then
|
||||
State.current_drawing_mode = 'manhattan'
|
||||
local drawing = Drawing.select_drawing_at_mouse(State)
|
||||
if drawing.pending.mode == 'freehand' then
|
||||
drawing.pending.p1 = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)
|
||||
elseif drawing.pending.mode == 'line' then
|
||||
-- do nothing
|
||||
elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then
|
||||
drawing.pending.p1 = drawing.pending.vertices[1]
|
||||
elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
|
||||
drawing.pending.p1 = drawing.pending.center
|
||||
end
|
||||
drawing.pending.mode = 'manhattan'
|
||||
elseif chord == 'C-m' and not App.mouse_down(1) then
|
||||
State.current_drawing_mode = 'manhattan'
|
||||
elseif chord == 'C-g' and not App.mouse_down(1) then
|
||||
State.current_drawing_mode = 'polygon'
|
||||
elseif App.mouse_down(1) and chord == 'g' then
|
||||
State.current_drawing_mode = 'polygon'
|
||||
local _,drawing = Drawing.current_drawing(State)
|
||||
if drawing.pending.mode == 'freehand' then
|
||||
drawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)}
|
||||
elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
|
||||
if drawing.pending.vertices == nil then
|
||||
drawing.pending.vertices = {drawing.pending.p1}
|
||||
end
|
||||
elseif drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then
|
||||
-- reuse existing vertices
|
||||
elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
|
||||
drawing.pending.vertices = {drawing.pending.center}
|
||||
end
|
||||
drawing.pending.mode = 'polygon'
|
||||
elseif chord == 'C-r' and not App.mouse_down(1) then
|
||||
State.current_drawing_mode = 'rectangle'
|
||||
elseif App.mouse_down(1) and chord == 'r' then
|
||||
State.current_drawing_mode = 'rectangle'
|
||||
local _,drawing = Drawing.current_drawing(State)
|
||||
if drawing.pending.mode == 'freehand' then
|
||||
drawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)}
|
||||
elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
|
||||
if drawing.pending.vertices == nil then
|
||||
drawing.pending.vertices = {drawing.pending.p1}
|
||||
end
|
||||
elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'square' then
|
||||
-- reuse existing (1-2) vertices
|
||||
elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
|
||||
drawing.pending.vertices = {drawing.pending.center}
|
||||
end
|
||||
drawing.pending.mode = 'rectangle'
|
||||
elseif chord == 'C-s' and not App.mouse_down(1) then
|
||||
State.current_drawing_mode = 'square'
|
||||
elseif App.mouse_down(1) and chord == 's' then
|
||||
State.current_drawing_mode = 'square'
|
||||
local _,drawing = Drawing.current_drawing(State)
|
||||
if drawing.pending.mode == 'freehand' then
|
||||
drawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)}
|
||||
elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
|
||||
if drawing.pending.vertices == nil then
|
||||
drawing.pending.vertices = {drawing.pending.p1}
|
||||
end
|
||||
elseif drawing.pending.mode == 'polygon' then
|
||||
while #drawing.pending.vertices > 2 do
|
||||
table.remove(drawing.pending.vertices)
|
||||
end
|
||||
elseif drawing.pending.mode == 'rectangle' then
|
||||
-- reuse existing (1-2) vertices
|
||||
elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
|
||||
drawing.pending.vertices = {drawing.pending.center}
|
||||
end
|
||||
drawing.pending.mode = 'square'
|
||||
elseif App.mouse_down(1) and chord == 'p' and State.current_drawing_mode == 'polygon' then
|
||||
local _,drawing,line_cache = Drawing.current_drawing(State)
|
||||
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
|
||||
local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
|
||||
table.insert(drawing.pending.vertices, j)
|
||||
elseif App.mouse_down(1) and chord == 'p' and (State.current_drawing_mode == 'rectangle' or State.current_drawing_mode == 'square') then
|
||||
local _,drawing,line_cache = Drawing.current_drawing(State)
|
||||
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
|
||||
local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
|
||||
while #drawing.pending.vertices >= 2 do
|
||||
table.remove(drawing.pending.vertices)
|
||||
end
|
||||
table.insert(drawing.pending.vertices, j)
|
||||
elseif chord == 'C-o' and not App.mouse_down(1) then
|
||||
State.current_drawing_mode = 'circle'
|
||||
elseif App.mouse_down(1) and chord == 'a' and State.current_drawing_mode == 'circle' then
|
||||
local _,drawing,line_cache = Drawing.current_drawing(State)
|
||||
drawing.pending.mode = 'arc'
|
||||
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
|
||||
local center = drawing.points[drawing.pending.center]
|
||||
drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my))
|
||||
drawing.pending.start_angle = geom.angle(center.x,center.y, mx,my)
|
||||
elseif App.mouse_down(1) and chord == 'o' then
|
||||
State.current_drawing_mode = 'circle'
|
||||
local _,drawing = Drawing.current_drawing(State)
|
||||
if drawing.pending.mode == 'freehand' then
|
||||
drawing.pending.center = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)
|
||||
elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
|
||||
drawing.pending.center = drawing.pending.p1
|
||||
elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then
|
||||
drawing.pending.center = drawing.pending.vertices[1]
|
||||
end
|
||||
drawing.pending.mode = 'circle'
|
||||
elseif chord == 'C-u' and not App.mouse_down(1) then
|
||||
local drawing_index,drawing,line_cache,i,p = Drawing.select_point_at_mouse(State)
|
||||
if drawing then
|
||||
if State.previous_drawing_mode == nil then
|
||||
State.previous_drawing_mode = State.current_drawing_mode
|
||||
end
|
||||
State.current_drawing_mode = 'move'
|
||||
drawing.pending = {mode=State.current_drawing_mode, target_point=p, target_point_index=i}
|
||||
State.lines.current_drawing_index = drawing_index
|
||||
State.lines.current_drawing = drawing
|
||||
end
|
||||
elseif chord == 'C-n' and not App.mouse_down(1) then
|
||||
local drawing_index,drawing,line_cache,point_index,p = Drawing.select_point_at_mouse(State)
|
||||
if drawing then
|
||||
if State.previous_drawing_mode == nil then
|
||||
-- don't clobber
|
||||
State.previous_drawing_mode = State.current_drawing_mode
|
||||
end
|
||||
State.current_drawing_mode = 'name'
|
||||
p.name = ''
|
||||
drawing.pending = {mode=State.current_drawing_mode, target_point=point_index}
|
||||
State.lines.current_drawing_index = drawing_index
|
||||
State.lines.current_drawing = drawing
|
||||
end
|
||||
elseif chord == 'C-d' and not App.mouse_down(1) then
|
||||
local _,drawing,_,i,p = Drawing.select_point_at_mouse(State)
|
||||
if drawing then
|
||||
for _,shape in ipairs(drawing.shapes) do
|
||||
if Drawing.contains_point(shape, i) then
|
||||
if shape.mode == 'polygon' then
|
||||
local idx = table.find(shape.vertices, i)
|
||||
assert(idx)
|
||||
table.remove(shape.vertices, idx)
|
||||
if #shape.vertices < 3 then
|
||||
shape.mode = 'deleted'
|
||||
end
|
||||
else
|
||||
shape.mode = 'deleted'
|
||||
end
|
||||
end
|
||||
end
|
||||
drawing.points[i].deleted = true
|
||||
end
|
||||
local drawing,_,_,shape = Drawing.select_shape_at_mouse(State)
|
||||
if drawing then
|
||||
shape.mode = 'deleted'
|
||||
end
|
||||
elseif chord == 'C-h' and not App.mouse_down(1) then
|
||||
local drawing = Drawing.select_drawing_at_mouse(State)
|
||||
if drawing then
|
||||
drawing.show_help = true
|
||||
end
|
||||
elseif chord == 'escape' and App.mouse_down(1) then
|
||||
local _,drawing = Drawing.current_drawing(State)
|
||||
drawing.pending = {}
|
||||
end
|
||||
end
|
||||
|
||||
function Drawing.complete_rectangle(firstx,firsty, secondx,secondy, x,y)
|
||||
if firstx == secondx then
|
||||
return x,secondy, x,firsty
|
||||
end
|
||||
if firsty == secondy then
|
||||
return secondx,y, firstx,y
|
||||
end
|
||||
local first_slope = (secondy-firsty)/(secondx-firstx)
|
||||
-- slope of second edge:
|
||||
-- -1/first_slope
|
||||
-- equation of line containing the second edge:
|
||||
-- y-secondy = -1/first_slope*(x-secondx)
|
||||
-- => 1/first_slope*x + y + (- secondy - secondx/first_slope) = 0
|
||||
-- now we want to find the point on this line that's closest to the mouse pointer.
|
||||
-- https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Line_defined_by_an_equation
|
||||
local a = 1/first_slope
|
||||
local c = -secondy - secondx/first_slope
|
||||
local thirdx = round(((x-a*y) - a*c) / (a*a + 1))
|
||||
local thirdy = round((a*(-x + a*y) - c) / (a*a + 1))
|
||||
-- slope of third edge = first_slope
|
||||
-- equation of line containing third edge:
|
||||
-- y - thirdy = first_slope*(x-thirdx)
|
||||
-- => -first_slope*x + y + (-thirdy + thirdx*first_slope) = 0
|
||||
-- now we want to find the point on this line that's closest to the first point
|
||||
local a = -first_slope
|
||||
local c = -thirdy + thirdx*first_slope
|
||||
local fourthx = round(((firstx-a*firsty) - a*c) / (a*a + 1))
|
||||
local fourthy = round((a*(-firstx + a*firsty) - c) / (a*a + 1))
|
||||
return thirdx,thirdy, fourthx,fourthy
|
||||
end
|
||||
|
||||
function Drawing.complete_square(firstx,firsty, secondx,secondy, x,y)
|
||||
-- use x,y only to decide which side of the first edge to complete the square on
|
||||
local deltax = secondx-firstx
|
||||
local deltay = secondy-firsty
|
||||
local thirdx = secondx+deltay
|
||||
local thirdy = secondy-deltax
|
||||
if not geom.same_side(firstx,firsty, secondx,secondy, thirdx,thirdy, x,y) then
|
||||
deltax = -deltax
|
||||
deltay = -deltay
|
||||
thirdx = secondx+deltay
|
||||
thirdy = secondy-deltax
|
||||
end
|
||||
local fourthx = firstx+deltay
|
||||
local fourthy = firsty-deltax
|
||||
return thirdx,thirdy, fourthx,fourthy
|
||||
end
|
||||
|
||||
function Drawing.current_drawing(State)
|
||||
local x, y = App.mouse_x(), App.mouse_y()
|
||||
for drawing_index,drawing in ipairs(State.lines) do
|
||||
if drawing.mode == 'drawing' then
|
||||
local line_cache = State.line_cache[drawing_index]
|
||||
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
|
||||
return drawing_index,drawing,line_cache
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function Drawing.select_shape_at_mouse(State)
|
||||
for drawing_index,drawing in ipairs(State.lines) do
|
||||
if drawing.mode == 'drawing' then
|
||||
local x, y = App.mouse_x(), App.mouse_y()
|
||||
local line_cache = State.line_cache[drawing_index]
|
||||
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
|
||||
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
|
||||
for i,shape in ipairs(drawing.shapes) do
|
||||
assert(shape)
|
||||
if geom.on_shape(mx,my, drawing, shape) then
|
||||
return drawing,line_cache,i,shape
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Drawing.select_point_at_mouse(State)
|
||||
for drawing_index,drawing in ipairs(State.lines) do
|
||||
if drawing.mode == 'drawing' then
|
||||
local x, y = App.mouse_x(), App.mouse_y()
|
||||
local line_cache = State.line_cache[drawing_index]
|
||||
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
|
||||
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
|
||||
for i,point in ipairs(drawing.points) do
|
||||
assert(point)
|
||||
if Drawing.near(point, mx,my, State.width) then
|
||||
return drawing_index,drawing,line_cache,i,point
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Drawing.select_drawing_at_mouse(State)
|
||||
for drawing_index,drawing in ipairs(State.lines) do
|
||||
if drawing.mode == 'drawing' then
|
||||
local x, y = App.mouse_x(), App.mouse_y()
|
||||
local line_cache = State.line_cache[drawing_index]
|
||||
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
|
||||
return drawing
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Drawing.contains_point(shape, p)
|
||||
if shape.mode == 'freehand' then
|
||||
-- not supported
|
||||
elseif shape.mode == 'line' or shape.mode == 'manhattan' then
|
||||
return shape.p1 == p or shape.p2 == p
|
||||
elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
|
||||
return table.find(shape.vertices, p)
|
||||
elseif shape.mode == 'circle' then
|
||||
return shape.center == p
|
||||
elseif shape.mode == 'arc' then
|
||||
return shape.center == p
|
||||
-- ugh, how to support angles
|
||||
elseif shape.mode == 'deleted' then
|
||||
-- already done
|
||||
else
|
||||
print(shape.mode)
|
||||
assert(false)
|
||||
end
|
||||
end
|
||||
|
||||
function Drawing.smoothen(shape)
|
||||
assert(shape.mode == 'freehand')
|
||||
for _=1,7 do
|
||||
for i=2,#shape.points-1 do
|
||||
local a = shape.points[i-1]
|
||||
local b = shape.points[i]
|
||||
local c = shape.points[i+1]
|
||||
b.x = round((a.x + b.x + c.x)/3)
|
||||
b.y = round((a.y + b.y + c.y)/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function round(num)
|
||||
return math.floor(num+.5)
|
||||
end
|
||||
|
||||
function Drawing.insert_point(points, x,y)
|
||||
table.insert(points, {x=x, y=y})
|
||||
return #points
|
||||
end
|
||||
|
||||
function Drawing.find_or_insert_point(points, x,y, width)
|
||||
-- check if UI would snap the two points together
|
||||
for i,point in ipairs(points) do
|
||||
if Drawing.near(point, x,y, width) then
|
||||
return i
|
||||
end
|
||||
end
|
||||
table.insert(points, {x=x, y=y})
|
||||
return #points
|
||||
end
|
||||
|
||||
function Drawing.near(point, x,y, width)
|
||||
local px,py = Drawing.pixels(x, width),Drawing.pixels(y, width)
|
||||
local cx,cy = Drawing.pixels(point.x, width), Drawing.pixels(point.y, width)
|
||||
return (cx-px)*(cx-px) + (cy-py)*(cy-py) < Same_point_distance*Same_point_distance
|
||||
end
|
||||
|
||||
function Drawing.pixels(n, width) -- parts to pixels
|
||||
return math.floor(n*width/256)
|
||||
end
|
||||
function Drawing.coord(n, width) -- pixels to parts
|
||||
return math.floor(n*256/width)
|
||||
end
|
||||
|
||||
function table.find(h, x)
|
||||
for k,v in pairs(h) do
|
||||
if v == x then
|
||||
return k
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,785 +0,0 @@
|
|||
-- major tests for drawings
|
||||
-- We minimize assumptions about specific pixels, and try to test at the level
|
||||
-- of specific shapes. In particular, no tests of freehand drawings.
|
||||
|
||||
function test_creating_drawing_saves()
|
||||
io.write('\ntest_creating_drawing_saves')
|
||||
App.screen.init{width=120, height=60}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.filename = 'foo'
|
||||
Editor_state.lines = load_array{}
|
||||
Text.redraw_all(Editor_state)
|
||||
edit.draw(Editor_state)
|
||||
-- click on button to create drawing
|
||||
edit.run_after_mouse_click(Editor_state, 8,Editor_state.top+8, 1)
|
||||
-- file not immediately saved
|
||||
edit.update(Editor_state, 0.01)
|
||||
check_nil(App.filesystem['foo'], 'F - test_creating_drawing_saves/early')
|
||||
-- wait until save
|
||||
App.wait_fake_time(3.1)
|
||||
edit.update(Editor_state, 0)
|
||||
-- filesystem contains drawing and an empty line of text
|
||||
check_eq(App.filesystem['foo'], '```lines\n```\n\n', 'F - test_creating_drawing_saves')
|
||||
end
|
||||
|
||||
function test_draw_line()
|
||||
io.write('\ntest_draw_line')
|
||||
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.filename = 'foo'
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'line'
|
||||
edit.draw(Editor_state)
|
||||
check_eq(#Editor_state.lines, 2, 'F - test_draw_line/baseline/#lines')
|
||||
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_line/baseline/mode')
|
||||
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_line/baseline/y')
|
||||
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_line/baseline/y')
|
||||
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_line/baseline/#shapes')
|
||||
-- draw a line
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 1, 'F - test_draw_line/#shapes')
|
||||
check_eq(#drawing.points, 2, 'F - test_draw_line/#points')
|
||||
check_eq(drawing.shapes[1].mode, 'line', 'F - test_draw_line/shape:1')
|
||||
local p1 = drawing.points[drawing.shapes[1].p1]
|
||||
local p2 = drawing.points[drawing.shapes[1].p2]
|
||||
check_eq(p1.x, 5, 'F - test_draw_line/p1:x')
|
||||
check_eq(p1.y, 6, 'F - test_draw_line/p1:y')
|
||||
check_eq(p2.x, 35, 'F - test_draw_line/p2:x')
|
||||
check_eq(p2.y, 36, 'F - test_draw_line/p2:y')
|
||||
-- wait until save
|
||||
App.wait_fake_time(3.1)
|
||||
edit.update(Editor_state, 0)
|
||||
-- The format on disk isn't perfectly stable. Table fields can be reordered.
|
||||
-- So just reload from disk to verify.
|
||||
load_from_disk(Editor_state)
|
||||
Text.redraw_all(Editor_state)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 1, 'F - test_draw_line/save/#shapes')
|
||||
check_eq(#drawing.points, 2, 'F - test_draw_line/save/#points')
|
||||
check_eq(drawing.shapes[1].mode, 'line', 'F - test_draw_line/save/shape:1')
|
||||
local p1 = drawing.points[drawing.shapes[1].p1]
|
||||
local p2 = drawing.points[drawing.shapes[1].p2]
|
||||
check_eq(p1.x, 5, 'F - test_draw_line/save/p1:x')
|
||||
check_eq(p1.y, 6, 'F - test_draw_line/save/p1:y')
|
||||
check_eq(p2.x, 35, 'F - test_draw_line/save/p2:x')
|
||||
check_eq(p2.y, 36, 'F - test_draw_line/save/p2:y')
|
||||
end
|
||||
|
||||
function test_draw_horizontal_line()
|
||||
io.write('\ntest_draw_horizontal_line')
|
||||
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'manhattan'
|
||||
edit.draw(Editor_state)
|
||||
check_eq(#Editor_state.lines, 2, 'F - test_draw_horizontal_line/baseline/#lines')
|
||||
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_horizontal_line/baseline/mode')
|
||||
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_horizontal_line/baseline/y')
|
||||
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_horizontal_line/baseline/y')
|
||||
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_horizontal_line/baseline/#shapes')
|
||||
-- draw a line that is more horizontal than vertical
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+26, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 1, 'F - test_draw_horizontal_line/#shapes')
|
||||
check_eq(#drawing.points, 2, 'F - test_draw_horizontal_line/#points')
|
||||
check_eq(drawing.shapes[1].mode, 'manhattan', 'F - test_draw_horizontal_line/shape_mode')
|
||||
local p1 = drawing.points[drawing.shapes[1].p1]
|
||||
local p2 = drawing.points[drawing.shapes[1].p2]
|
||||
check_eq(p1.x, 5, 'F - test_draw_horizontal_line/p1:x')
|
||||
check_eq(p1.y, 6, 'F - test_draw_horizontal_line/p1:y')
|
||||
check_eq(p2.x, 35, 'F - test_draw_horizontal_line/p2:x')
|
||||
check_eq(p2.y, p1.y, 'F - test_draw_horizontal_line/p2:y')
|
||||
end
|
||||
|
||||
function test_draw_circle()
|
||||
io.write('\ntest_draw_circle')
|
||||
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'line'
|
||||
edit.draw(Editor_state)
|
||||
check_eq(#Editor_state.lines, 2, 'F - test_draw_circle/baseline/#lines')
|
||||
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_circle/baseline/mode')
|
||||
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_circle/baseline/y')
|
||||
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_circle/baseline/y')
|
||||
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_circle/baseline/#shapes')
|
||||
-- draw a circle
|
||||
App.mouse_move(Editor_state.left+4, Editor_state.top+Drawing_padding_top+4) -- hover on drawing
|
||||
edit.run_after_keychord(Editor_state, 'C-o')
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+35+30, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 1, 'F - test_draw_circle/#shapes')
|
||||
check_eq(#drawing.points, 1, 'F - test_draw_circle/#points')
|
||||
check_eq(drawing.shapes[1].mode, 'circle', 'F - test_draw_horizontal_line/shape_mode')
|
||||
check_eq(drawing.shapes[1].radius, 30, 'F - test_draw_circle/radius')
|
||||
local center = drawing.points[drawing.shapes[1].center]
|
||||
check_eq(center.x, 35, 'F - test_draw_circle/center:x')
|
||||
check_eq(center.y, 36, 'F - test_draw_circle/center:y')
|
||||
end
|
||||
|
||||
function test_cancel_stroke()
|
||||
io.write('\ntest_cancel_stroke')
|
||||
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.filename = 'foo'
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'line'
|
||||
edit.draw(Editor_state)
|
||||
check_eq(#Editor_state.lines, 2, 'F - test_cancel_stroke/baseline/#lines')
|
||||
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_cancel_stroke/baseline/mode')
|
||||
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_cancel_stroke/baseline/y')
|
||||
check_eq(Editor_state.lines[1].h, 128, 'F - test_cancel_stroke/baseline/y')
|
||||
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_cancel_stroke/baseline/#shapes')
|
||||
-- start drawing a line
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
|
||||
-- cancel
|
||||
edit.run_after_keychord(Editor_state, 'escape')
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 0, 'F - test_cancel_stroke/#shapes')
|
||||
end
|
||||
|
||||
function test_keys_do_not_affect_shape_when_mouse_up()
|
||||
io.write('\ntest_keys_do_not_affect_shape_when_mouse_up')
|
||||
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'line'
|
||||
edit.draw(Editor_state)
|
||||
-- hover over drawing and press 'o' without holding mouse
|
||||
App.mouse_move(Editor_state.left+4, Editor_state.top+Drawing_padding_top+4) -- hover on drawing
|
||||
edit.run_after_keychord(Editor_state, 'o')
|
||||
-- no change to drawing mode
|
||||
check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_keys_do_not_affect_shape_when_mouse_up/drawing_mode')
|
||||
-- no change to text either because we didn't run the textinput event
|
||||
end
|
||||
|
||||
function test_draw_circle_mid_stroke()
|
||||
io.write('\ntest_draw_circle_mid_stroke')
|
||||
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'line'
|
||||
edit.draw(Editor_state)
|
||||
check_eq(#Editor_state.lines, 2, 'F - test_draw_circle_mid_stroke/baseline/#lines')
|
||||
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_circle_mid_stroke/baseline/mode')
|
||||
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_circle_mid_stroke/baseline/y')
|
||||
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_circle_mid_stroke/baseline/y')
|
||||
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_circle_mid_stroke/baseline/#shapes')
|
||||
-- draw a circle
|
||||
App.mouse_move(Editor_state.left+4, Editor_state.top+Drawing_padding_top+4) -- hover on drawing
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
edit.run_after_keychord(Editor_state, 'o')
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+35+30, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 1, 'F - test_draw_circle_mid_stroke/#shapes')
|
||||
check_eq(#drawing.points, 1, 'F - test_draw_circle_mid_stroke/#points')
|
||||
check_eq(drawing.shapes[1].mode, 'circle', 'F - test_draw_horizontal_line/shape_mode')
|
||||
check_eq(drawing.shapes[1].radius, 30, 'F - test_draw_circle_mid_stroke/radius')
|
||||
local center = drawing.points[drawing.shapes[1].center]
|
||||
check_eq(center.x, 35, 'F - test_draw_circle_mid_stroke/center:x')
|
||||
check_eq(center.y, 36, 'F - test_draw_circle_mid_stroke/center:y')
|
||||
end
|
||||
|
||||
function test_draw_arc()
|
||||
io.write('\ntest_draw_arc')
|
||||
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'circle'
|
||||
edit.draw(Editor_state)
|
||||
check_eq(#Editor_state.lines, 2, 'F - test_draw_arc/baseline/#lines')
|
||||
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_arc/baseline/mode')
|
||||
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_arc/baseline/y')
|
||||
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_arc/baseline/y')
|
||||
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_arc/baseline/#shapes')
|
||||
-- draw an arc
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
App.mouse_move(Editor_state.left+35+30, Editor_state.top+Drawing_padding_top+36)
|
||||
edit.run_after_keychord(Editor_state, 'a') -- arc mode
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+35+50, Editor_state.top+Drawing_padding_top+36+50, 1) -- 45°
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 1, 'F - test_draw_arc/#shapes')
|
||||
check_eq(#drawing.points, 1, 'F - test_draw_arc/#points')
|
||||
check_eq(drawing.shapes[1].mode, 'arc', 'F - test_draw_horizontal_line/shape_mode')
|
||||
local arc = drawing.shapes[1]
|
||||
check_eq(arc.radius, 30, 'F - test_draw_arc/radius')
|
||||
local center = drawing.points[arc.center]
|
||||
check_eq(center.x, 35, 'F - test_draw_arc/center:x')
|
||||
check_eq(center.y, 36, 'F - test_draw_arc/center:y')
|
||||
check_eq(arc.start_angle, 0, 'F - test_draw_arc/start:angle')
|
||||
check_eq(arc.end_angle, math.pi/4, 'F - test_draw_arc/end:angle')
|
||||
end
|
||||
|
||||
function test_draw_polygon()
|
||||
io.write('\ntest_draw_polygon')
|
||||
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
edit.draw(Editor_state)
|
||||
check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_draw_polygon/baseline/drawing_mode')
|
||||
check_eq(#Editor_state.lines, 2, 'F - test_draw_polygon/baseline/#lines')
|
||||
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_polygon/baseline/mode')
|
||||
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_polygon/baseline/y')
|
||||
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_polygon/baseline/y')
|
||||
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_polygon/baseline/#shapes')
|
||||
-- first point
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
|
||||
edit.run_after_keychord(Editor_state, 'g') -- polygon mode
|
||||
-- second point
|
||||
App.mouse_move(Editor_state.left+65, Editor_state.top+Drawing_padding_top+36)
|
||||
edit.run_after_keychord(Editor_state, 'p') -- add point
|
||||
-- final point
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+26, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 1, 'F - test_draw_polygon/#shapes')
|
||||
check_eq(#drawing.points, 3, 'F - test_draw_polygon/vertices')
|
||||
local shape = drawing.shapes[1]
|
||||
check_eq(shape.mode, 'polygon', 'F - test_draw_polygon/shape_mode')
|
||||
check_eq(#shape.vertices, 3, 'F - test_draw_polygon/vertices')
|
||||
local p = drawing.points[shape.vertices[1]]
|
||||
check_eq(p.x, 5, 'F - test_draw_polygon/p1:x')
|
||||
check_eq(p.y, 6, 'F - test_draw_polygon/p1:y')
|
||||
local p = drawing.points[shape.vertices[2]]
|
||||
check_eq(p.x, 65, 'F - test_draw_polygon/p2:x')
|
||||
check_eq(p.y, 36, 'F - test_draw_polygon/p2:y')
|
||||
local p = drawing.points[shape.vertices[3]]
|
||||
check_eq(p.x, 35, 'F - test_draw_polygon/p3:x')
|
||||
check_eq(p.y, 26, 'F - test_draw_polygon/p3:y')
|
||||
end
|
||||
|
||||
function test_draw_rectangle()
|
||||
io.write('\ntest_draw_rectangle')
|
||||
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
edit.draw(Editor_state)
|
||||
check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_draw_rectangle/baseline/drawing_mode')
|
||||
check_eq(#Editor_state.lines, 2, 'F - test_draw_rectangle/baseline/#lines')
|
||||
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_rectangle/baseline/mode')
|
||||
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_rectangle/baseline/y')
|
||||
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_rectangle/baseline/y')
|
||||
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_rectangle/baseline/#shapes')
|
||||
-- first point
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
edit.run_after_keychord(Editor_state, 'r') -- rectangle mode
|
||||
-- second point/first edge
|
||||
App.mouse_move(Editor_state.left+42, Editor_state.top+Drawing_padding_top+45)
|
||||
edit.run_after_keychord(Editor_state, 'p')
|
||||
-- override second point/first edge
|
||||
App.mouse_move(Editor_state.left+75, Editor_state.top+Drawing_padding_top+76)
|
||||
edit.run_after_keychord(Editor_state, 'p')
|
||||
-- release (decides 'thickness' of rectangle perpendicular to first edge)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+15, Editor_state.top+Drawing_padding_top+26, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 1, 'F - test_draw_rectangle/#shapes')
|
||||
check_eq(#drawing.points, 5, 'F - test_draw_rectangle/#points') -- currently includes every point added
|
||||
local shape = drawing.shapes[1]
|
||||
check_eq(shape.mode, 'rectangle', 'F - test_draw_rectangle/shape_mode')
|
||||
check_eq(#shape.vertices, 4, 'F - test_draw_rectangle/vertices')
|
||||
local p = drawing.points[shape.vertices[1]]
|
||||
check_eq(p.x, 35, 'F - test_draw_rectangle/p1:x')
|
||||
check_eq(p.y, 36, 'F - test_draw_rectangle/p1:y')
|
||||
local p = drawing.points[shape.vertices[2]]
|
||||
check_eq(p.x, 75, 'F - test_draw_rectangle/p2:x')
|
||||
check_eq(p.y, 76, 'F - test_draw_rectangle/p2:y')
|
||||
local p = drawing.points[shape.vertices[3]]
|
||||
check_eq(p.x, 70, 'F - test_draw_rectangle/p3:x')
|
||||
check_eq(p.y, 81, 'F - test_draw_rectangle/p3:y')
|
||||
local p = drawing.points[shape.vertices[4]]
|
||||
check_eq(p.x, 30, 'F - test_draw_rectangle/p4:x')
|
||||
check_eq(p.y, 41, 'F - test_draw_rectangle/p4:y')
|
||||
end
|
||||
|
||||
function test_draw_rectangle_intermediate()
|
||||
io.write('\ntest_draw_rectangle_intermediate')
|
||||
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
edit.draw(Editor_state)
|
||||
check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_draw_rectangle_intermediate/baseline/drawing_mode')
|
||||
check_eq(#Editor_state.lines, 2, 'F - test_draw_rectangle_intermediate/baseline/#lines')
|
||||
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_rectangle_intermediate/baseline/mode')
|
||||
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_rectangle_intermediate/baseline/y')
|
||||
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_rectangle_intermediate/baseline/y')
|
||||
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_rectangle_intermediate/baseline/#shapes')
|
||||
-- first point
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
edit.run_after_keychord(Editor_state, 'r') -- rectangle mode
|
||||
-- second point/first edge
|
||||
App.mouse_move(Editor_state.left+42, Editor_state.top+Drawing_padding_top+45)
|
||||
edit.run_after_keychord(Editor_state, 'p')
|
||||
-- override second point/first edge
|
||||
App.mouse_move(Editor_state.left+75, Editor_state.top+Drawing_padding_top+76)
|
||||
edit.run_after_keychord(Editor_state, 'p')
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.points, 3, 'F - test_draw_rectangle_intermediate/#points') -- currently includes every point added
|
||||
local pending = drawing.pending
|
||||
check_eq(pending.mode, 'rectangle', 'F - test_draw_rectangle_intermediate/shape_mode')
|
||||
check_eq(#pending.vertices, 2, 'F - test_draw_rectangle_intermediate/vertices')
|
||||
local p = drawing.points[pending.vertices[1]]
|
||||
check_eq(p.x, 35, 'F - test_draw_rectangle_intermediate/p1:x')
|
||||
check_eq(p.y, 36, 'F - test_draw_rectangle_intermediate/p1:y')
|
||||
local p = drawing.points[pending.vertices[2]]
|
||||
check_eq(p.x, 75, 'F - test_draw_rectangle_intermediate/p2:x')
|
||||
check_eq(p.y, 76, 'F - test_draw_rectangle_intermediate/p2:y')
|
||||
-- outline of rectangle is drawn based on where the mouse is, but we can't check that so far
|
||||
end
|
||||
|
||||
function test_draw_square()
|
||||
io.write('\ntest_draw_square')
|
||||
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
edit.draw(Editor_state)
|
||||
check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_draw_square/baseline/drawing_mode')
|
||||
check_eq(#Editor_state.lines, 2, 'F - test_draw_square/baseline/#lines')
|
||||
check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_square/baseline/mode')
|
||||
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_square/baseline/y')
|
||||
check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_square/baseline/y')
|
||||
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_square/baseline/#shapes')
|
||||
-- first point
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
edit.run_after_keychord(Editor_state, 's') -- square mode
|
||||
-- second point/first edge
|
||||
App.mouse_move(Editor_state.left+42, Editor_state.top+Drawing_padding_top+45)
|
||||
edit.run_after_keychord(Editor_state, 'p')
|
||||
-- override second point/first edge
|
||||
App.mouse_move(Editor_state.left+65, Editor_state.top+Drawing_padding_top+66)
|
||||
edit.run_after_keychord(Editor_state, 'p')
|
||||
-- release (decides which side of first edge to draw square on)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+15, Editor_state.top+Drawing_padding_top+26, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 1, 'F - test_draw_square/#shapes')
|
||||
check_eq(#drawing.points, 5, 'F - test_draw_square/#points') -- currently includes every point added
|
||||
check_eq(drawing.shapes[1].mode, 'square', 'F - test_draw_square/shape_mode')
|
||||
check_eq(#drawing.shapes[1].vertices, 4, 'F - test_draw_square/vertices')
|
||||
local p = drawing.points[drawing.shapes[1].vertices[1]]
|
||||
check_eq(p.x, 35, 'F - test_draw_square/p1:x')
|
||||
check_eq(p.y, 36, 'F - test_draw_square/p1:y')
|
||||
local p = drawing.points[drawing.shapes[1].vertices[2]]
|
||||
check_eq(p.x, 65, 'F - test_draw_square/p2:x')
|
||||
check_eq(p.y, 66, 'F - test_draw_square/p2:y')
|
||||
local p = drawing.points[drawing.shapes[1].vertices[3]]
|
||||
check_eq(p.x, 35, 'F - test_draw_square/p3:x')
|
||||
check_eq(p.y, 96, 'F - test_draw_square/p3:y')
|
||||
local p = drawing.points[drawing.shapes[1].vertices[4]]
|
||||
check_eq(p.x, 5, 'F - test_draw_square/p4:x')
|
||||
check_eq(p.y, 66, 'F - test_draw_square/p4:y')
|
||||
end
|
||||
|
||||
function test_name_point()
|
||||
io.write('\ntest_name_point')
|
||||
-- create a drawing with a line
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.filename = 'foo'
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'line'
|
||||
edit.draw(Editor_state)
|
||||
-- draw a line
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 1, 'F - test_name_point/baseline/#shapes')
|
||||
check_eq(#drawing.points, 2, 'F - test_name_point/baseline/#points')
|
||||
check_eq(drawing.shapes[1].mode, 'line', 'F - test_name_point/baseline/shape:1')
|
||||
local p1 = drawing.points[drawing.shapes[1].p1]
|
||||
local p2 = drawing.points[drawing.shapes[1].p2]
|
||||
check_eq(p1.x, 5, 'F - test_name_point/baseline/p1:x')
|
||||
check_eq(p1.y, 6, 'F - test_name_point/baseline/p1:y')
|
||||
check_eq(p2.x, 35, 'F - test_name_point/baseline/p2:x')
|
||||
check_eq(p2.y, 36, 'F - test_name_point/baseline/p2:y')
|
||||
check_nil(p2.name, 'F - test_name_point/baseline/p2:name')
|
||||
-- enter 'name' mode without moving the mouse
|
||||
edit.run_after_keychord(Editor_state, 'C-n')
|
||||
check_eq(Editor_state.current_drawing_mode, 'name', 'F - test_name_point/mode:1')
|
||||
edit.run_after_textinput(Editor_state, 'A')
|
||||
check_eq(p2.name, 'A', 'F - test_name_point')
|
||||
-- still in 'name' mode
|
||||
check_eq(Editor_state.current_drawing_mode, 'name', 'F - test_name_point/mode:2')
|
||||
-- exit 'name' mode
|
||||
edit.run_after_keychord(Editor_state, 'return')
|
||||
check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_name_point/mode:3')
|
||||
check_eq(p2.name, 'A', 'F - test_name_point')
|
||||
-- wait until save
|
||||
App.wait_fake_time(3.1)
|
||||
edit.update(Editor_state, 0)
|
||||
-- change is saved
|
||||
load_from_disk(Editor_state)
|
||||
Text.redraw_all(Editor_state)
|
||||
local p2 = Editor_state.lines[1].points[drawing.shapes[1].p2]
|
||||
check_eq(p2.name, 'A', 'F - test_name_point/save')
|
||||
end
|
||||
|
||||
function test_move_point()
|
||||
io.write('\ntest_move_point')
|
||||
-- create a drawing with a line
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.filename = 'foo'
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'line'
|
||||
edit.draw(Editor_state)
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 1, 'F - test_move_point/baseline/#shapes')
|
||||
check_eq(#drawing.points, 2, 'F - test_move_point/baseline/#points')
|
||||
check_eq(drawing.shapes[1].mode, 'line', 'F - test_move_point/baseline/shape:1')
|
||||
local p1 = drawing.points[drawing.shapes[1].p1]
|
||||
local p2 = drawing.points[drawing.shapes[1].p2]
|
||||
check_eq(p1.x, 5, 'F - test_move_point/baseline/p1:x')
|
||||
check_eq(p1.y, 6, 'F - test_move_point/baseline/p1:y')
|
||||
check_eq(p2.x, 35, 'F - test_move_point/baseline/p2:x')
|
||||
check_eq(p2.y, 36, 'F - test_move_point/baseline/p2:y')
|
||||
-- wait until save
|
||||
App.wait_fake_time(3.1)
|
||||
edit.update(Editor_state, 0)
|
||||
-- line is saved to disk
|
||||
load_from_disk(Editor_state)
|
||||
Text.redraw_all(Editor_state)
|
||||
local drawing = Editor_state.lines[1]
|
||||
local p2 = Editor_state.lines[1].points[drawing.shapes[1].p2]
|
||||
check_eq(p2.x, 35, 'F - test_move_point/save/x')
|
||||
check_eq(p2.y, 36, 'F - test_move_point/save/y')
|
||||
edit.draw(Editor_state)
|
||||
-- enter 'move' mode without moving the mouse
|
||||
edit.run_after_keychord(Editor_state, 'C-u')
|
||||
check_eq(Editor_state.current_drawing_mode, 'move', 'F - test_move_point/mode:1')
|
||||
-- point is lifted
|
||||
check_eq(drawing.pending.mode, 'move', 'F - test_move_point/mode:2')
|
||||
check_eq(drawing.pending.target_point, p2, 'F - test_move_point/target')
|
||||
-- move point
|
||||
App.mouse_move(Editor_state.left+26, Editor_state.top+Drawing_padding_top+44)
|
||||
edit.update(Editor_state, 0.05)
|
||||
local p2 = drawing.points[drawing.shapes[1].p2]
|
||||
check_eq(p2.x, 26, 'F - test_move_point/x')
|
||||
check_eq(p2.y, 44, 'F - test_move_point/y')
|
||||
-- exit 'move' mode
|
||||
edit.run_after_mouse_click(Editor_state, Editor_state.left+26, Editor_state.top+Drawing_padding_top+44, 1)
|
||||
check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_move_point/mode:3')
|
||||
check_eq(drawing.pending, {}, 'F - test_move_point/pending')
|
||||
-- wait until save
|
||||
App.wait_fake_time(3.1)
|
||||
edit.update(Editor_state, 0)
|
||||
-- change is saved
|
||||
load_from_disk(Editor_state)
|
||||
Text.redraw_all(Editor_state)
|
||||
local p2 = Editor_state.lines[1].points[drawing.shapes[1].p2]
|
||||
check_eq(p2.x, 26, 'F - test_move_point/save/x')
|
||||
check_eq(p2.y, 44, 'F - test_move_point/save/y')
|
||||
end
|
||||
|
||||
function test_move_point_on_manhattan_line()
|
||||
io.write('\ntest_move_point_on_manhattan_line')
|
||||
-- create a drawing with a manhattan line
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.filename = 'foo'
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'manhattan'
|
||||
edit.draw(Editor_state)
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+46, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 1, 'F - test_move_point_on_manhattan_line/baseline/#shapes')
|
||||
check_eq(#drawing.points, 2, 'F - test_move_point_on_manhattan_line/baseline/#points')
|
||||
check_eq(drawing.shapes[1].mode, 'manhattan', 'F - test_move_point_on_manhattan_line/baseline/shape:1')
|
||||
edit.draw(Editor_state)
|
||||
-- enter 'move' mode
|
||||
edit.run_after_keychord(Editor_state, 'C-u')
|
||||
check_eq(Editor_state.current_drawing_mode, 'move', 'F - test_move_point_on_manhattan_line/mode:1')
|
||||
-- move point
|
||||
App.mouse_move(Editor_state.left+26, Editor_state.top+Drawing_padding_top+44)
|
||||
edit.update(Editor_state, 0.05)
|
||||
-- line is no longer manhattan
|
||||
check_eq(drawing.shapes[1].mode, 'line', 'F - test_move_point_on_manhattan_line/baseline/shape:1')
|
||||
end
|
||||
|
||||
function test_delete_lines_at_point()
|
||||
io.write('\ntest_delete_lines_at_point')
|
||||
-- create a drawing with two lines connected at a point
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.filename = 'foo'
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'line'
|
||||
edit.draw(Editor_state)
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+55, Editor_state.top+Drawing_padding_top+26, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 2, 'F - test_delete_lines_at_point/baseline/#shapes')
|
||||
check_eq(drawing.shapes[1].mode, 'line', 'F - test_delete_lines_at_point/baseline/shape:1')
|
||||
check_eq(drawing.shapes[2].mode, 'line', 'F - test_delete_lines_at_point/baseline/shape:2')
|
||||
-- hover on the common point and delete
|
||||
App.mouse_move(Editor_state.left+35, Editor_state.top+Drawing_padding_top+36)
|
||||
edit.run_after_keychord(Editor_state, 'C-d')
|
||||
check_eq(drawing.shapes[1].mode, 'deleted', 'F - test_delete_lines_at_point/shape:1')
|
||||
check_eq(drawing.shapes[2].mode, 'deleted', 'F - test_delete_lines_at_point/shape:2')
|
||||
-- wait for some time
|
||||
App.wait_fake_time(3.1)
|
||||
edit.update(Editor_state, 0)
|
||||
-- deleted points disappear after file is reloaded
|
||||
load_from_disk(Editor_state)
|
||||
Text.redraw_all(Editor_state)
|
||||
check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_delete_lines_at_point/save')
|
||||
end
|
||||
|
||||
function test_delete_line_under_mouse_pointer()
|
||||
io.write('\ntest_delete_line_under_mouse_pointer')
|
||||
-- create a drawing with two lines connected at a point
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'line'
|
||||
edit.draw(Editor_state)
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+55, Editor_state.top+Drawing_padding_top+26, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 2, 'F - test_delete_line_under_mouse_pointer/baseline/#shapes')
|
||||
check_eq(drawing.shapes[1].mode, 'line', 'F - test_delete_line_under_mouse_pointer/baseline/shape:1')
|
||||
check_eq(drawing.shapes[2].mode, 'line', 'F - test_delete_line_under_mouse_pointer/baseline/shape:2')
|
||||
-- hover on one of the lines and delete
|
||||
App.mouse_move(Editor_state.left+25, Editor_state.top+Drawing_padding_top+26)
|
||||
edit.run_after_keychord(Editor_state, 'C-d')
|
||||
-- only that line is deleted
|
||||
check_eq(drawing.shapes[1].mode, 'deleted', 'F - test_delete_line_under_mouse_pointer/shape:1')
|
||||
check_eq(drawing.shapes[2].mode, 'line', 'F - test_delete_line_under_mouse_pointer/shape:2')
|
||||
end
|
||||
|
||||
function test_delete_point_from_polygon()
|
||||
io.write('\ntest_delete_point_from_polygon')
|
||||
-- create a drawing with two lines connected at a point
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'line'
|
||||
edit.draw(Editor_state)
|
||||
-- first point
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
|
||||
edit.run_after_keychord(Editor_state, 'g') -- polygon mode
|
||||
-- second point
|
||||
App.mouse_move(Editor_state.left+65, Editor_state.top+Drawing_padding_top+36)
|
||||
edit.run_after_keychord(Editor_state, 'p') -- add point
|
||||
-- third point
|
||||
App.mouse_move(Editor_state.left+35, Editor_state.top+Drawing_padding_top+26)
|
||||
edit.run_after_keychord(Editor_state, 'p') -- add point
|
||||
-- fourth point
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+14, Editor_state.top+Drawing_padding_top+16, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 1, 'F - test_delete_point_from_polygon/baseline/#shapes')
|
||||
check_eq(drawing.shapes[1].mode, 'polygon', 'F - test_delete_point_from_polygon/baseline/mode')
|
||||
check_eq(#drawing.shapes[1].vertices, 4, 'F - test_delete_point_from_polygon/baseline/vertices')
|
||||
-- hover on a point and delete
|
||||
App.mouse_move(Editor_state.left+35, Editor_state.top+Drawing_padding_top+26)
|
||||
edit.run_after_keychord(Editor_state, 'C-d')
|
||||
-- just the one point is deleted
|
||||
check_eq(drawing.shapes[1].mode, 'polygon', 'F - test_delete_point_from_polygon/shape')
|
||||
check_eq(#drawing.shapes[1].vertices, 3, 'F - test_delete_point_from_polygon/vertices')
|
||||
end
|
||||
|
||||
function test_delete_point_from_polygon()
|
||||
io.write('\ntest_delete_point_from_polygon')
|
||||
-- create a drawing with two lines connected at a point
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'line'
|
||||
edit.draw(Editor_state)
|
||||
-- first point
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
|
||||
edit.run_after_keychord(Editor_state, 'g') -- polygon mode
|
||||
-- second point
|
||||
App.mouse_move(Editor_state.left+65, Editor_state.top+Drawing_padding_top+36)
|
||||
edit.run_after_keychord(Editor_state, 'p') -- add point
|
||||
-- third point
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+14, Editor_state.top+Drawing_padding_top+16, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 1, 'F - test_delete_point_from_polygon/baseline/#shapes')
|
||||
check_eq(drawing.shapes[1].mode, 'polygon', 'F - test_delete_point_from_polygon/baseline/mode')
|
||||
check_eq(#drawing.shapes[1].vertices, 3, 'F - test_delete_point_from_polygon/baseline/vertices')
|
||||
-- hover on a point and delete
|
||||
App.mouse_move(Editor_state.left+65, Editor_state.top+Drawing_padding_top+36)
|
||||
edit.run_after_keychord(Editor_state, 'C-d')
|
||||
-- there's < 3 points left, so the whole polygon is deleted
|
||||
check_eq(drawing.shapes[1].mode, 'deleted', 'F - test_delete_point_from_polygon')
|
||||
end
|
||||
|
||||
function test_undo_name_point()
|
||||
io.write('\ntest_undo_name_point')
|
||||
-- create a drawing with a line
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.filename = 'foo'
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'line'
|
||||
edit.draw(Editor_state)
|
||||
-- draw a line
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 1, 'F - test_undo_name_point/baseline/#shapes')
|
||||
check_eq(#drawing.points, 2, 'F - test_undo_name_point/baseline/#points')
|
||||
check_eq(drawing.shapes[1].mode, 'line', 'F - test_undo_name_point/baseline/shape:1')
|
||||
local p1 = drawing.points[drawing.shapes[1].p1]
|
||||
local p2 = drawing.points[drawing.shapes[1].p2]
|
||||
check_eq(p1.x, 5, 'F - test_undo_name_point/baseline/p1:x')
|
||||
check_eq(p1.y, 6, 'F - test_undo_name_point/baseline/p1:y')
|
||||
check_eq(p2.x, 35, 'F - test_undo_name_point/baseline/p2:x')
|
||||
check_eq(p2.y, 36, 'F - test_undo_name_point/baseline/p2:y')
|
||||
check_nil(p2.name, 'F - test_undo_name_point/baseline/p2:name')
|
||||
check_eq(#Editor_state.history, 1, 'F - test_undo_name_point/baseline/history:1')
|
||||
--? print('a', Editor_state.lines.current_drawing)
|
||||
-- enter 'name' mode without moving the mouse
|
||||
edit.run_after_keychord(Editor_state, 'C-n')
|
||||
edit.run_after_textinput(Editor_state, 'A')
|
||||
edit.run_after_keychord(Editor_state, 'return')
|
||||
check_eq(p2.name, 'A', 'F - test_undo_name_point/baseline')
|
||||
check_eq(#Editor_state.history, 3, 'F - test_undo_name_point/baseline/history:2')
|
||||
check_eq(Editor_state.next_history, 4, 'F - test_undo_name_point/baseline/next_history')
|
||||
--? print('b', Editor_state.lines.current_drawing)
|
||||
-- undo
|
||||
edit.run_after_keychord(Editor_state, 'C-z')
|
||||
local drawing = Editor_state.lines[1]
|
||||
local p2 = drawing.points[drawing.shapes[1].p2]
|
||||
check_eq(Editor_state.next_history, 3, 'F - test_undo_name_point/next_history')
|
||||
check_eq(p2.name, '', 'F - test_undo_name_point') -- not quite what it was before, but close enough
|
||||
-- wait until save
|
||||
App.wait_fake_time(3.1)
|
||||
edit.update(Editor_state, 0)
|
||||
-- undo is saved
|
||||
load_from_disk(Editor_state)
|
||||
Text.redraw_all(Editor_state)
|
||||
local p2 = Editor_state.lines[1].points[drawing.shapes[1].p2]
|
||||
check_eq(p2.name, '', 'F - test_undo_name_point/save')
|
||||
end
|
||||
|
||||
function test_undo_move_point()
|
||||
io.write('\ntest_undo_move_point')
|
||||
-- create a drawing with a line
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.filename = 'foo'
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'line'
|
||||
edit.draw(Editor_state)
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 1, 'F - test_undo_move_point/baseline/#shapes')
|
||||
check_eq(#drawing.points, 2, 'F - test_undo_move_point/baseline/#points')
|
||||
check_eq(drawing.shapes[1].mode, 'line', 'F - test_undo_move_point/baseline/shape:1')
|
||||
local p1 = drawing.points[drawing.shapes[1].p1]
|
||||
local p2 = drawing.points[drawing.shapes[1].p2]
|
||||
check_eq(p1.x, 5, 'F - test_undo_move_point/baseline/p1:x')
|
||||
check_eq(p1.y, 6, 'F - test_undo_move_point/baseline/p1:y')
|
||||
check_eq(p2.x, 35, 'F - test_undo_move_point/baseline/p2:x')
|
||||
check_eq(p2.y, 36, 'F - test_undo_move_point/baseline/p2:y')
|
||||
check_nil(p2.name, 'F - test_undo_move_point/baseline/p2:name')
|
||||
-- move p2
|
||||
edit.run_after_keychord(Editor_state, 'C-u')
|
||||
App.mouse_move(Editor_state.left+26, Editor_state.top+Drawing_padding_top+44)
|
||||
edit.update(Editor_state, 0.05)
|
||||
local p2 = drawing.points[drawing.shapes[1].p2]
|
||||
check_eq(p2.x, 26, 'F - test_undo_move_point/x')
|
||||
check_eq(p2.y, 44, 'F - test_undo_move_point/y')
|
||||
-- exit 'move' mode
|
||||
edit.run_after_mouse_click(Editor_state, Editor_state.left+26, Editor_state.top+Drawing_padding_top+44, 1)
|
||||
check_eq(Editor_state.next_history, 4, 'F - test_undo_move_point/next_history')
|
||||
-- undo
|
||||
edit.run_after_keychord(Editor_state, 'C-z')
|
||||
edit.run_after_keychord(Editor_state, 'C-z') -- bug: need to undo twice
|
||||
local drawing = Editor_state.lines[1]
|
||||
local p2 = drawing.points[drawing.shapes[1].p2]
|
||||
check_eq(Editor_state.next_history, 2, 'F - test_undo_move_point/next_history')
|
||||
check_eq(p2.x, 35, 'F - test_undo_move_point/x')
|
||||
check_eq(p2.y, 36, 'F - test_undo_move_point/y')
|
||||
-- wait until save
|
||||
App.wait_fake_time(3.1)
|
||||
edit.update(Editor_state, 0)
|
||||
-- undo is saved
|
||||
load_from_disk(Editor_state)
|
||||
Text.redraw_all(Editor_state)
|
||||
local p2 = Editor_state.lines[1].points[drawing.shapes[1].p2]
|
||||
check_eq(p2.x, 35, 'F - test_undo_move_point/save/x')
|
||||
check_eq(p2.y, 36, 'F - test_undo_move_point/save/y')
|
||||
end
|
||||
|
||||
function test_undo_delete_point()
|
||||
io.write('\ntest_undo_delete_point')
|
||||
-- create a drawing with two lines connected at a point
|
||||
App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixels
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.filename = 'foo'
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.current_drawing_mode = 'line'
|
||||
edit.draw(Editor_state)
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+55, Editor_state.top+Drawing_padding_top+26, 1)
|
||||
local drawing = Editor_state.lines[1]
|
||||
check_eq(#drawing.shapes, 2, 'F - test_undo_delete_point/baseline/#shapes')
|
||||
check_eq(drawing.shapes[1].mode, 'line', 'F - test_undo_delete_point/baseline/shape:1')
|
||||
check_eq(drawing.shapes[2].mode, 'line', 'F - test_undo_delete_point/baseline/shape:2')
|
||||
-- hover on the common point and delete
|
||||
App.mouse_move(Editor_state.left+35, Editor_state.top+Drawing_padding_top+36)
|
||||
edit.run_after_keychord(Editor_state, 'C-d')
|
||||
check_eq(drawing.shapes[1].mode, 'deleted', 'F - test_undo_delete_point/shape:1')
|
||||
check_eq(drawing.shapes[2].mode, 'deleted', 'F - test_undo_delete_point/shape:2')
|
||||
-- undo
|
||||
edit.run_after_keychord(Editor_state, 'C-z')
|
||||
local drawing = Editor_state.lines[1]
|
||||
local p2 = drawing.points[drawing.shapes[1].p2]
|
||||
check_eq(Editor_state.next_history, 3, 'F - test_undo_move_point/next_history')
|
||||
check_eq(drawing.shapes[1].mode, 'line', 'F - test_undo_delete_point/shape:1')
|
||||
check_eq(drawing.shapes[2].mode, 'line', 'F - test_undo_delete_point/shape:2')
|
||||
-- wait until save
|
||||
App.wait_fake_time(3.1)
|
||||
edit.update(Editor_state, 0)
|
||||
-- undo is saved
|
||||
load_from_disk(Editor_state)
|
||||
Text.redraw_all(Editor_state)
|
||||
check_eq(#Editor_state.lines[1].shapes, 2, 'F - test_undo_delete_point/save')
|
||||
end
|
234
edit.lua
234
edit.lua
|
@ -1,60 +1,24 @@
|
|||
-- some constants people might like to tweak
|
||||
Text_color = {r=0, g=0, b=0}
|
||||
Cursor_color = {r=1, g=0, b=0}
|
||||
Stroke_color = {r=0, g=0, b=0}
|
||||
Current_stroke_color = {r=0.7, g=0.7, b=0.7} -- in process of being drawn
|
||||
Current_name_background_color = {r=1, g=0, b=0, a=0.1} -- name currently being edited
|
||||
Focus_stroke_color = {r=1, g=0, b=0} -- what mouse is hovering over
|
||||
Highlight_color = {r=0.7, g=0.7, b=0.9} -- selected text
|
||||
Icon_color = {r=0.7, g=0.7, b=0.7} -- color of current mode icon in drawings
|
||||
Help_color = {r=0, g=0.5, b=0}
|
||||
Help_background_color = {r=0, g=0.5, b=0, a=0.1}
|
||||
|
||||
Margin_top = 15
|
||||
Margin_left = 25
|
||||
Margin_right = 25
|
||||
|
||||
Drawing_padding_top = 10
|
||||
Drawing_padding_bottom = 10
|
||||
Drawing_padding_height = Drawing_padding_top + Drawing_padding_bottom
|
||||
|
||||
Same_point_distance = 4 -- pixel distance at which two points are considered the same
|
||||
|
||||
utf8 = require 'utf8'
|
||||
|
||||
require 'file'
|
||||
require 'text'
|
||||
require 'drawing'
|
||||
require 'geom'
|
||||
require 'help'
|
||||
require 'icons'
|
||||
|
||||
edit = {}
|
||||
|
||||
-- run in both tests and a real run
|
||||
function edit.initialize_state(top, left, right, font_height, line_height) -- currently always draws to bottom of screen
|
||||
local result = {
|
||||
-- a line is either text or a drawing
|
||||
-- a text is a table with:
|
||||
-- mode = 'text',
|
||||
-- string data,
|
||||
-- a drawing is a table with:
|
||||
-- mode = 'drawing'
|
||||
-- a (y) coord in pixels (updated while painting screen),
|
||||
-- a (h)eight,
|
||||
-- an array of points, and
|
||||
-- an array of shapes
|
||||
-- a shape is a table containing:
|
||||
-- a mode
|
||||
-- an array points for mode 'freehand' (raw x,y coords; freehand drawings don't pollute the points array of a drawing)
|
||||
-- an array vertices for mode 'polygon', 'rectangle', 'square'
|
||||
-- p1, p2 for mode 'line'
|
||||
-- center, radius for mode 'circle'
|
||||
-- center, radius, start_angle, end_angle for mode 'arc'
|
||||
-- Unless otherwise specified, coord fields are normalized; a drawing is always 256 units wide
|
||||
-- The field names are carefully chosen so that switching modes in midstream
|
||||
-- remembers previously entered points where that makes sense.
|
||||
lines = {{mode='text', data=''}}, -- array of lines
|
||||
lines = {''}, -- array of strings
|
||||
|
||||
-- Lines can be too long to fit on screen, in which case they _wrap_ into
|
||||
-- multiple _screen lines_.
|
||||
|
@ -91,9 +55,6 @@ function edit.initialize_state(top, left, right, font_height, line_height) -- c
|
|||
cursor_x = 0,
|
||||
cursor_y = 0,
|
||||
|
||||
current_drawing_mode = 'line',
|
||||
previous_drawing_mode = nil, -- extra state for some ephemeral modes like moving/deleting/naming points
|
||||
|
||||
font_height = font_height,
|
||||
line_height = line_height,
|
||||
em = App.newText(love.graphics.getFont(), 'm'), -- widest possible character width
|
||||
|
@ -118,15 +79,6 @@ function edit.initialize_state(top, left, right, font_height, line_height) -- c
|
|||
return result
|
||||
end -- App.initialize_state
|
||||
|
||||
function edit.fixup_cursor(State)
|
||||
for i,line in ipairs(State.lines) do
|
||||
if line.mode == 'text' then
|
||||
State.cursor1.line = i
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function edit.draw(State)
|
||||
App.color(Text_color)
|
||||
assert(#State.lines == #State.line_cache)
|
||||
|
@ -142,51 +94,25 @@ function edit.draw(State)
|
|||
--? print('draw:', y, line_index, line)
|
||||
if y + State.line_height > App.screen.height then break end
|
||||
State.screen_bottom1.line = line_index
|
||||
if line.mode == 'text' then
|
||||
--? print('text.draw', y, line_index)
|
||||
local startpos = 1
|
||||
if line_index == State.screen_top1.line then
|
||||
startpos = State.screen_top1.pos
|
||||
end
|
||||
if line.data == '' then
|
||||
-- button to insert new drawing
|
||||
button('draw', {x=4,y=y+4, w=12,h=12, color={1,1,0},
|
||||
icon = icon.insert_drawing,
|
||||
onpress1 = function()
|
||||
Drawing.before = snapshot(State, line_index-1, line_index)
|
||||
table.insert(State.lines, line_index, {mode='drawing', y=y, h=256/2, points={}, shapes={}, pending={}})
|
||||
table.insert(State.line_cache, line_index, {})
|
||||
if State.cursor1.line >= line_index then
|
||||
State.cursor1.line = State.cursor1.line+1
|
||||
end
|
||||
schedule_save(State)
|
||||
record_undo_event(State, {before=Drawing.before, after=snapshot(State, line_index-1, line_index+1)})
|
||||
end,
|
||||
})
|
||||
end
|
||||
y, State.screen_bottom1.pos = Text.draw(State, line_index, y, startpos)
|
||||
y = y + State.line_height
|
||||
--? print('=> y', y)
|
||||
elseif line.mode == 'drawing' then
|
||||
y = y+Drawing_padding_top
|
||||
Drawing.draw(State, line_index, y)
|
||||
y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottom
|
||||
else
|
||||
print(line.mode)
|
||||
assert(false)
|
||||
--? print('text.draw', y, line_index)
|
||||
local startpos = 1
|
||||
if line_index == State.screen_top1.line then
|
||||
startpos = State.screen_top1.pos
|
||||
end
|
||||
y, State.screen_bottom1.pos = Text.draw(State, line_index, y, startpos)
|
||||
y = y + State.line_height
|
||||
--? print('=> y', y)
|
||||
end
|
||||
if State.cursor_y == -1 then
|
||||
State.cursor_y = App.screen.height
|
||||
end
|
||||
--? print('screen bottom: '..tostring(State.screen_bottom1.pos)..' in '..tostring(State.lines[State.screen_bottom1.line].data))
|
||||
--? print('screen bottom: '..tostring(State.screen_bottom1.pos)..' in '..tostring(State.lines[State.screen_bottom1.line]))
|
||||
if State.search_term then
|
||||
Text.draw_search_bar(State)
|
||||
end
|
||||
end
|
||||
|
||||
function edit.update(State, dt)
|
||||
Drawing.update(State, dt)
|
||||
if State.next_save and State.next_save < App.getTime() then
|
||||
save_to_disk(State)
|
||||
State.next_save = nil
|
||||
|
@ -212,36 +138,25 @@ function edit.mouse_pressed(State, x,y, mouse_button)
|
|||
propagate_to_button_handlers(x,y, mouse_button)
|
||||
|
||||
for line_index,line in ipairs(State.lines) do
|
||||
if line.mode == 'text' then
|
||||
if Text.in_line(State, line_index, x,y) then
|
||||
-- delicate dance between cursor, selection and old cursor/selection
|
||||
-- scenarios:
|
||||
-- regular press+release: sets cursor, clears selection
|
||||
-- shift press+release:
|
||||
-- sets selection to old cursor if not set otherwise leaves it untouched
|
||||
-- sets cursor
|
||||
-- press and hold to start a selection: sets selection on press, cursor on release
|
||||
-- press and hold, then press shift: ignore shift
|
||||
-- i.e. mouse_released should never look at shift state
|
||||
State.old_cursor1 = State.cursor1
|
||||
State.old_selection1 = State.selection1
|
||||
State.mousepress_shift = App.shift_down()
|
||||
State.selection1 = {
|
||||
line=line_index,
|
||||
pos=Text.to_pos_on_line(State, line_index, x, y),
|
||||
}
|
||||
--? print('selection', State.selection1.line, State.selection1.pos)
|
||||
break
|
||||
end
|
||||
elseif line.mode == 'drawing' then
|
||||
local line_cache = State.line_cache[line_index]
|
||||
if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) then
|
||||
State.lines.current_drawing_index = line_index
|
||||
State.lines.current_drawing = line
|
||||
Drawing.before = snapshot(State, line_index)
|
||||
Drawing.mouse_pressed(State, line_index, x,y, mouse_button)
|
||||
break
|
||||
end
|
||||
if Text.in_line(State, line_index, x,y) then
|
||||
-- delicate dance between cursor, selection and old cursor/selection
|
||||
-- scenarios:
|
||||
-- regular press+release: sets cursor, clears selection
|
||||
-- shift press+release:
|
||||
-- sets selection to old cursor if not set otherwise leaves it untouched
|
||||
-- sets cursor
|
||||
-- press and hold to start a selection: sets selection on press, cursor on release
|
||||
-- press and hold, then press shift: ignore shift
|
||||
-- i.e. mouse_released should never look at shift state
|
||||
State.old_cursor1 = State.cursor1
|
||||
State.old_selection1 = State.selection1
|
||||
State.mousepress_shift = App.shift_down()
|
||||
State.selection1 = {
|
||||
line=line_index,
|
||||
pos=Text.to_pos_on_line(State, line_index, x, y),
|
||||
}
|
||||
--? print('selection', State.selection1.line, State.selection1.pos)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -249,40 +164,29 @@ end
|
|||
function edit.mouse_released(State, x,y, mouse_button)
|
||||
if State.search_term then return end
|
||||
--? print('release')
|
||||
if State.lines.current_drawing then
|
||||
Drawing.mouse_released(State, x,y, mouse_button)
|
||||
schedule_save(State)
|
||||
if Drawing.before then
|
||||
record_undo_event(State, {before=Drawing.before, after=snapshot(State, State.lines.current_drawing_index)})
|
||||
Drawing.before = nil
|
||||
end
|
||||
else
|
||||
for line_index,line in ipairs(State.lines) do
|
||||
if line.mode == 'text' then
|
||||
if Text.in_line(State, line_index, x,y) then
|
||||
--? print('reset selection')
|
||||
State.cursor1 = {
|
||||
line=line_index,
|
||||
pos=Text.to_pos_on_line(State, line_index, x, y),
|
||||
}
|
||||
--? print('cursor', State.cursor1.line, State.cursor1.pos)
|
||||
if State.mousepress_shift then
|
||||
if State.old_selection1.line == nil then
|
||||
State.selection1 = State.old_cursor1
|
||||
else
|
||||
State.selection1 = State.old_selection1
|
||||
end
|
||||
end
|
||||
State.old_cursor1, State.old_selection1, State.mousepress_shift = nil
|
||||
if eq(State.cursor1, State.selection1) then
|
||||
State.selection1 = {}
|
||||
end
|
||||
break
|
||||
for line_index,line in ipairs(State.lines) do
|
||||
if Text.in_line(State, line_index, x,y) then
|
||||
--? print('reset selection')
|
||||
State.cursor1 = {
|
||||
line=line_index,
|
||||
pos=Text.to_pos_on_line(State, line_index, x, y),
|
||||
}
|
||||
--? print('cursor', State.cursor1.line, State.cursor1.pos)
|
||||
if State.mousepress_shift then
|
||||
if State.old_selection1.line == nil then
|
||||
State.selection1 = State.old_cursor1
|
||||
else
|
||||
State.selection1 = State.old_selection1
|
||||
end
|
||||
end
|
||||
State.old_cursor1, State.old_selection1, State.mousepress_shift = nil
|
||||
if eq(State.cursor1, State.selection1) then
|
||||
State.selection1 = {}
|
||||
end
|
||||
break
|
||||
end
|
||||
--? print('selection:', State.selection1.line, State.selection1.pos)
|
||||
end
|
||||
--? print('selection:', State.selection1.line, State.selection1.pos)
|
||||
end
|
||||
|
||||
function edit.textinput(State, t)
|
||||
|
@ -291,12 +195,6 @@ function edit.textinput(State, t)
|
|||
State.search_term = State.search_term..t
|
||||
State.search_text = nil
|
||||
Text.search_next(State)
|
||||
elseif State.current_drawing_mode == 'name' then
|
||||
local before = snapshot(State, State.lines.current_drawing_index)
|
||||
local drawing = State.lines.current_drawing
|
||||
local p = drawing.points[drawing.pending.target_point]
|
||||
p.name = p.name..t
|
||||
record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
|
||||
else
|
||||
Text.textinput(State, t)
|
||||
end
|
||||
|
@ -305,7 +203,6 @@ end
|
|||
|
||||
function edit.keychord_pressed(State, chord, key)
|
||||
if State.selection1.line and
|
||||
not State.lines.current_drawing and
|
||||
-- printable character created using shift key => delete selection
|
||||
-- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys)
|
||||
(not App.shift_down() or utf8.len(key) == 1) and
|
||||
|
@ -413,42 +310,7 @@ function edit.keychord_pressed(State, chord, key)
|
|||
end
|
||||
schedule_save(State)
|
||||
record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
|
||||
-- dispatch to drawing or text
|
||||
elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then
|
||||
-- DON'T reset line_cache.starty here
|
||||
local drawing_index, drawing = Drawing.current_drawing(State)
|
||||
if drawing_index then
|
||||
local before = snapshot(State, drawing_index)
|
||||
Drawing.keychord_pressed(State, chord)
|
||||
record_undo_event(State, {before=before, after=snapshot(State, drawing_index)})
|
||||
schedule_save(State)
|
||||
end
|
||||
elseif chord == 'escape' and not App.mouse_down(1) then
|
||||
for _,line in ipairs(State.lines) do
|
||||
if line.mode == 'drawing' then
|
||||
line.show_help = false
|
||||
end
|
||||
end
|
||||
elseif State.current_drawing_mode == 'name' then
|
||||
if chord == 'return' then
|
||||
State.current_drawing_mode = State.previous_drawing_mode
|
||||
State.previous_drawing_mode = nil
|
||||
else
|
||||
local before = snapshot(State, State.lines.current_drawing_index)
|
||||
local drawing = State.lines.current_drawing
|
||||
local p = drawing.points[drawing.pending.target_point]
|
||||
if chord == 'escape' then
|
||||
p.name = nil
|
||||
record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
|
||||
elseif chord == 'backspace' then
|
||||
local len = utf8.len(p.name)
|
||||
local byte_offset = Text.offset(p.name, len-1)
|
||||
if len == 1 then byte_offset = 0 end
|
||||
p.name = string.sub(p.name, 1, byte_offset)
|
||||
record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
|
||||
end
|
||||
end
|
||||
schedule_save(State)
|
||||
-- dispatch to text
|
||||
else
|
||||
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
|
||||
Text.keychord_pressed(State, chord)
|
||||
|
|
133
file.lua
133
file.lua
|
@ -12,15 +12,11 @@ function load_from_file(infile)
|
|||
while true do
|
||||
local line = infile_next_line()
|
||||
if line == nil then break end
|
||||
if line == '```lines' then -- inflexible with whitespace since these files are always autogenerated
|
||||
table.insert(result, load_drawing(infile_next_line))
|
||||
else
|
||||
table.insert(result, {mode='text', data=line})
|
||||
end
|
||||
table.insert(result, line)
|
||||
end
|
||||
end
|
||||
if #result == 0 then
|
||||
table.insert(result, {mode='text', data=''})
|
||||
table.insert(result, '')
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
@ -31,82 +27,11 @@ function save_to_disk(State)
|
|||
error('failed to write to "'..State.filename..'"')
|
||||
end
|
||||
for _,line in ipairs(State.lines) do
|
||||
if line.mode == 'drawing' then
|
||||
store_drawing(outfile, line)
|
||||
else
|
||||
outfile:write(line.data, '\n')
|
||||
end
|
||||
outfile:write(line, '\n')
|
||||
end
|
||||
outfile:close()
|
||||
end
|
||||
|
||||
json = require 'json'
|
||||
function load_drawing(infile_next_line)
|
||||
local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}
|
||||
while true do
|
||||
local line = infile_next_line()
|
||||
assert(line)
|
||||
if line == '```' then break end
|
||||
local shape = json.decode(line)
|
||||
if shape.mode == 'freehand' then
|
||||
-- no changes needed
|
||||
elseif shape.mode == 'line' or shape.mode == 'manhattan' then
|
||||
local name = shape.p1.name
|
||||
shape.p1 = Drawing.insert_point(drawing.points, shape.p1.x, shape.p1.y)
|
||||
drawing.points[shape.p1].name = name
|
||||
name = shape.p2.name
|
||||
shape.p2 = Drawing.insert_point(drawing.points, shape.p2.x, shape.p2.y)
|
||||
drawing.points[shape.p2].name = name
|
||||
elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
|
||||
for i,p in ipairs(shape.vertices) do
|
||||
local name = p.name
|
||||
shape.vertices[i] = Drawing.insert_point(drawing.points, p.x,p.y)
|
||||
drawing.points[shape.vertices[i]].name = name
|
||||
end
|
||||
elseif shape.mode == 'circle' or shape.mode == 'arc' then
|
||||
local name = shape.center.name
|
||||
shape.center = Drawing.insert_point(drawing.points, shape.center.x,shape.center.y)
|
||||
drawing.points[shape.center].name = name
|
||||
elseif shape.mode == 'deleted' then
|
||||
-- ignore
|
||||
else
|
||||
print(shape.mode)
|
||||
assert(false)
|
||||
end
|
||||
table.insert(drawing.shapes, shape)
|
||||
end
|
||||
return drawing
|
||||
end
|
||||
|
||||
function store_drawing(outfile, drawing)
|
||||
outfile:write('```lines\n')
|
||||
for _,shape in ipairs(drawing.shapes) do
|
||||
if shape.mode == 'freehand' then
|
||||
outfile:write(json.encode(shape), '\n')
|
||||
elseif shape.mode == 'line' or shape.mode == 'manhattan' then
|
||||
local line = json.encode({mode=shape.mode, p1=drawing.points[shape.p1], p2=drawing.points[shape.p2]})
|
||||
outfile:write(line, '\n')
|
||||
elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
|
||||
local obj = {mode=shape.mode, vertices={}}
|
||||
for _,p in ipairs(shape.vertices) do
|
||||
table.insert(obj.vertices, drawing.points[p])
|
||||
end
|
||||
local line = json.encode(obj)
|
||||
outfile:write(line, '\n')
|
||||
elseif shape.mode == 'circle' then
|
||||
outfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius}), '\n')
|
||||
elseif shape.mode == 'arc' then
|
||||
outfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius, start_angle=shape.start_angle, end_angle=shape.end_angle}), '\n')
|
||||
elseif shape.mode == 'deleted' then
|
||||
-- ignore
|
||||
else
|
||||
print(shape.mode)
|
||||
assert(false)
|
||||
end
|
||||
end
|
||||
outfile:write('```\n')
|
||||
end
|
||||
|
||||
-- for tests
|
||||
function load_array(a)
|
||||
local result = {}
|
||||
|
@ -115,58 +40,10 @@ function load_array(a)
|
|||
while true do
|
||||
i,line = next_line(a, i)
|
||||
if i == nil then break end
|
||||
--? print(line)
|
||||
if line == '```lines' then -- inflexible with whitespace since these files are always autogenerated
|
||||
--? print('inserting drawing')
|
||||
i, drawing = load_drawing_from_array(next_line, a, i)
|
||||
--? print('i now', i)
|
||||
table.insert(result, drawing)
|
||||
else
|
||||
--? print('inserting text')
|
||||
table.insert(result, {mode='text', data=line})
|
||||
end
|
||||
table.insert(result, line)
|
||||
end
|
||||
if #result == 0 then
|
||||
table.insert(result, {mode='text', data=''})
|
||||
table.insert(result, '')
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function load_drawing_from_array(iter, a, i)
|
||||
local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}
|
||||
local line
|
||||
while true do
|
||||
i, line = iter(a, i)
|
||||
assert(i)
|
||||
--? print(i)
|
||||
if line == '```' then break end
|
||||
local shape = json.decode(line)
|
||||
if shape.mode == 'freehand' then
|
||||
-- no changes needed
|
||||
elseif shape.mode == 'line' or shape.mode == 'manhattan' then
|
||||
local name = shape.p1.name
|
||||
shape.p1 = Drawing.insert_point(drawing.points, shape.p1.x, shape.p1.y)
|
||||
drawing.points[shape.p1].name = name
|
||||
name = shape.p2.name
|
||||
shape.p2 = Drawing.insert_point(drawing.points, shape.p2.x, shape.p2.y)
|
||||
drawing.points[shape.p2].name = name
|
||||
elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
|
||||
for i,p in ipairs(shape.vertices) do
|
||||
local name = p.name
|
||||
shape.vertices[i] = Drawing.insert_point(drawing.points, p.x,p.y)
|
||||
drawing.points[shape.vertices[i]].name = name
|
||||
end
|
||||
elseif shape.mode == 'circle' or shape.mode == 'arc' then
|
||||
local name = shape.center.name
|
||||
shape.center = Drawing.insert_point(drawing.points, shape.center.x,shape.center.y)
|
||||
drawing.points[shape.center].name = name
|
||||
elseif shape.mode == 'deleted' then
|
||||
-- ignore
|
||||
else
|
||||
print(shape.mode)
|
||||
assert(false)
|
||||
end
|
||||
table.insert(drawing.shapes, shape)
|
||||
end
|
||||
return i, drawing
|
||||
end
|
||||
|
|
168
geom.lua
168
geom.lua
|
@ -1,168 +0,0 @@
|
|||
geom = {}
|
||||
|
||||
function geom.on_shape(x,y, drawing, shape)
|
||||
if shape.mode == 'freehand' then
|
||||
return geom.on_freehand(x,y, drawing, shape)
|
||||
elseif shape.mode == 'line' then
|
||||
return geom.on_line(x,y, drawing, shape)
|
||||
elseif shape.mode == 'manhattan' then
|
||||
local p1 = drawing.points[shape.p1]
|
||||
local p2 = drawing.points[shape.p2]
|
||||
if p1.x == p2.x then
|
||||
if x ~= p1.x then return false end
|
||||
local y1,y2 = p1.y, p2.y
|
||||
if y1 > y2 then
|
||||
y1,y2 = y2,y1
|
||||
end
|
||||
return y >= y1-2 and y <= y2+2
|
||||
elseif p1.y == p2.y then
|
||||
if y ~= p1.y then return false end
|
||||
local x1,x2 = p1.x, p2.x
|
||||
if x1 > x2 then
|
||||
x1,x2 = x2,x1
|
||||
end
|
||||
return x >= x1-2 and x <= x2+2
|
||||
end
|
||||
elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
|
||||
return geom.on_polygon(x,y, drawing, shape)
|
||||
elseif shape.mode == 'circle' then
|
||||
local center = drawing.points[shape.center]
|
||||
local dist = geom.dist(center.x,center.y, x,y)
|
||||
return dist > shape.radius*0.95 and dist < shape.radius*1.05
|
||||
elseif shape.mode == 'arc' then
|
||||
local center = drawing.points[shape.center]
|
||||
local dist = geom.dist(center.x,center.y, x,y)
|
||||
if dist < shape.radius*0.95 or dist > shape.radius*1.05 then
|
||||
return false
|
||||
end
|
||||
return geom.angle_between(center.x,center.y, x,y, shape.start_angle,shape.end_angle)
|
||||
elseif shape.mode == 'deleted' then
|
||||
else
|
||||
print(shape.mode)
|
||||
assert(false)
|
||||
end
|
||||
end
|
||||
|
||||
function geom.on_freehand(x,y, drawing, shape)
|
||||
local prev
|
||||
for _,p in ipairs(shape.points) do
|
||||
if prev then
|
||||
if geom.on_line(x,y, drawing, {p1=prev, p2=p}) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
prev = p
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function geom.on_line(x,y, drawing, shape)
|
||||
local p1,p2
|
||||
if type(shape.p1) == 'number' then
|
||||
p1 = drawing.points[shape.p1]
|
||||
p2 = drawing.points[shape.p2]
|
||||
else
|
||||
p1 = shape.p1
|
||||
p2 = shape.p2
|
||||
end
|
||||
if p1.x == p2.x then
|
||||
if math.abs(p1.x-x) > 2 then
|
||||
return false
|
||||
end
|
||||
local y1,y2 = p1.y,p2.y
|
||||
if y1 > y2 then
|
||||
y1,y2 = y2,y1
|
||||
end
|
||||
return y >= y1-2 and y <= y2+2
|
||||
end
|
||||
-- has the right slope and intercept
|
||||
local m = (p2.y - p1.y) / (p2.x - p1.x)
|
||||
local yp = p1.y + m*(x-p1.x)
|
||||
if yp < y-2 or yp > y+2 then
|
||||
return false
|
||||
end
|
||||
-- between endpoints
|
||||
local k = (x-p1.x) / (p2.x-p1.x)
|
||||
return k > -0.005 and k < 1.005
|
||||
end
|
||||
|
||||
function geom.on_polygon(x,y, drawing, shape)
|
||||
local prev
|
||||
for _,p in ipairs(shape.vertices) do
|
||||
if prev then
|
||||
if geom.on_line(x,y, drawing, {p1=prev, p2=p}) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
prev = p
|
||||
end
|
||||
return geom.on_line(x,y, drawing, {p1=shape.vertices[1], p2=shape.vertices[#shape.vertices]})
|
||||
end
|
||||
|
||||
-- are (x3,y3) and (x4,y4) on the same side of the line between (x1,y1) and (x2,y2)
|
||||
function geom.same_side(x1,y1, x2,y2, x3,y3, x4,y4)
|
||||
if x1 == x2 then
|
||||
return math.sign(x3-x1) == math.sign(x4-x1)
|
||||
end
|
||||
if y1 == y2 then
|
||||
return math.sign(y3-y1) == math.sign(y4-y1)
|
||||
end
|
||||
local m = (y2-y1)/(x2-x1)
|
||||
return math.sign(m*(x3-x1) + y1-y3) == math.sign(m*(x4-x1) + y1-y4)
|
||||
end
|
||||
|
||||
function math.sign(x)
|
||||
if x > 0 then
|
||||
return 1
|
||||
elseif x == 0 then
|
||||
return 0
|
||||
elseif x < 0 then
|
||||
return -1
|
||||
end
|
||||
end
|
||||
|
||||
function geom.angle_with_hint(x1, y1, x2, y2, hint)
|
||||
local result = geom.angle(x1,y1, x2,y2)
|
||||
if hint then
|
||||
-- Smooth the discontinuity where angle goes from positive to negative.
|
||||
-- The hint is a memory of which way we drew it last time.
|
||||
while result > hint+math.pi/10 do
|
||||
result = result-math.pi*2
|
||||
end
|
||||
while result < hint-math.pi/10 do
|
||||
result = result+math.pi*2
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- result is from -π/2 to 3π/2, approximately adding math.atan2 from Lua 5.3
|
||||
-- (LÖVE is Lua 5.1)
|
||||
function geom.angle(x1,y1, x2,y2)
|
||||
local result = math.atan((y2-y1)/(x2-x1))
|
||||
if x2 < x1 then
|
||||
result = result+math.pi
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- is the line between x,y and cx,cy at an angle between s and e?
|
||||
function geom.angle_between(ox,oy, x,y, s,e)
|
||||
local angle = geom.angle(ox,oy, x,y)
|
||||
if s > e then
|
||||
s,e = e,s
|
||||
end
|
||||
-- I'm not sure this is right or ideal..
|
||||
angle = angle-math.pi*2
|
||||
if s <= angle and angle <= e then
|
||||
return true
|
||||
end
|
||||
angle = angle+math.pi*2
|
||||
if s <= angle and angle <= e then
|
||||
return true
|
||||
end
|
||||
angle = angle+math.pi*2
|
||||
return s <= angle and angle <= e
|
||||
end
|
||||
|
||||
function geom.dist(x1,y1, x2,y2) return ((x2-x1)^2+(y2-y1)^2)^0.5 end
|
156
help.lua
156
help.lua
|
@ -1,156 +0,0 @@
|
|||
function draw_help_without_mouse_pressed(State, drawing_index)
|
||||
local drawing = State.lines[drawing_index]
|
||||
local line_cache = State.line_cache[drawing_index]
|
||||
App.color(Help_color)
|
||||
local y = line_cache.starty+10
|
||||
love.graphics.print("Things you can do:", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
love.graphics.print("* Press the mouse button to start drawing a "..current_shape(State), State.left+30,y)
|
||||
y = y + State.line_height
|
||||
love.graphics.print("* Hover on a point and press 'ctrl+u' to pick it up and start moving it,", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
love.graphics.print("then press the mouse button to drop it", State.left+30+bullet_indent(),y)
|
||||
y = y + State.line_height
|
||||
love.graphics.print("* Hover on a point and press 'ctrl+n', type a name, then press 'enter'", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
love.graphics.print("* Hover on a point or shape and press 'ctrl+d' to delete it", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
if State.current_drawing_mode ~= 'freehand' then
|
||||
love.graphics.print("* Press 'ctrl+p' to switch to drawing freehand strokes", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
end
|
||||
if State.current_drawing_mode ~= 'line' then
|
||||
love.graphics.print("* Press 'ctrl+l' to switch to drawing lines", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
end
|
||||
if State.current_drawing_mode ~= 'manhattan' then
|
||||
love.graphics.print("* Press 'ctrl+m' to switch to drawing horizontal/vertical lines", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
end
|
||||
if State.current_drawing_mode ~= 'circle' then
|
||||
love.graphics.print("* Press 'ctrl+o' to switch to drawing circles/arcs", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
end
|
||||
if State.current_drawing_mode ~= 'polygon' then
|
||||
love.graphics.print("* Press 'ctrl+g' to switch to drawing polygons", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
end
|
||||
if State.current_drawing_mode ~= 'rectangle' then
|
||||
love.graphics.print("* Press 'ctrl+r' to switch to drawing rectangles", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
end
|
||||
if State.current_drawing_mode ~= 'square' then
|
||||
love.graphics.print("* Press 'ctrl+s' to switch to drawing squares", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
end
|
||||
love.graphics.print("* Press 'ctrl+=' or 'ctrl+-' to zoom in or out, ctrl+0 to reset zoom", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
love.graphics.print("Press 'esc' now to hide this message", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
App.color(Help_background_color)
|
||||
love.graphics.rectangle('fill', State.left,line_cache.starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-line_cache.starty))
|
||||
end
|
||||
|
||||
function draw_help_with_mouse_pressed(State, drawing_index)
|
||||
local drawing = State.lines[drawing_index]
|
||||
local line_cache = State.line_cache[drawing_index]
|
||||
App.color(Help_color)
|
||||
local y = line_cache.starty+10
|
||||
love.graphics.print("You're currently drawing a "..current_shape(State, drawing.pending), State.left+30,y)
|
||||
y = y + State.line_height
|
||||
love.graphics.print('Things you can do now:', State.left+30,y)
|
||||
y = y + State.line_height
|
||||
if State.current_drawing_mode == 'freehand' then
|
||||
love.graphics.print('* Release the mouse button to finish drawing the stroke', State.left+30,y)
|
||||
y = y + State.line_height
|
||||
elseif State.current_drawing_mode == 'line' or State.current_drawing_mode == 'manhattan' then
|
||||
love.graphics.print('* Release the mouse button to finish drawing the line', State.left+30,y)
|
||||
y = y + State.line_height
|
||||
elseif State.current_drawing_mode == 'circle' then
|
||||
if drawing.pending.mode == 'circle' then
|
||||
love.graphics.print('* Release the mouse button to finish drawing the circle', State.left+30,y)
|
||||
y = y + State.line_height
|
||||
love.graphics.print("* Press 'a' to draw just an arc of a circle", State.left+30,y)
|
||||
else
|
||||
love.graphics.print('* Release the mouse button to finish drawing the arc', State.left+30,y)
|
||||
end
|
||||
y = y + State.line_height
|
||||
elseif State.current_drawing_mode == 'polygon' then
|
||||
love.graphics.print('* Release the mouse button to finish drawing the polygon', State.left+30,y)
|
||||
y = y + State.line_height
|
||||
love.graphics.print("* Press 'p' to add a vertex to the polygon", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
elseif State.current_drawing_mode == 'rectangle' then
|
||||
if #drawing.pending.vertices < 2 then
|
||||
love.graphics.print("* Press 'p' to add a vertex to the rectangle", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
else
|
||||
love.graphics.print('* Release the mouse button to finish drawing the rectangle', State.left+30,y)
|
||||
y = y + State.line_height
|
||||
love.graphics.print("* Press 'p' to replace the second vertex of the rectangle", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
end
|
||||
elseif State.current_drawing_mode == 'square' then
|
||||
if #drawing.pending.vertices < 2 then
|
||||
love.graphics.print("* Press 'p' to add a vertex to the square", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
else
|
||||
love.graphics.print('* Release the mouse button to finish drawing the square', State.left+30,y)
|
||||
y = y + State.line_height
|
||||
love.graphics.print("* Press 'p' to replace the second vertex of the square", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
end
|
||||
end
|
||||
love.graphics.print("* Press 'esc' then release the mouse button to cancel the current shape", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
y = y + State.line_height
|
||||
if State.current_drawing_mode ~= 'line' then
|
||||
love.graphics.print("* Press 'l' to switch to drawing lines", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
end
|
||||
if State.current_drawing_mode ~= 'manhattan' then
|
||||
love.graphics.print("* Press 'm' to switch to drawing horizontal/vertical lines", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
end
|
||||
if State.current_drawing_mode ~= 'circle' then
|
||||
love.graphics.print("* Press 'o' to switch to drawing circles/arcs", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
end
|
||||
if State.current_drawing_mode ~= 'polygon' then
|
||||
love.graphics.print("* Press 'g' to switch to drawing polygons", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
end
|
||||
if State.current_drawing_mode ~= 'rectangle' then
|
||||
love.graphics.print("* Press 'r' to switch to drawing rectangles", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
end
|
||||
if State.current_drawing_mode ~= 'square' then
|
||||
love.graphics.print("* Press 's' to switch to drawing squares", State.left+30,y)
|
||||
y = y + State.line_height
|
||||
end
|
||||
App.color(Help_background_color)
|
||||
love.graphics.rectangle('fill', State.left,line_cache.starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-line_cache.starty))
|
||||
end
|
||||
|
||||
function current_shape(State, shape)
|
||||
if State.current_drawing_mode == 'freehand' then
|
||||
return 'freehand stroke'
|
||||
elseif State.current_drawing_mode == 'line' then
|
||||
return 'straight line'
|
||||
elseif State.current_drawing_mode == 'manhattan' then
|
||||
return 'horizontal/vertical line'
|
||||
elseif State.current_drawing_mode == 'circle' and shape and shape.start_angle then
|
||||
return 'arc'
|
||||
else
|
||||
return State.current_drawing_mode
|
||||
end
|
||||
end
|
||||
|
||||
_bullet_indent = nil
|
||||
function bullet_indent()
|
||||
if _bullet_indent == nil then
|
||||
local text = love.graphics.newText(love.graphics.getFont(), '* ')
|
||||
_bullet_indent = text:getWidth()
|
||||
end
|
||||
return _bullet_indent
|
||||
end
|
58
icons.lua
58
icons.lua
|
@ -1,58 +0,0 @@
|
|||
icon = {}
|
||||
|
||||
function icon.insert_drawing(x, y)
|
||||
App.color(Icon_color)
|
||||
love.graphics.rectangle('line', x,y, 12,12)
|
||||
love.graphics.line(4,y+6, 16,y+6)
|
||||
love.graphics.line(10,y, 10,y+12)
|
||||
end
|
||||
|
||||
function icon.freehand(x, y)
|
||||
love.graphics.line(x+4,y+7,x+5,y+5)
|
||||
love.graphics.line(x+5,y+5,x+7,y+4)
|
||||
love.graphics.line(x+7,y+4,x+9,y+3)
|
||||
love.graphics.line(x+9,y+3,x+10,y+5)
|
||||
love.graphics.line(x+10,y+5,x+12,y+6)
|
||||
love.graphics.line(x+12,y+6,x+13,y+8)
|
||||
love.graphics.line(x+13,y+8,x+13,y+10)
|
||||
love.graphics.line(x+13,y+10,x+14,y+12)
|
||||
love.graphics.line(x+14,y+12,x+15,y+14)
|
||||
love.graphics.line(x+15,y+14,x+15,y+16)
|
||||
end
|
||||
|
||||
function icon.line(x, y)
|
||||
love.graphics.line(x+4,y+2, x+16,y+18)
|
||||
end
|
||||
|
||||
function icon.manhattan(x, y)
|
||||
love.graphics.line(x+4,y+20, x+4,y+2)
|
||||
love.graphics.line(x+4,y+2, x+10,y+2)
|
||||
love.graphics.line(x+10,y+2, x+10,y+10)
|
||||
love.graphics.line(x+10,y+10, x+18,y+10)
|
||||
end
|
||||
|
||||
function icon.polygon(x, y)
|
||||
love.graphics.line(x+8,y+2, x+14,y+2)
|
||||
love.graphics.line(x+14,y+2, x+18,y+10)
|
||||
love.graphics.line(x+18,y+10, x+10,y+18)
|
||||
love.graphics.line(x+10,y+18, x+4,y+12)
|
||||
love.graphics.line(x+4,y+12, x+8,y+2)
|
||||
end
|
||||
|
||||
function icon.rectangle(x, y)
|
||||
love.graphics.line(x+4,y+8, x+4,y+16)
|
||||
love.graphics.line(x+4,y+16, x+16,y+16)
|
||||
love.graphics.line(x+16,y+16, x+16,y+8)
|
||||
love.graphics.line(x+16,y+8, x+4,y+8)
|
||||
end
|
||||
|
||||
function icon.square(x, y)
|
||||
love.graphics.line(x+6,y+6, x+6,y+16)
|
||||
love.graphics.line(x+6,y+16, x+16,y+16)
|
||||
love.graphics.line(x+16,y+16, x+16,y+6)
|
||||
love.graphics.line(x+16,y+6, x+6,y+6)
|
||||
end
|
||||
|
||||
function icon.circle(x, y)
|
||||
love.graphics.circle('line', x+10,y+10, 8)
|
||||
end
|
6
main.lua
6
main.lua
|
@ -1,4 +1,5 @@
|
|||
utf8 = require 'utf8'
|
||||
json = require 'json'
|
||||
|
||||
require 'app'
|
||||
require 'test'
|
||||
|
@ -43,13 +44,9 @@ function App.initialize(arg)
|
|||
Text.redraw_all(Editor_state)
|
||||
Editor_state.screen_top1 = {line=1, pos=1}
|
||||
Editor_state.cursor1 = {line=1, pos=1}
|
||||
edit.fixup_cursor(Editor_state)
|
||||
else
|
||||
load_from_disk(Editor_state)
|
||||
Text.redraw_all(Editor_state)
|
||||
if Editor_state.cursor1.line > #Editor_state.lines or Editor_state.lines[Editor_state.cursor1.line].mode ~= 'text' then
|
||||
edit.fixup_cursor(Editor_state)
|
||||
end
|
||||
end
|
||||
love.window.setTitle('lines.love - '..Editor_state.filename)
|
||||
|
||||
|
@ -127,7 +124,6 @@ function App.filedropped(file)
|
|||
file:open('r')
|
||||
Editor_state.lines = load_from_file(file)
|
||||
file:close()
|
||||
edit.fixup_cursor(Editor_state)
|
||||
love.window.setTitle('lines.love - '..Editor_state.filename)
|
||||
end
|
||||
|
||||
|
|
|
@ -38,9 +38,9 @@ function test_drop_file()
|
|||
}
|
||||
App.filedropped(fake_dropped_file)
|
||||
check_eq(#Editor_state.lines, 3, 'F - test_drop_file/#lines')
|
||||
check_eq(Editor_state.lines[1].data, 'abc', 'F - test_drop_file/lines:1')
|
||||
check_eq(Editor_state.lines[2].data, 'def', 'F - test_drop_file/lines:2')
|
||||
check_eq(Editor_state.lines[3].data, 'ghi', 'F - test_drop_file/lines:3')
|
||||
check_eq(Editor_state.lines[1], 'abc', 'F - test_drop_file/lines:1')
|
||||
check_eq(Editor_state.lines[2], 'def', 'F - test_drop_file/lines:2')
|
||||
check_eq(Editor_state.lines[3], 'ghi', 'F - test_drop_file/lines:3')
|
||||
end
|
||||
|
||||
function test_drop_file_saves_previous()
|
||||
|
|
16
search.lua
16
search.lua
|
@ -21,14 +21,14 @@ end
|
|||
|
||||
function Text.search_next(State)
|
||||
-- search current line from cursor
|
||||
local pos = find(State.lines[State.cursor1.line].data, State.search_term, State.cursor1.pos)
|
||||
local pos = find(State.lines[State.cursor1.line], State.search_term, State.cursor1.pos)
|
||||
if pos then
|
||||
State.cursor1.pos = pos
|
||||
end
|
||||
if pos == nil then
|
||||
-- search lines below cursor
|
||||
for i=State.cursor1.line+1,#State.lines do
|
||||
pos = find(State.lines[i].data, State.search_term)
|
||||
pos = find(State.lines[i], State.search_term)
|
||||
if pos then
|
||||
State.cursor1.line = i
|
||||
State.cursor1.pos = pos
|
||||
|
@ -39,7 +39,7 @@ function Text.search_next(State)
|
|||
if pos == nil then
|
||||
-- wrap around
|
||||
for i=1,State.cursor1.line-1 do
|
||||
pos = find(State.lines[i].data, State.search_term)
|
||||
pos = find(State.lines[i], State.search_term)
|
||||
if pos then
|
||||
State.cursor1.line = i
|
||||
State.cursor1.pos = pos
|
||||
|
@ -49,7 +49,7 @@ function Text.search_next(State)
|
|||
end
|
||||
if pos == nil then
|
||||
-- search current line until cursor
|
||||
pos = find(State.lines[State.cursor1.line].data, State.search_term)
|
||||
pos = find(State.lines[State.cursor1.line], State.search_term)
|
||||
if pos and pos < State.cursor1.pos then
|
||||
State.cursor1.pos = pos
|
||||
end
|
||||
|
@ -69,14 +69,14 @@ end
|
|||
|
||||
function Text.search_previous(State)
|
||||
-- search current line before cursor
|
||||
local pos = rfind(State.lines[State.cursor1.line].data, State.search_term, State.cursor1.pos-1)
|
||||
local pos = rfind(State.lines[State.cursor1.line], State.search_term, State.cursor1.pos-1)
|
||||
if pos then
|
||||
State.cursor1.pos = pos
|
||||
end
|
||||
if pos == nil then
|
||||
-- search lines above cursor
|
||||
for i=State.cursor1.line-1,1,-1 do
|
||||
pos = rfind(State.lines[i].data, State.search_term)
|
||||
pos = rfind(State.lines[i], State.search_term)
|
||||
if pos then
|
||||
State.cursor1.line = i
|
||||
State.cursor1.pos = pos
|
||||
|
@ -87,7 +87,7 @@ function Text.search_previous(State)
|
|||
if pos == nil then
|
||||
-- wrap around
|
||||
for i=#State.lines,State.cursor1.line+1,-1 do
|
||||
pos = rfind(State.lines[i].data, State.search_term)
|
||||
pos = rfind(State.lines[i], State.search_term)
|
||||
if pos then
|
||||
State.cursor1.line = i
|
||||
State.cursor1.pos = pos
|
||||
|
@ -97,7 +97,7 @@ function Text.search_previous(State)
|
|||
end
|
||||
if pos == nil then
|
||||
-- search current line after cursor
|
||||
pos = rfind(State.lines[State.cursor1.line].data, State.search_term)
|
||||
pos = rfind(State.lines[State.cursor1.line], State.search_term)
|
||||
if pos and pos > State.cursor1.pos then
|
||||
State.cursor1.pos = pos
|
||||
end
|
||||
|
|
40
select.lua
40
select.lua
|
@ -53,19 +53,19 @@ end
|
|||
-- Returns some intermediate computation useful elsewhere.
|
||||
function Text.draw_highlight(State, line, x,y, pos, lo,hi)
|
||||
if lo then
|
||||
local lo_offset = Text.offset(line.data, lo)
|
||||
local hi_offset = Text.offset(line.data, hi)
|
||||
local pos_offset = Text.offset(line.data, pos)
|
||||
local lo_offset = Text.offset(line, lo)
|
||||
local hi_offset = Text.offset(line, hi)
|
||||
local pos_offset = Text.offset(line, pos)
|
||||
local lo_px
|
||||
if pos == lo then
|
||||
lo_px = 0
|
||||
else
|
||||
local before = line.data:sub(pos_offset, lo_offset-1)
|
||||
local before = line:sub(pos_offset, lo_offset-1)
|
||||
local before_text = App.newText(love.graphics.getFont(), before)
|
||||
lo_px = App.width(before_text)
|
||||
end
|
||||
--? print(lo,pos,hi, '--', lo_offset,pos_offset,hi_offset, '--', lo_px)
|
||||
local s = line.data:sub(lo_offset, hi_offset-1)
|
||||
local s = line:sub(lo_offset, hi_offset-1)
|
||||
local text = App.newText(love.graphics.getFont(), s)
|
||||
local text_width = App.width(text)
|
||||
App.color(Highlight_color)
|
||||
|
@ -92,10 +92,8 @@ end
|
|||
|
||||
function Text.to_pos(State, x,y)
|
||||
for line_index,line in ipairs(State.lines) do
|
||||
if line.mode == 'text' then
|
||||
if Text.in_line(State, line_index, x,y) then
|
||||
return line_index, Text.to_pos_on_line(State, line_index, x,y)
|
||||
end
|
||||
if Text.in_line(State, line_index, x,y) then
|
||||
return line_index, Text.to_pos_on_line(State, line_index, x,y)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -138,20 +136,20 @@ function Text.delete_selection_without_undo(State)
|
|||
State.selection1 = {}
|
||||
-- delete everything between min (inclusive) and max (exclusive)
|
||||
Text.clear_screen_line_cache(State, minl)
|
||||
local min_offset = Text.offset(State.lines[minl].data, minp)
|
||||
local max_offset = Text.offset(State.lines[maxl].data, maxp)
|
||||
local min_offset = Text.offset(State.lines[minl], minp)
|
||||
local max_offset = Text.offset(State.lines[maxl], maxp)
|
||||
if minl == maxl then
|
||||
--? print('minl == maxl')
|
||||
State.lines[minl].data = State.lines[minl].data:sub(1, min_offset-1)..State.lines[minl].data:sub(max_offset)
|
||||
State.lines[minl] = State.lines[minl]:sub(1, min_offset-1)..State.lines[minl]:sub(max_offset)
|
||||
return
|
||||
end
|
||||
assert(minl < maxl)
|
||||
local rhs = State.lines[maxl].data:sub(max_offset)
|
||||
local rhs = State.lines[maxl]:sub(max_offset)
|
||||
for i=maxl,minl+1,-1 do
|
||||
table.remove(State.lines, i)
|
||||
table.remove(State.line_cache, i)
|
||||
end
|
||||
State.lines[minl].data = State.lines[minl].data:sub(1, min_offset-1)..rhs
|
||||
State.lines[minl] = State.lines[minl]:sub(1, min_offset-1)..rhs
|
||||
end
|
||||
|
||||
function Text.selection(State)
|
||||
|
@ -167,18 +165,16 @@ function Text.selection(State)
|
|||
minp,maxp = maxp,minp
|
||||
end
|
||||
end
|
||||
local min_offset = Text.offset(State.lines[minl].data, minp)
|
||||
local max_offset = Text.offset(State.lines[maxl].data, maxp)
|
||||
local min_offset = Text.offset(State.lines[minl], minp)
|
||||
local max_offset = Text.offset(State.lines[maxl], maxp)
|
||||
if minl == maxl then
|
||||
return State.lines[minl].data:sub(min_offset, max_offset-1)
|
||||
return State.lines[minl]:sub(min_offset, max_offset-1)
|
||||
end
|
||||
assert(minl < maxl)
|
||||
local result = {State.lines[minl].data:sub(min_offset)}
|
||||
local result = {State.lines[minl]:sub(min_offset)}
|
||||
for i=minl+1,maxl-1 do
|
||||
if State.lines[i].mode == 'text' then
|
||||
table.insert(result, State.lines[i].data)
|
||||
end
|
||||
table.insert(result, State.lines[i])
|
||||
end
|
||||
table.insert(result, State.lines[maxl].data:sub(1, max_offset-1))
|
||||
table.insert(result, State.lines[maxl]:sub(1, max_offset-1))
|
||||
return table.concat(result, '\n')
|
||||
end
|
||||
|
|
228
text.lua
228
text.lua
|
@ -50,7 +50,7 @@ function Text.draw(State, line_index, y, startpos)
|
|||
if line_index == State.cursor1.line then
|
||||
if pos <= State.cursor1.pos and pos + frag_len > State.cursor1.pos then
|
||||
if State.search_term then
|
||||
if State.lines[State.cursor1.line].data:sub(State.cursor1.pos, State.cursor1.pos+utf8.len(State.search_term)-1) == State.search_term then
|
||||
if State.lines[State.cursor1.line]:sub(State.cursor1.pos, State.cursor1.pos+utf8.len(State.search_term)-1) == State.search_term then
|
||||
local lo_px = Text.draw_highlight(State, line, x,y, pos, State.cursor1.pos, State.cursor1.pos+utf8.len(State.search_term))
|
||||
App.color(Text_color)
|
||||
love.graphics.print(State.search_term, x+lo_px,y)
|
||||
|
@ -92,7 +92,7 @@ function Text.compute_fragments(State, line_index)
|
|||
line_cache.fragments = {}
|
||||
local x = State.left
|
||||
-- try to wrap at word boundaries
|
||||
for frag in line.data:gmatch('%S*%s*') do
|
||||
for frag in line:gmatch('%S*%s*') do
|
||||
local frag_text = App.newText(love.graphics.getFont(), frag)
|
||||
local frag_width = App.width(frag_text)
|
||||
--? print('x: '..tostring(x)..'; '..tostring(State.right-x)..'px to go')
|
||||
|
@ -142,8 +142,8 @@ function Text.textinput(State, t)
|
|||
end
|
||||
|
||||
function Text.insert_at_cursor(State, t)
|
||||
local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
|
||||
State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)..t..string.sub(State.lines[State.cursor1.line].data, byte_offset)
|
||||
local byte_offset = Text.offset(State.lines[State.cursor1.line], State.cursor1.pos)
|
||||
State.lines[State.cursor1.line] = string.sub(State.lines[State.cursor1.line], 1, byte_offset-1)..t..string.sub(State.lines[State.cursor1.line], byte_offset)
|
||||
Text.clear_screen_line_cache(State, State.cursor1.line)
|
||||
State.cursor1.pos = State.cursor1.pos+1
|
||||
end
|
||||
|
@ -182,28 +182,23 @@ function Text.keychord_pressed(State, chord)
|
|||
local before
|
||||
if State.cursor1.pos > 1 then
|
||||
before = snapshot(State, State.cursor1.line)
|
||||
local byte_start = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos-1)
|
||||
local byte_end = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
|
||||
local byte_start = utf8.offset(State.lines[State.cursor1.line], State.cursor1.pos-1)
|
||||
local byte_end = utf8.offset(State.lines[State.cursor1.line], State.cursor1.pos)
|
||||
if byte_start then
|
||||
if byte_end then
|
||||
State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_start-1)..string.sub(State.lines[State.cursor1.line].data, byte_end)
|
||||
State.lines[State.cursor1.line] = string.sub(State.lines[State.cursor1.line], 1, byte_start-1)..string.sub(State.lines[State.cursor1.line], byte_end)
|
||||
else
|
||||
State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_start-1)
|
||||
State.lines[State.cursor1.line] = string.sub(State.lines[State.cursor1.line], 1, byte_start-1)
|
||||
end
|
||||
State.cursor1.pos = State.cursor1.pos-1
|
||||
end
|
||||
elseif State.cursor1.line > 1 then
|
||||
before = snapshot(State, State.cursor1.line-1, State.cursor1.line)
|
||||
if State.lines[State.cursor1.line-1].mode == 'drawing' then
|
||||
table.remove(State.lines, State.cursor1.line-1)
|
||||
table.remove(State.line_cache, State.cursor1.line-1)
|
||||
else
|
||||
-- join lines
|
||||
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line-1].data)+1
|
||||
State.lines[State.cursor1.line-1].data = State.lines[State.cursor1.line-1].data..State.lines[State.cursor1.line].data
|
||||
table.remove(State.lines, State.cursor1.line)
|
||||
table.remove(State.line_cache, State.cursor1.line)
|
||||
end
|
||||
-- join lines
|
||||
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line-1])+1
|
||||
State.lines[State.cursor1.line-1] = State.lines[State.cursor1.line-1]..State.lines[State.cursor1.line]
|
||||
table.remove(State.lines, State.cursor1.line)
|
||||
table.remove(State.line_cache, State.cursor1.line)
|
||||
State.cursor1.line = State.cursor1.line-1
|
||||
end
|
||||
if State.screen_top1.line > #State.lines then
|
||||
|
@ -227,27 +222,25 @@ function Text.keychord_pressed(State, chord)
|
|||
return
|
||||
end
|
||||
local before
|
||||
if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
|
||||
if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line]) then
|
||||
before = snapshot(State, State.cursor1.line)
|
||||
else
|
||||
before = snapshot(State, State.cursor1.line, State.cursor1.line+1)
|
||||
end
|
||||
if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
|
||||
local byte_start = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
|
||||
local byte_end = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos+1)
|
||||
if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line]) then
|
||||
local byte_start = utf8.offset(State.lines[State.cursor1.line], State.cursor1.pos)
|
||||
local byte_end = utf8.offset(State.lines[State.cursor1.line], State.cursor1.pos+1)
|
||||
if byte_start then
|
||||
if byte_end then
|
||||
State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_start-1)..string.sub(State.lines[State.cursor1.line].data, byte_end)
|
||||
State.lines[State.cursor1.line] = string.sub(State.lines[State.cursor1.line], 1, byte_start-1)..string.sub(State.lines[State.cursor1.line], byte_end)
|
||||
else
|
||||
State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_start-1)
|
||||
State.lines[State.cursor1.line] = string.sub(State.lines[State.cursor1.line], 1, byte_start-1)
|
||||
end
|
||||
-- no change to State.cursor1.pos
|
||||
end
|
||||
elseif State.cursor1.line < #State.lines then
|
||||
if State.lines[State.cursor1.line+1].mode == 'text' then
|
||||
-- join lines
|
||||
State.lines[State.cursor1.line].data = State.lines[State.cursor1.line].data..State.lines[State.cursor1.line+1].data
|
||||
end
|
||||
-- join lines
|
||||
State.lines[State.cursor1.line] = State.lines[State.cursor1.line]..State.lines[State.cursor1.line+1]
|
||||
table.remove(State.lines, State.cursor1.line+1)
|
||||
table.remove(State.line_cache, State.cursor1.line+1)
|
||||
end
|
||||
|
@ -340,10 +333,10 @@ function Text.keychord_pressed(State, chord)
|
|||
end
|
||||
|
||||
function Text.insert_return(State)
|
||||
local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
|
||||
table.insert(State.lines, State.cursor1.line+1, {mode='text', data=string.sub(State.lines[State.cursor1.line].data, byte_offset)})
|
||||
local byte_offset = Text.offset(State.lines[State.cursor1.line], State.cursor1.pos)
|
||||
table.insert(State.lines, State.cursor1.line+1, string.sub(State.lines[State.cursor1.line], byte_offset))
|
||||
table.insert(State.line_cache, State.cursor1.line+1, {})
|
||||
State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)
|
||||
State.lines[State.cursor1.line] = string.sub(State.lines[State.cursor1.line], 1, byte_offset-1)
|
||||
Text.clear_screen_line_cache(State, State.cursor1.line)
|
||||
State.cursor1.line = State.cursor1.line+1
|
||||
State.cursor1.pos = 1
|
||||
|
@ -358,11 +351,7 @@ function Text.pageup(State)
|
|||
while y >= State.top do
|
||||
--? print(y, top2.line, top2.screen_line, top2.screen_pos)
|
||||
if State.screen_top1.line == 1 and State.screen_top1.pos == 1 then break end
|
||||
if State.lines[State.screen_top1.line].mode == 'text' then
|
||||
y = y - State.line_height
|
||||
elseif State.lines[State.screen_top1.line].mode == 'drawing' then
|
||||
y = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width)
|
||||
end
|
||||
y = y - State.line_height
|
||||
top2 = Text.previous_screen_line(State, top2)
|
||||
end
|
||||
State.screen_top1 = Text.to1(State, top2)
|
||||
|
@ -399,35 +388,30 @@ function Text.pagedown(State)
|
|||
end
|
||||
|
||||
function Text.up(State)
|
||||
assert(State.lines[State.cursor1.line].mode == 'text')
|
||||
--? print('up', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
|
||||
local screen_line_index,screen_line_starting_pos = Text.pos_at_start_of_cursor_screen_line(State)
|
||||
if screen_line_starting_pos == 1 then
|
||||
--? print('cursor is at first screen line of its line')
|
||||
-- line is done; skip to previous text line
|
||||
local new_cursor_line = State.cursor1.line
|
||||
while new_cursor_line > 1 do
|
||||
new_cursor_line = new_cursor_line-1
|
||||
if State.lines[new_cursor_line].mode == 'text' then
|
||||
--? print('found previous text line')
|
||||
State.cursor1.line = new_cursor_line
|
||||
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
|
||||
-- previous text line found, pick its final screen line
|
||||
--? print('has multiple screen lines')
|
||||
local screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos
|
||||
--? print(#screen_line_starting_pos)
|
||||
screen_line_starting_pos = screen_line_starting_pos[#screen_line_starting_pos]
|
||||
--? print('previous screen line starts at pos '..tostring(screen_line_starting_pos)..' of its line')
|
||||
if State.screen_top1.line > State.cursor1.line then
|
||||
State.screen_top1.line = State.cursor1.line
|
||||
State.screen_top1.pos = screen_line_starting_pos
|
||||
--? print('pos of top of screen is also '..tostring(State.screen_top1.pos)..' of the same line')
|
||||
end
|
||||
local screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, screen_line_starting_pos)
|
||||
local s = string.sub(State.lines[State.cursor1.line].data, screen_line_starting_byte_offset)
|
||||
State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1
|
||||
break
|
||||
if State.cursor1.line > 1 then
|
||||
local new_cursor_line = State.cursor1.line-1
|
||||
--? print('found previous text line')
|
||||
State.cursor1.line = new_cursor_line
|
||||
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
|
||||
-- previous text line found, pick its final screen line
|
||||
--? print('has multiple screen lines')
|
||||
local screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos
|
||||
--? print(#screen_line_starting_pos)
|
||||
screen_line_starting_pos = screen_line_starting_pos[#screen_line_starting_pos]
|
||||
--? print('previous screen line starts at pos '..tostring(screen_line_starting_pos)..' of its line')
|
||||
if State.screen_top1.line > State.cursor1.line then
|
||||
State.screen_top1.line = State.cursor1.line
|
||||
State.screen_top1.pos = screen_line_starting_pos
|
||||
--? print('pos of top of screen is also '..tostring(State.screen_top1.pos)..' of the same line')
|
||||
end
|
||||
local screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line], screen_line_starting_pos)
|
||||
local s = string.sub(State.lines[State.cursor1.line], screen_line_starting_byte_offset)
|
||||
State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1
|
||||
end
|
||||
if State.cursor1.line < State.screen_top1.line then
|
||||
State.screen_top1.line = State.cursor1.line
|
||||
|
@ -442,28 +426,23 @@ function Text.up(State)
|
|||
State.screen_top1.pos = new_screen_line_starting_pos
|
||||
--? print('also setting pos of top of screen to '..tostring(State.screen_top1.pos))
|
||||
end
|
||||
local new_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, new_screen_line_starting_pos)
|
||||
local s = string.sub(State.lines[State.cursor1.line].data, new_screen_line_starting_byte_offset)
|
||||
local new_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line], new_screen_line_starting_pos)
|
||||
local s = string.sub(State.lines[State.cursor1.line], new_screen_line_starting_byte_offset)
|
||||
State.cursor1.pos = new_screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1
|
||||
--? print('cursor pos is now '..tostring(State.cursor1.pos))
|
||||
end
|
||||
end
|
||||
|
||||
function Text.down(State)
|
||||
assert(State.lines[State.cursor1.line].mode == 'text')
|
||||
--? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
|
||||
if Text.cursor_at_final_screen_line(State) then
|
||||
-- line is done, skip to next text line
|
||||
--? print('cursor at final screen line of its line')
|
||||
local new_cursor_line = State.cursor1.line
|
||||
while new_cursor_line < #State.lines do
|
||||
new_cursor_line = new_cursor_line+1
|
||||
if State.lines[new_cursor_line].mode == 'text' then
|
||||
State.cursor1.line = new_cursor_line
|
||||
State.cursor1.pos = Text.nearest_cursor_pos(State.lines[State.cursor1.line].data, State.cursor_x, State.left)
|
||||
--? print(State.cursor1.pos)
|
||||
break
|
||||
end
|
||||
if State.cursor1.line < #State.lines then
|
||||
local new_cursor_line = State.cursor1.line+1
|
||||
State.cursor1.line = new_cursor_line
|
||||
State.cursor1.pos = Text.nearest_cursor_pos(State.lines[State.cursor1.line], State.cursor_x, State.left)
|
||||
--? print(State.cursor1.pos)
|
||||
end
|
||||
if State.cursor1.line > State.screen_bottom1.line then
|
||||
--? print('screen top before:', State.screen_top1.line, State.screen_top1.pos)
|
||||
|
@ -481,8 +460,8 @@ function Text.down(State)
|
|||
local screen_line_index, screen_line_starting_pos = Text.pos_at_start_of_cursor_screen_line(State)
|
||||
new_screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos[screen_line_index+1]
|
||||
--? print('switching pos of screen line at cursor from '..tostring(screen_line_starting_pos)..' to '..tostring(new_screen_line_starting_pos))
|
||||
local new_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, new_screen_line_starting_pos)
|
||||
local s = string.sub(State.lines[State.cursor1.line].data, new_screen_line_starting_byte_offset)
|
||||
local new_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line], new_screen_line_starting_pos)
|
||||
local s = string.sub(State.lines[State.cursor1.line], new_screen_line_starting_byte_offset)
|
||||
State.cursor1.pos = new_screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1
|
||||
--? print('cursor pos is now', State.cursor1.line, State.cursor1.pos)
|
||||
if scroll_down then
|
||||
|
@ -502,7 +481,7 @@ function Text.start_of_line(State)
|
|||
end
|
||||
|
||||
function Text.end_of_line(State)
|
||||
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1
|
||||
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line]) + 1
|
||||
local _,botpos = Text.pos_at_start_of_cursor_screen_line(State)
|
||||
local botline1 = {line=State.cursor1.line, pos=botpos}
|
||||
if Text.cursor_past_screen_bottom(State) then
|
||||
|
@ -516,7 +495,7 @@ function Text.word_left(State)
|
|||
if State.cursor1.pos == 1 then
|
||||
break
|
||||
end
|
||||
if Text.match(State.lines[State.cursor1.line].data, State.cursor1.pos-1, '%S') then
|
||||
if Text.match(State.lines[State.cursor1.line], State.cursor1.pos-1, '%S') then
|
||||
break
|
||||
end
|
||||
Text.left(State)
|
||||
|
@ -528,7 +507,7 @@ function Text.word_left(State)
|
|||
break
|
||||
end
|
||||
assert(State.cursor1.pos > 1)
|
||||
if Text.match(State.lines[State.cursor1.line].data, State.cursor1.pos-1, '%s') then
|
||||
if Text.match(State.lines[State.cursor1.line], State.cursor1.pos-1, '%s') then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
@ -537,20 +516,20 @@ end
|
|||
function Text.word_right(State)
|
||||
-- skip some whitespace
|
||||
while true do
|
||||
if State.cursor1.pos > utf8.len(State.lines[State.cursor1.line].data) then
|
||||
if State.cursor1.pos > utf8.len(State.lines[State.cursor1.line]) then
|
||||
break
|
||||
end
|
||||
if Text.match(State.lines[State.cursor1.line].data, State.cursor1.pos, '%S') then
|
||||
if Text.match(State.lines[State.cursor1.line], State.cursor1.pos, '%S') then
|
||||
break
|
||||
end
|
||||
Text.right_without_scroll(State)
|
||||
end
|
||||
while true do
|
||||
Text.right_without_scroll(State)
|
||||
if State.cursor1.pos > utf8.len(State.lines[State.cursor1.line].data) then
|
||||
if State.cursor1.pos > utf8.len(State.lines[State.cursor1.line]) then
|
||||
break
|
||||
end
|
||||
if Text.match(State.lines[State.cursor1.line].data, State.cursor1.pos, '%s') then
|
||||
if Text.match(State.lines[State.cursor1.line], State.cursor1.pos, '%s') then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
@ -569,19 +548,11 @@ function Text.match(s, pos, pat)
|
|||
end
|
||||
|
||||
function Text.left(State)
|
||||
assert(State.lines[State.cursor1.line].mode == 'text')
|
||||
if State.cursor1.pos > 1 then
|
||||
State.cursor1.pos = State.cursor1.pos-1
|
||||
else
|
||||
local new_cursor_line = State.cursor1.line
|
||||
while new_cursor_line > 1 do
|
||||
new_cursor_line = new_cursor_line-1
|
||||
if State.lines[new_cursor_line].mode == 'text' then
|
||||
State.cursor1.line = new_cursor_line
|
||||
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1
|
||||
break
|
||||
end
|
||||
end
|
||||
elseif State.cursor1.line > 1 then
|
||||
State.cursor1.line = State.cursor1.line-1
|
||||
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line]) + 1
|
||||
end
|
||||
if Text.lt1(State.cursor1, State.screen_top1) then
|
||||
local top2 = Text.to2(State, State.screen_top1)
|
||||
|
@ -598,19 +569,11 @@ function Text.right(State)
|
|||
end
|
||||
|
||||
function Text.right_without_scroll(State)
|
||||
assert(State.lines[State.cursor1.line].mode == 'text')
|
||||
if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
|
||||
if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line]) then
|
||||
State.cursor1.pos = State.cursor1.pos+1
|
||||
else
|
||||
local new_cursor_line = State.cursor1.line
|
||||
while new_cursor_line <= #State.lines-1 do
|
||||
new_cursor_line = new_cursor_line+1
|
||||
if State.lines[new_cursor_line].mode == 'text' then
|
||||
State.cursor1.line = new_cursor_line
|
||||
State.cursor1.pos = 1
|
||||
break
|
||||
end
|
||||
end
|
||||
elseif State.cursor1.line <= #State.lines-1 then
|
||||
State.cursor1.line = State.cursor1.line+1
|
||||
State.cursor1.pos = 1
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -633,23 +596,7 @@ function Text.cursor_at_final_screen_line(State)
|
|||
end
|
||||
|
||||
function Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
|
||||
local y = State.top
|
||||
while State.cursor1.line <= #State.lines do
|
||||
if State.lines[State.cursor1.line].mode == 'text' then
|
||||
break
|
||||
end
|
||||
--? print('cursor skips', State.cursor1.line)
|
||||
y = y + Drawing_padding_height + Drawing.pixels(State.lines[State.cursor1.line].h, State.width)
|
||||
State.cursor1.line = State.cursor1.line + 1
|
||||
end
|
||||
-- hack: insert a text line at bottom of file if necessary
|
||||
if State.cursor1.line > #State.lines then
|
||||
assert(State.cursor1.line == #State.lines+1)
|
||||
table.insert(State.lines, {mode='text', data=''})
|
||||
table.insert(State.line_cache, {})
|
||||
end
|
||||
--? print(y, App.screen.height, App.screen.height-State.line_height)
|
||||
if y > App.screen.height - State.line_height then
|
||||
if State.top > App.screen.height - State.line_height then
|
||||
--? print('scroll up')
|
||||
Text.snap_cursor_to_bottom_of_screen(State)
|
||||
end
|
||||
|
@ -665,24 +612,11 @@ function Text.snap_cursor_to_bottom_of_screen(State)
|
|||
while true do
|
||||
--? print(y, 'top2:', top2.line, top2.screen_line, top2.screen_pos)
|
||||
if top2.line == 1 and top2.screen_line == 1 then break end
|
||||
if top2.screen_line > 1 or State.lines[top2.line-1].mode == 'text' then
|
||||
local h = State.line_height
|
||||
if y - h < State.top then
|
||||
break
|
||||
end
|
||||
y = y - h
|
||||
else
|
||||
assert(top2.line > 1)
|
||||
assert(State.lines[top2.line-1].mode == 'drawing')
|
||||
-- We currently can't draw partial drawings, so either skip it entirely
|
||||
-- or not at all.
|
||||
local h = Drawing_padding_height + Drawing.pixels(State.lines[top2.line-1].h, State.width)
|
||||
if y - h < State.top then
|
||||
break
|
||||
end
|
||||
--? print('skipping drawing of height', h)
|
||||
y = y - h
|
||||
local h = State.line_height
|
||||
if y - h < State.top then
|
||||
break
|
||||
end
|
||||
y = y - h
|
||||
top2 = Text.previous_screen_line(State, top2)
|
||||
end
|
||||
--? print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos)
|
||||
|
@ -713,8 +647,8 @@ function Text.to_pos_on_line(State, line_index, mx, my)
|
|||
local start_screen_line_index = Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos)
|
||||
for screen_line_index = start_screen_line_index,#line_cache.screen_line_starting_pos do
|
||||
local screen_line_starting_pos = line_cache.screen_line_starting_pos[screen_line_index]
|
||||
local screen_line_starting_byte_offset = Text.offset(line.data, screen_line_starting_pos)
|
||||
--? print('iter', y, screen_line_index, screen_line_starting_pos, string.sub(line.data, screen_line_starting_byte_offset))
|
||||
local screen_line_starting_byte_offset = Text.offset(line, screen_line_starting_pos)
|
||||
--? print('iter', y, screen_line_index, screen_line_starting_pos, string.sub(line, screen_line_starting_byte_offset))
|
||||
local nexty = y + State.line_height
|
||||
if my < nexty then
|
||||
-- On all wrapped screen lines but the final one, clicks past end of
|
||||
|
@ -724,7 +658,7 @@ function Text.to_pos_on_line(State, line_index, mx, my)
|
|||
--? print('past end of non-final line; return')
|
||||
return line_cache.screen_line_starting_pos[screen_line_index+1]-1
|
||||
end
|
||||
local s = string.sub(line.data, screen_line_starting_byte_offset)
|
||||
local s = string.sub(line, screen_line_starting_byte_offset)
|
||||
--? print('return', mx, Text.nearest_cursor_pos(s, mx, State.left), '=>', screen_line_starting_pos + Text.nearest_cursor_pos(s, mx, State.left) - 1)
|
||||
return screen_line_starting_pos + Text.nearest_cursor_pos(s, mx, State.left) - 1
|
||||
end
|
||||
|
@ -737,14 +671,14 @@ function Text.screen_line_width(State, line_index, i)
|
|||
local line = State.lines[line_index]
|
||||
local line_cache = State.line_cache[line_index]
|
||||
local start_pos = line_cache.screen_line_starting_pos[i]
|
||||
local start_offset = Text.offset(line.data, start_pos)
|
||||
local start_offset = Text.offset(line, start_pos)
|
||||
local screen_line
|
||||
if i < #line_cache.screen_line_starting_pos then
|
||||
local past_end_pos = line_cache.screen_line_starting_pos[i+1]
|
||||
local past_end_offset = Text.offset(line.data, past_end_pos)
|
||||
screen_line = string.sub(line.data, start_offset, past_end_offset-1)
|
||||
local past_end_offset = Text.offset(line, past_end_pos)
|
||||
screen_line = string.sub(line, start_offset, past_end_offset-1)
|
||||
else
|
||||
screen_line = string.sub(line.data, start_pos)
|
||||
screen_line = string.sub(line, start_pos)
|
||||
end
|
||||
local screen_line_text = App.newText(love.graphics.getFont(), screen_line)
|
||||
return App.width(screen_line_text)
|
||||
|
@ -847,9 +781,6 @@ function Text.x(s, pos)
|
|||
end
|
||||
|
||||
function Text.to2(State, loc1)
|
||||
if State.lines[loc1.line].mode == 'drawing' then
|
||||
return {line=loc1.line, screen_line=1, screen_pos=1}
|
||||
end
|
||||
local result = {line=loc1.line, screen_line=1}
|
||||
Text.populate_screen_line_starting_pos(State, loc1.line)
|
||||
for i=#State.line_cache[loc1.line].screen_line_starting_pos,1,-1 do
|
||||
|
@ -911,8 +842,6 @@ function Text.previous_screen_line(State, loc2)
|
|||
return {line=loc2.line, screen_line=loc2.screen_line-1, screen_pos=1}
|
||||
elseif loc2.line == 1 then
|
||||
return loc2
|
||||
elseif State.lines[loc2.line-1].mode == 'drawing' then
|
||||
return {line=loc2.line-1, screen_line=1, screen_pos=1}
|
||||
else
|
||||
local l = State.lines[loc2.line-1]
|
||||
Text.populate_screen_line_starting_pos(State, loc2.line-1)
|
||||
|
@ -922,7 +851,6 @@ end
|
|||
|
||||
function Text.populate_screen_line_starting_pos(State, line_index)
|
||||
local line = State.lines[line_index]
|
||||
if line.mode ~= 'text' then return end
|
||||
local line_cache = State.line_cache[line_index]
|
||||
if line_cache.screen_line_starting_pos then
|
||||
return
|
||||
|
|
|
@ -14,34 +14,6 @@ function test_initial_state()
|
|||
check_eq(Editor_state.screen_top1.pos, 1, 'F - test_initial_state/screen_top:pos')
|
||||
end
|
||||
|
||||
function test_click_to_create_drawing()
|
||||
io.write('\ntest_click_to_create_drawing')
|
||||
App.screen.init{width=120, height=60}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{}
|
||||
Text.redraw_all(Editor_state)
|
||||
edit.draw(Editor_state)
|
||||
edit.run_after_mouse_click(Editor_state, 8,Editor_state.top+8, 1)
|
||||
-- cursor skips drawing to always remain on text
|
||||
check_eq(#Editor_state.lines, 2, 'F - test_click_to_create_drawing/#lines')
|
||||
check_eq(Editor_state.cursor1.line, 2, 'F - test_click_to_create_drawing/cursor')
|
||||
end
|
||||
|
||||
function test_backspace_to_delete_drawing()
|
||||
io.write('\ntest_backspace_to_delete_drawing')
|
||||
-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
|
||||
App.screen.init{width=120, height=60}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'```lines', '```', ''}
|
||||
Text.redraw_all(Editor_state)
|
||||
-- cursor is on text as always (outside tests this will get initialized correctly)
|
||||
Editor_state.cursor1.line = 2
|
||||
-- backspacing deletes the drawing
|
||||
edit.run_after_keychord(Editor_state, 'backspace')
|
||||
check_eq(#Editor_state.lines, 1, 'F - test_backspace_to_delete_drawing/#lines')
|
||||
check_eq(Editor_state.cursor1.line, 1, 'F - test_backspace_to_delete_drawing/cursor')
|
||||
end
|
||||
|
||||
function test_backspace_from_start_of_final_line()
|
||||
io.write('\ntest_backspace_from_start_of_final_line')
|
||||
-- display final line of text with cursor at start of it
|
||||
|
@ -629,7 +601,7 @@ function test_cursor_movement_without_shift_resets_selection()
|
|||
edit.run_after_keychord(Editor_state, 'right')
|
||||
-- no change to data, selection is reset
|
||||
check_nil(Editor_state.selection1.line, 'F - test_cursor_movement_without_shift_resets_selection')
|
||||
check_eq(Editor_state.lines[1].data, 'abc', 'F - test_cursor_movement_without_shift_resets_selection/data')
|
||||
check_eq(Editor_state.lines[1], 'abc', 'F - test_cursor_movement_without_shift_resets_selection/data')
|
||||
end
|
||||
|
||||
function test_edit_deletes_selection()
|
||||
|
@ -647,7 +619,7 @@ function test_edit_deletes_selection()
|
|||
-- press a key
|
||||
edit.run_after_textinput(Editor_state, 'x')
|
||||
-- selected text is deleted and replaced with the key
|
||||
check_eq(Editor_state.lines[1].data, 'xbc', 'F - test_edit_deletes_selection')
|
||||
check_eq(Editor_state.lines[1], 'xbc', 'F - test_edit_deletes_selection')
|
||||
end
|
||||
|
||||
function test_edit_with_shift_key_deletes_selection()
|
||||
|
@ -670,7 +642,7 @@ function test_edit_with_shift_key_deletes_selection()
|
|||
App.fake_key_release('lshift')
|
||||
-- selected text is deleted and replaced with the key
|
||||
check_nil(Editor_state.selection1.line, 'F - test_edit_with_shift_key_deletes_selection')
|
||||
check_eq(Editor_state.lines[1].data, 'Dbc', 'F - test_edit_with_shift_key_deletes_selection/data')
|
||||
check_eq(Editor_state.lines[1], 'Dbc', 'F - test_edit_with_shift_key_deletes_selection/data')
|
||||
end
|
||||
|
||||
function test_copy_does_not_reset_selection()
|
||||
|
@ -708,7 +680,7 @@ function test_cut()
|
|||
edit.run_after_keychord(Editor_state, 'C-x')
|
||||
check_eq(App.clipboard, 'a', 'F - test_cut/clipboard')
|
||||
-- selected text is deleted
|
||||
check_eq(Editor_state.lines[1].data, 'bc', 'F - test_cut/data')
|
||||
check_eq(Editor_state.lines[1], 'bc', 'F - test_cut/data')
|
||||
end
|
||||
|
||||
function test_paste_replaces_selection()
|
||||
|
@ -729,7 +701,7 @@ function test_paste_replaces_selection()
|
|||
edit.run_after_keychord(Editor_state, 'C-v')
|
||||
-- selection is reset since shift key is not pressed
|
||||
-- selection includes the newline, so it's also deleted
|
||||
check_eq(Editor_state.lines[1].data, 'xyzdef', 'F - test_paste_replaces_selection')
|
||||
check_eq(Editor_state.lines[1], 'xyzdef', 'F - test_paste_replaces_selection')
|
||||
end
|
||||
|
||||
function test_deleting_selection_may_scroll()
|
||||
|
@ -755,7 +727,7 @@ function test_deleting_selection_may_scroll()
|
|||
edit.run_after_keychord(Editor_state, 'backspace')
|
||||
-- page scrolls up
|
||||
check_eq(Editor_state.screen_top1.line, 1, 'F - test_deleting_selection_may_scroll')
|
||||
check_eq(Editor_state.lines[1].data, 'ahi', 'F - test_deleting_selection_may_scroll/data')
|
||||
check_eq(Editor_state.lines[1], 'ahi', 'F - test_deleting_selection_may_scroll/data')
|
||||
end
|
||||
|
||||
function test_edit_wrapping_text()
|
||||
|
@ -821,8 +793,8 @@ function test_insert_newline_at_start_of_line()
|
|||
edit.run_after_keychord(Editor_state, 'return')
|
||||
check_eq(Editor_state.cursor1.line, 2, 'F - test_insert_newline_at_start_of_line/cursor:line')
|
||||
check_eq(Editor_state.cursor1.pos, 1, 'F - test_insert_newline_at_start_of_line/cursor:pos')
|
||||
check_eq(Editor_state.lines[1].data, '', 'F - test_insert_newline_at_start_of_line/data:1')
|
||||
check_eq(Editor_state.lines[2].data, 'abc', 'F - test_insert_newline_at_start_of_line/data:2')
|
||||
check_eq(Editor_state.lines[1], '', 'F - test_insert_newline_at_start_of_line/data:1')
|
||||
check_eq(Editor_state.lines[2], 'abc', 'F - test_insert_newline_at_start_of_line/data:2')
|
||||
end
|
||||
|
||||
function test_insert_from_clipboard()
|
||||
|
@ -994,36 +966,6 @@ function test_pagedown()
|
|||
App.screen.check(y, 'ghi', 'F - test_pagedown/screen:2')
|
||||
end
|
||||
|
||||
function test_pagedown_skips_drawings()
|
||||
io.write('\ntest_pagedown_skips_drawings')
|
||||
-- some lines of text with a drawing intermixed
|
||||
local drawing_width = 50
|
||||
App.screen.init{width=Editor_state.left+drawing_width, height=80}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc', -- height 15
|
||||
'```lines', '```', -- height 25
|
||||
'def', -- height 15
|
||||
'ghi'} -- height 15
|
||||
Text.redraw_all(Editor_state)
|
||||
check_eq(Editor_state.lines[2].mode, 'drawing', 'F - test_pagedown_skips_drawings/baseline/lines')
|
||||
Editor_state.cursor1 = {line=1, pos=1}
|
||||
Editor_state.screen_top1 = {line=1, pos=1}
|
||||
Editor_state.screen_bottom1 = {}
|
||||
local drawing_height = Drawing_padding_height + drawing_width/2 -- default
|
||||
-- initially the screen displays the first line and the drawing
|
||||
-- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80px
|
||||
edit.draw(Editor_state)
|
||||
local y = Editor_state.top
|
||||
App.screen.check(y, 'abc', 'F - test_pagedown_skips_drawings/baseline/screen:1')
|
||||
-- after pagedown the screen draws the drawing up top
|
||||
-- 15px margin + 10px margin + 25px drawing + 10px margin + 15px line3 = 75px < screen height 80px
|
||||
edit.run_after_keychord(Editor_state, 'pagedown')
|
||||
check_eq(Editor_state.screen_top1.line, 2, 'F - test_pagedown_skips_drawings/screen_top')
|
||||
check_eq(Editor_state.cursor1.line, 3, 'F - test_pagedown_skips_drawings/cursor')
|
||||
y = Editor_state.top + drawing_height
|
||||
App.screen.check(y, 'def', 'F - test_pagedown_skips_drawings/screen:1')
|
||||
end
|
||||
|
||||
function test_pagedown_often_shows_start_of_wrapping_line()
|
||||
io.write('\ntest_pagedown_often_shows_start_of_wrapping_line')
|
||||
-- draw a few lines ending in part of a wrapping line
|
||||
|
@ -1816,7 +1758,7 @@ function test_backspace_past_line_boundary()
|
|||
Editor_state.cursor1 = {line=2, pos=1}
|
||||
-- backspace joins with previous line
|
||||
edit.run_after_keychord(Editor_state, 'backspace')
|
||||
check_eq(Editor_state.lines[1].data, 'abcdef', "F - test_backspace_past_line_boundary")
|
||||
check_eq(Editor_state.lines[1], 'abcdef', "F - test_backspace_past_line_boundary")
|
||||
end
|
||||
|
||||
-- some tests for operating over selections created using Shift- chords
|
||||
|
@ -1833,7 +1775,7 @@ function test_backspace_over_selection()
|
|||
Editor_state.selection1 = {line=1, pos=2}
|
||||
-- backspace deletes the selected character, even though it's after the cursor
|
||||
edit.run_after_keychord(Editor_state, 'backspace')
|
||||
check_eq(Editor_state.lines[1].data, 'bc', "F - test_backspace_over_selection/data")
|
||||
check_eq(Editor_state.lines[1], 'bc', "F - test_backspace_over_selection/data")
|
||||
-- cursor (remains) at start of selection
|
||||
check_eq(Editor_state.cursor1.line, 1, "F - test_backspace_over_selection/cursor:line")
|
||||
check_eq(Editor_state.cursor1.pos, 1, "F - test_backspace_over_selection/cursor:pos")
|
||||
|
@ -1852,7 +1794,7 @@ function test_backspace_over_selection_reverse()
|
|||
Editor_state.selection1 = {line=1, pos=1}
|
||||
-- backspace deletes the selected character
|
||||
edit.run_after_keychord(Editor_state, 'backspace')
|
||||
check_eq(Editor_state.lines[1].data, 'bc', "F - test_backspace_over_selection_reverse/data")
|
||||
check_eq(Editor_state.lines[1], 'bc', "F - test_backspace_over_selection_reverse/data")
|
||||
-- cursor moves to start of selection
|
||||
check_eq(Editor_state.cursor1.line, 1, "F - test_backspace_over_selection_reverse/cursor:line")
|
||||
check_eq(Editor_state.cursor1.pos, 1, "F - test_backspace_over_selection_reverse/cursor:pos")
|
||||
|
@ -1871,8 +1813,8 @@ function test_backspace_over_multiple_lines()
|
|||
Editor_state.selection1 = {line=4, pos=2}
|
||||
-- backspace deletes the region and joins the remaining portions of lines on either side
|
||||
edit.run_after_keychord(Editor_state, 'backspace')
|
||||
check_eq(Editor_state.lines[1].data, 'akl', "F - test_backspace_over_multiple_lines/data:1")
|
||||
check_eq(Editor_state.lines[2].data, 'mno', "F - test_backspace_over_multiple_lines/data:2")
|
||||
check_eq(Editor_state.lines[1], 'akl', "F - test_backspace_over_multiple_lines/data:1")
|
||||
check_eq(Editor_state.lines[2], 'mno', "F - test_backspace_over_multiple_lines/data:2")
|
||||
-- cursor remains at start of selection
|
||||
check_eq(Editor_state.cursor1.line, 1, "F - test_backspace_over_multiple_lines/cursor:line")
|
||||
check_eq(Editor_state.cursor1.pos, 2, "F - test_backspace_over_multiple_lines/cursor:pos")
|
||||
|
@ -1891,8 +1833,8 @@ function test_backspace_to_end_of_line()
|
|||
Editor_state.selection1 = {line=1, pos=4}
|
||||
-- backspace deletes rest of line without joining to any other line
|
||||
edit.run_after_keychord(Editor_state, 'backspace')
|
||||
check_eq(Editor_state.lines[1].data, 'a', "F - test_backspace_to_start_of_line/data:1")
|
||||
check_eq(Editor_state.lines[2].data, 'def', "F - test_backspace_to_start_of_line/data:2")
|
||||
check_eq(Editor_state.lines[1], 'a', "F - test_backspace_to_start_of_line/data:1")
|
||||
check_eq(Editor_state.lines[2], 'def', "F - test_backspace_to_start_of_line/data:2")
|
||||
-- cursor remains at start of selection
|
||||
check_eq(Editor_state.cursor1.line, 1, "F - test_backspace_to_start_of_line/cursor:line")
|
||||
check_eq(Editor_state.cursor1.pos, 2, "F - test_backspace_to_start_of_line/cursor:pos")
|
||||
|
@ -1911,8 +1853,8 @@ function test_backspace_to_start_of_line()
|
|||
Editor_state.selection1 = {line=2, pos=3}
|
||||
-- backspace deletes beginning of line without joining to any other line
|
||||
edit.run_after_keychord(Editor_state, 'backspace')
|
||||
check_eq(Editor_state.lines[1].data, 'abc', "F - test_backspace_to_start_of_line/data:1")
|
||||
check_eq(Editor_state.lines[2].data, 'f', "F - test_backspace_to_start_of_line/data:2")
|
||||
check_eq(Editor_state.lines[1], 'abc', "F - test_backspace_to_start_of_line/data:1")
|
||||
check_eq(Editor_state.lines[2], 'f', "F - test_backspace_to_start_of_line/data:2")
|
||||
-- cursor remains at start of selection
|
||||
check_eq(Editor_state.cursor1.line, 2, "F - test_backspace_to_start_of_line/cursor:line")
|
||||
check_eq(Editor_state.cursor1.pos, 1, "F - test_backspace_to_start_of_line/cursor:pos")
|
||||
|
@ -2008,7 +1950,7 @@ function test_undo_restores_selection()
|
|||
edit.draw(Editor_state)
|
||||
-- delete selected text
|
||||
edit.run_after_textinput(Editor_state, 'x')
|
||||
check_eq(Editor_state.lines[1].data, 'xbc', 'F - test_undo_restores_selection/baseline')
|
||||
check_eq(Editor_state.lines[1], 'xbc', 'F - test_undo_restores_selection/baseline')
|
||||
check_nil(Editor_state.selection1.line, 'F - test_undo_restores_selection/baseline:selection')
|
||||
-- undo
|
||||
edit.run_after_keychord(Editor_state, 'C-z')
|
||||
|
@ -2022,7 +1964,7 @@ function test_search()
|
|||
io.write('\ntest_search')
|
||||
App.screen.init{width=120, height=60}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'```lines', '```', 'def', 'ghi', 'deg'}
|
||||
Editor_state.lines = load_array{'abc', 'def', 'ghi', 'deg'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=1, pos=1}
|
||||
Editor_state.screen_top1 = {line=1, pos=1}
|
||||
|
|
14
undo.lua
14
undo.lua
|
@ -60,19 +60,7 @@ function snapshot(State, s,e)
|
|||
-- deep copy lines without cached stuff like text fragments
|
||||
for i=s,e do
|
||||
local line = State.lines[i]
|
||||
if line.mode == 'text' then
|
||||
table.insert(event.lines, {mode='text', data=line.data})
|
||||
elseif line.mode == 'drawing' then
|
||||
local points=deepcopy(line.points)
|
||||
--? print('copying', line.points, 'with', #line.points, 'points into', points)
|
||||
local shapes=deepcopy(line.shapes)
|
||||
--? print('copying', line.shapes, 'with', #line.shapes, 'shapes into', shapes)
|
||||
table.insert(event.lines, {mode='drawing', h=line.h, points=points, shapes=shapes, pending={}})
|
||||
--? table.insert(event.lines, {mode='drawing', h=line.h, points=deepcopy(line.points), shapes=deepcopy(line.shapes), pending={}})
|
||||
else
|
||||
print(line.mode)
|
||||
assert(false)
|
||||
end
|
||||
table.insert(event.lines, State.lines[i])
|
||||
end
|
||||
return event
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue