4Y2QDDAZT2X7SEWYNFEMTYJC6O3UBK4ID3AIOAZZNE2S2H57UQSAC
PTBOQDJ7MKVSO2ANF5J725IPRWORYKEZVUHB7B5MBGLEA7JSHLVQC
IYXT7GFCVBRV7HT5UFF4SCNHDU3PPJZKKCXZLJLUFASOEUIRE35QC
CRBLAWBOTECOU5MFURWCRBR43OO7NWIHP5LC35IMGTQKN73AUS6AC
R5QXEHUIZLELJGGCZAE7ATNS3CLRJ7JFRENMGH4XXH24C5WABZDQC
K2X6G75Z6XBC4DVIRWC5HC7XA3A2SKOM3MWSQTCFEYWIJL7LME2QC
R6MNUXDJJEMQH6GGP4I7QZ7DQSSOE2BLLI24JJIDWV6WBPCRDQHAC
VP5KC4XZBKD536KCBFO47UKH74RNONENDCFJAHUMVUTPVPGJWD4AC
ORRSP7FVCHI2TF5GXBRGQYYJAA3JFYXZBM3T663BKSBV22FCZVCAC
2TCIWW6ZC73FHPZLO5QOBUFZCIL74U2MBYTPNXFYYFJ7IH4YAGUQC
A4BSGS2CX4JK7IELL655EC6HAY6ILCWTGIHWZXHRGQOKU3HSUPLAC
MU2HIRR62BULXR6QKVGJNUWSURYYJ2YFCHUHS4BOI3KHUM725BLAC
MBAJPTDJ4KHWACEHWYGCFMHPQYM6FQKCSIIDKWCE765UI3VTDMIAC
BLWAYPKV3MLDZ4ALXLUJ25AIR6PCIL4RFYNRYLB26GFVC2KQBYBAC
KJLZCK2RECVH77W4AJJMSXWYPU3U6Q3W3BY73TMXNLRZJZVQZAZAC
ORKN6EOBUFVAD2TXYW5OIKSL55RU24LOFDTTTXHDZUZ57QRDCY7QC
HYEAFRZ2UEKDYTAE2GDQLHEJBPQASP2NDLMXB7F6MTVK2BKOXKEAC
KTZQ57HVZU4XGWRPXBA27G4GXZFV74YYKJRXCJCE7UKDS7NGJVBAC
2CK5QI7WA7M4IVSACFGOJYAIDKRUTZVMMPSFWEJTUNMWTN7AX4NAC
34BZ5ZKNAB4XQGXOPVBZHBDYD5D3X4V6T72XZSR5LJXF4UIVSWQAC
KYNGDE2CKNOKUC2XMAS5MEU6YT2C3IW5SIZLOJE64G3ERT7BSWFAC
VHQCNMARPMNBSIUFLJG7HVK4QGDNPCGNVFLHS3I4IGNVSV5MRLYQC
ZLJYLPOTXIVBVWJ4NTRM2YCQPT2FCSN7446P56MJFEFY45QTB7IAC
KMSL74GAMFNTAKGDKZFP2AMQXUMOC3XH373BO4IABZWBEP3YAXKAC
5SM6DRHKPLFWQPCZDTVM4ENVENWBYBQ3Q2KYKSWLWYUTOSEPCRLAC
NSM73TX3N6HN6CYVX5LHHWQTHOZITHWIWNOWXZQROBGP6KSDEPBAC
ZS5IYZH5EXXPSVIFWS7XW5POEVRRCK6XV6PB36D3EJXJRT22LKOQC
JOPVPUSAMMU6RFVDQR4NJC4GNNUFB7GPKVH7OS5FKCYS5QZ53VLQC
3PSFWAILGRA4OYXWS2DX7VF332AIBPYBXHEA4GIQY2XEJVD65UMAC
D4B52CQ2QKG2HQKFUQOO5S2ME325DTW3PH2D7SBXCW4BPQFYG7CAC
ONHKBLLCD5NDO3HSSUMAMGJ7HDT53JYVV56DI42AEYI3W63GKACQC
KKQKPGCIHAG2JESQAWEMCBTAKBDC5AVIQ6LCZ2ORQM2AUCFQYLSQC
2TQUKHBC2EB3WDBD5UL62DQYV7CV6B7OJYK7CHOEDNOZENSOG42AC
KWIVKQQ7AANRG6R4ZRB5TDBZ2TZTXAXIR2P6JNT362KIAJ7JQ4VQC
YXQOITYSGYQQBOMF7CJ33C2CTDAI5R2W2RCRSKJ6JTLV7MS4WVXAC
VOU73AK6XOVIOCY6PHUXS5RQZ2TGFEF7RYNOKFE2XSHRCZBAJMYQC
QCPXQ2E3USF3Z6R6WJ2JKHTRMPKA6QWXFKKRMLXA3MXABJEL543AC
JCXL74WVQ23V53EOCKA2NXQIYA5NNOXGN5WYC7ZW42EI2I6D5IJAC
BULPIBEGL7TMK6CVIE7IS7WGAHGOSUJBGJSFQK542MOWGHP2ADQQC
CVSRHMJ2BM4LPVG67ULIVQMP2NW3YY2JC2ZQBEA6EB5KVM4O2L5AC
Z5HLXU4PJWWJJDBCK52NBD6PIRIA3TAN2BKZB5HBYFGIDBX4F5HAC
MXA3RZYKUI4UF2ISY7JEF6VKX6NOPZMZH5SLLCZHRJKFIXXXDPSAC
36Z442IVPXHZ7D2QI26YLN3TDDEMDRQ2GKBYQAD6NUHQZVCCY4VAC
App.screen.init{width=800, height=600}
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(#drawing.shapes, 1, 'baseline/#shapes')
check_eq(#drawing.points, 2, 'baseline/#points')
check_eq(drawing.shapes[1].mode, 'line', 'baseline/shape:1')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
check_nil(App.filesystem['foo'], 'early')
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)
local starty = Text.starty(State, line_index)
if pmx < State.right and pmy > starty and pmy < starty+Drawing.pixels(line.h, State.width) then
love.graphics.rectangle('line', State.left,starty, State.width,Drawing.pixels(line.h, State.width))
icon[State.current_drawing_mode](State.right-22, starty+4)
icon[State.previous_drawing_mode](State.right-22, starty+4)
local my = Drawing.coord(pmy-starty, State.width)
Drawing.draw_shape(line, shape, starty, State.left,State.right)
local function py(y) return Drawing.pixels(y, State.width)+starty end
Drawing.draw_pending_shape(line, starty, State.left,State.right)
function Drawing.in_current_drawing(State, x,y, left,right)
return Drawing.in_drawing(State, State.lines.current_drawing_index, x,y, left,right)
end
function Drawing.in_drawing(State, line_index, x,y, left,right)
assert(State.lines[line_index].mode == 'drawing')
local starty = Text.starty(State, line_index)
if starty == nil then return false end -- outside current page
local drawing = State.lines[line_index]
local width = right-left
return y >= starty and y < starty + Drawing.pixels(drawing.h, width) and x >= left and x < right
end
local starty = Text.starty(State, drawing_index)
local cy = Drawing.coord(y-starty, State.width)
local starty = Text.starty(State, State.lines.current_drawing_index)
if starty == nil then
local my = Drawing.coord(pmy-starty, State.width)
if Drawing.in_current_drawing(State, pmx,pmy, State.left,State.right) then
if Drawing.in_current_drawing(State, pmx, pmy, State.left,State.right) then
local starty = Text.starty(State, State.lines.current_drawing_index)
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
App.mouse_move(State.left+Drawing.pixels(p2.x, State.width), starty+Drawing.pixels(p2.y, State.width))
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
local drawing_index,drawing = Drawing.current_drawing(State)
local starty = Text.starty(State, drawing_index)
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width)
local drawing_index,drawing = Drawing.current_drawing(State)
local starty = Text.starty(State, drawing_index)
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width)
local drawing_index,drawing = Drawing.current_drawing(State)
local starty = Text.starty(State, drawing_index)
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width)
local drawing_index,drawing,_,i,p = Drawing.select_point_at_mouse(State)
local drawing_index,drawing,_,point_index,p = Drawing.select_point_at_mouse(State)
if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then
return drawing_index,drawing
local starty = Text.starty(State, drawing_index)
if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
return drawing,starty,i,shape
local starty = Text.starty(State, drawing_index)
if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
return drawing_index,drawing,starty,i,point
if Drawing.in_drawing(State, drawing_index, 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
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()
for i,point in ipairs(drawing.points) do
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()
for i,shape in ipairs(drawing.shapes) do
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()
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)
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 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
drawing.pending.mode = 'arc'
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 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
function Drawing.keychord_press(State, chord)
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
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
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
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
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
table.insert(drawing.shapes, drawing.pending)
end
elseif drawing.pending.mode == 'polygon' then
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]
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]
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
function Drawing.mouse_release(State, x,y, mouse_button)
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
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 App.mouse_down(1) then
-- some event cleared starty just this frame
-- draw in this frame will soon set starty
-- just skip this frame
return
end
-- all the action is in mouse_release
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
local cx = Drawing.coord(x-State.left, State.width)
function Drawing.mouse_press(State, drawing_index, x,y, mouse_button)
end
local width = right-left
-- after mouse_release
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
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)
end
local function px(x) return Drawing.pixels(x, State.width)+State.left end
for _,shape in ipairs(line.shapes) do
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)
else
if icon[State.current_drawing_mode] then
App.color(Icon_color)
local starty = Text.starty(State, drawing_index)
local y = starty+10
love.graphics.rectangle('fill', State.left,starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-starty))
local starty = Text.starty(State, drawing_index)
local y = starty+10
love.graphics.rectangle('fill', State.left,starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-starty))
end
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
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)
App.color(Help_color)
end
function draw_help_with_mouse_pressed(State, drawing_index)
local drawing = State.lines[drawing_index]
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
App.color(Help_color)
if y < State.top then
local screen_bottom1 = Text.screen_bottom1(State)
return screen_bottom1.line, Text.pos_at_end_of_screen_line(State, screen_bottom1)
end
function Text.cut_selection(State)
if State.selection1.line == nil then return end
local result = Text.selection(State)
Text.delete_selection(State)
return result
end
function Text.delete_selection(State)
if State.selection1.line == nil then return end
local minl,maxl = minmax(State.selection1.line, State.cursor1.line)
local before = snapshot(State, minl, maxl)
Text.delete_selection_without_undo(State)
record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
end
function Text.delete_selection_without_undo(State)
if State.selection1.line == nil then return end
-- min,max = sorted(State.selection1,State.cursor1)
local minl,minp = State.selection1.line,State.selection1.pos
local maxl,maxp = State.cursor1.line,State.cursor1.pos
if minl > maxl then
minl,maxl = maxl,minl
minp,maxp = maxp,minp
elseif minl == maxl then
if minp > maxp then
minp,maxp = maxp,minp
end
end
-- update State.cursor1 and State.selection1
State.cursor1.line = minl
State.cursor1.pos = minp
if Text.lt1(State.cursor1, State.screen_top1) then
State.screen_top1.line = State.cursor1.line
State.screen_top1.pos = Text.pos_at_start_of_screen_line(State, State.cursor1)
end
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)
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)
return
end
return State.screen_top1.line, State.screen_top1.pos
App.screen.init{width=800, height=600}
edit.run_after_keychord(Editor_state, 'M-right', 'right')
edit.run_after_keychord(Editor_state, 'M-right', 'right')
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.run_after_keychord(Editor_state, 'S-right', 'right')
edit.run_after_keychord(Editor_state, 'right', 'right')
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.run_after_keychord(Editor_state, 'right', 'right')
check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
check_eq(Editor_state.cursor1.pos, 6, 'cursor:pos')
-- click on first location
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
-- hold down shift and click on a second location
App.fake_key_press('lshift')
edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)
-- hold down shift and click at a third location
App.fake_key_press('lshift')
edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+Editor_state.line_height+5, 1)
App.fake_key_release('lshift')
-- selection is between first and third location. forget the second location, not the first.
-- click on first location
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
-- hold down shift and click somewhere else
App.fake_key_press('lshift')
edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)
App.fake_key_release('lshift')
-- press mouse above first line of text
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)
check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')
check_eq(Editor_state.selection1.line, 1, 'selection:line')
check_eq(Editor_state.selection1.pos, 1, 'selection:pos')
-- press and hold on first location
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
-- drag and release somewhere else
edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)
-- no change to data, selection is reset
App.fake_key_release('lshift')
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
check_eq(Editor_state.cursor1.line, 2, 'line')
check_eq(Editor_state.cursor1.pos, 4, 'pos')
check_eq(Editor_state.cursor1.pos, 9, 'check')
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
-- return y for the next line
return y
--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
State.screen_top1 = Text.previous_screen_top1(State)
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
end
-- return the top y coordinate of a given line_index,
-- or nil if no part of it is on screen
function Text.starty(State, line_index)
-- does not modify State (except to populate line_cache)
if line_index < State.screen_top1.line then return end
local loc2 = Text.to2(State, State.screen_top1)
local y = State.top
while true do
if State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing_padding_top
end
if loc2.line == line_index then return y end
if State.lines[loc2.line].mode == 'text' then
y = y + State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing.pixels(State.lines[loc2.line].h, State.width) + Drawing_padding_bottom
end
if y + State.line_height > App.screen.height then break end
local next_loc2 = Text.next_screen_line(State, loc2)
if Text.eq2(next_loc2, loc2) then break end -- end of file
loc2 = next_loc2
end
end
function Text.previous_screen_top1(State)
-- duplicate some logic from love.draw
-- does not modify State (except to populate line_cache)
local loc2 = Text.to2(State, State.screen_top1)
if loc2.line == 1 and loc2.screen_line == 1 and loc2.screen_pos == 1 then break end
if State.lines[loc2.line].mode == 'text' then
elseif State.lines[loc2.line].mode == 'drawing' then
y = y - Drawing_padding_height - Drawing.pixels(State.lines[loc2.line].h, State.width)
loc2 = Text.previous_screen_line(State, loc2)
return Text.to1(State, loc2)
State.screen_top1 = Text.screen_bottom1(State)
end
-- return the location of the start of the bottom-most line on screen
function Text.screen_bottom1(State)
-- duplicate some logic from love.draw
-- does not modify State (except to populate line_cache)
local loc2 = Text.to2(State, State.screen_top1)
local y = State.top
while true do
if State.lines[loc2.line].mode == 'text' then
y = y + State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing_padding_height + Drawing.pixels(State.lines[loc2.line].h, State.width)
end
if y + State.line_height > App.screen.height then break end
local next_loc2 = Text.next_screen_line(State, loc2)
if Text.eq2(next_loc2, loc2) then break end
loc2 = next_loc2
end
return Text.to1(State, loc2)
--? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
local screen_bottom1 = Text.screen_bottom1(State)
--? print('down 2', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, screen_bottom1.line, screen_bottom1.pos)
if State.cursor1.line > screen_bottom1.line then
local screen_bottom1 = Text.screen_bottom1(State)
local scroll_down = Text.le1(screen_bottom1, State.cursor1)
--? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
assert(State.lines[loc1.line].mode == 'text')
end
function Text.final_text_loc_on_screen(State)
local screen_bottom1 = Text.screen_bottom1(State)
if State.lines[screen_bottom1.line].mode == 'text' then
return {
line=screen_bottom1.line,
pos=Text.pos_at_end_of_screen_line(State, screen_bottom1),
}
end
local loc2 = Text.to2(State, screen_bottom1)
while true do
if State.lines[loc2.line].mode == 'text' then break end
assert(loc2.line > 1 or loc2.screen_line > 1 and loc2.screen_pos > 1) -- elsewhere we're making sure there's always at least one text line on screen
loc2 = Text.previous_screen_line(State, loc2)
end
local result = Text.to1(State, loc2)
result.pos = Text.pos_at_end_of_screen_line(State, result)
return result
--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
local starty = Text.starty(State, line_index)
if starty == nil then return false end -- outside current page
if y < starty then return false end
return y < starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1)
local starty = Text.starty(State, line_index)
assert(my >= starty, 'failed to map y pixel to line')
local y = starty
end
function Text.eq2(a, b)
return a.line == b.line and a.screen_line == b.screen_line and a.screen_pos == b.screen_pos
function Text.next_screen_line(State, loc2)
if State.lines[loc2.line].mode == 'drawing' then
return {line=loc2.line+1, screen_line=1, screen_pos=1}
end
Text.populate_screen_line_starting_pos(State, loc2.line)
if loc2.screen_line >= #State.line_cache[loc2.line].screen_line_starting_pos then
if loc2.line < #State.lines then
return {line=loc2.line+1, screen_line=1, screen_pos=1}
else
return loc2
end
else
return {line=loc2.line, screen_line=loc2.screen_line+1, screen_pos=1}
end
end
local screen_bottom1 = Text.screen_bottom1(State)
elseif State.cursor1.line >= screen_bottom1.line then
State.cursor1 = Text.final_text_loc_on_screen(State)
end
end
end
-- slightly expensive since it redraws the screen
function Text.cursor_out_of_screen(State)
if Text.cursor_out_of_screen(State) then
if Text.lt1(State.cursor1, State.screen_top1) then
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
-- resize helper
function Text.tweak_screen_top_and_cursor(State)
if State.screen_top1.pos == 1 then return end
Text.populate_screen_line_starting_pos(State, State.screen_top1.line)
local line = State.lines[State.screen_top1.line]
local line_cache = State.line_cache[State.screen_top1.line]
for i=2,#line_cache.screen_line_starting_pos do
local pos = line_cache.screen_line_starting_pos[i]
if pos == State.screen_top1.pos then
break
end
if pos > State.screen_top1.pos then
-- make sure screen top is at start of a screen line
local prev = line_cache.screen_line_starting_pos[i-1]
if State.screen_top1.pos - prev < pos - State.screen_top1.pos then
State.screen_top1.pos = prev
else
State.screen_top1.pos = pos
end
break
end
end
-- make sure cursor is on screen
end
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))
-- duplicate some logic from Text.draw
end
-- convert mx,my in pixels to schema-1 coordinates
Text.populate_screen_line_starting_pos(State, line_index)
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
--? print('cursor pos '..tostring(State.cursor1.pos)..' is on the #'..tostring(top2.screen_line)..' screen line down')
local y = App.screen.height - State.line_height
-- duplicate some logic from love.draw
while true do
end
Text.populate_screen_line_starting_pos(State, loc1.line)
local line_cache = State.line_cache[loc1.line]
local most_recent_final_pos = utf8.len(State.lines[loc1.line].data)+1
for i=#line_cache.screen_line_starting_pos,1,-1 do
local spos = line_cache.screen_line_starting_pos[i]
if spos <= loc1.pos then
return most_recent_final_pos
end
most_recent_final_pos = spos-1
end
end
function Text.start_of_line(State)
--? print('cursor is NOT at final screen line of its line')
local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1)
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
local 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)
--? print('screen top before:', State.screen_top1.line, State.screen_top1.pos)
--? print('scroll up preserving cursor')
Text.snap_cursor_to_bottom_of_screen(State)
--? print('screen top after:', State.screen_top1.line, State.screen_top1.pos)
end
assert(State.cursor1.pos, 'cursor has no pos')
end
function Text.up(State)
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
end
function Text.pagedown(State)
end
end
y = y - State.line_height
local y = App.screen.height - State.line_height
while y >= State.top do
-- duplicate some logic from love.draw
end
schedule_save(State)
record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
elseif chord == 'backspace' then
Text.insert_at_cursor(State, '\t')
if State.cursor_y > App.screen.height - State.line_height then
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
Text.insert_at_cursor(State, t)
if State.cursor_y > App.screen.height - State.line_height then
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
end
record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
end
function Text.insert_at_cursor(State, t)
end
function Text.screen_line(line, line_cache, i)
local pos = line_cache.screen_line_starting_pos[i]
local offset = Text.offset(line.data, pos)
if i >= #line_cache.screen_line_starting_pos then
return line.data:sub(offset)
function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers)
--
-- On lines that are drawings, pos will be nil.
cursor1 = {line=1, pos=1}, -- position of cursor; must be on a text line
y = Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers)
if Drawing.in_drawing(State, line_index, x, y, State.left,State.right) then
State.selection1 = Text.final_text_loc_on_screen(State)
State.cursor1 = Text.final_text_loc_on_screen(State)
State.cursor1 = Text.screen_bottom1(State)
chord ~= 'C-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(key) then
edit.draw(State)
App.fake_mouse_release(x,y, mouse_button)
Text.delete_selection(State, State.left, State.right)
end
edit.put_cursor_on_next_text_line(State)
edit.clean_up_mouse_press(State)
end
State.lines.current_drawing_index = line_index
State.lines.current_drawing = line
Drawing.before = snapshot(State, line_index)
--? 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
selection1 = {},
-- some extra state to compute selection between mouse press and release
old_cursor1 = nil,
old_selection1 = nil,
mousepress_shift = nil,
screen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screen