on.draw no longer mutates anything but the screen.
We now have levels of recomputation:
The level at which we start using viewport settings (B) is also responsible for updating the default font, a global setting used all over the place inside the editor widget.
Hopefully we won't see bad box heights any more. We still have text rendering differently at different zoom levels, though.
4TL2FLYP36JS4K6QBXZBCFCTIBMIT7OAF7KMAAFGPAF5OWSP4QFAC
{"clip":236,"on.mouse_released":178,"clip_all":265,"on.update":315,"box_height":297,"on.keychord_pressed":311,"compute_layout":301,"scale":7,"Page":312,"Box_heights":277,"to_text":180,"B":316,"Surface":196,"Cursor_node":172,"on.draw":310,"font":228,"vx":5,"on.code_changed":306,"Viewport":303,"on.textinput":177,"A":309,"vy":8,"parent":315,"on.initialize":304,"on":1,"initialize_editor":313,"on.mouse_pressed":179}
B = function()
-- recompute various aspects based on the current viewport settings
love.graphics.setFont(love.graphics.newFont(scale(20))) -- editor objects implicitly depend on current font so update it
for _,obj in ipairs(Surface) do
if obj.type == 'line' then
obj.zdata = {}
for i=1,#obj.data,2 do
table.insert(obj.zdata, vx(obj.data[i]))
table.insert(obj.zdata, vy(obj.data[i+1]))
end
elseif obj.type == 'bezier' then
zdata = {}
for i=1,#obj.data,2 do
table.insert(zdata, vx(obj.data[i]))
table.insert(zdata, vy(obj.data[i+1]))
end
obj.zdata = love.math.newBezierCurve(zdata):render()
elseif obj.type == 'text' then
if obj.w then
initialize_editor(obj)
else
obj.text = love.graphics.newText(love.graphics.getFont(), obj.data)
end
end
end
end
on.update = function(dt)
if Pan then
Viewport.x = Pan.x - love.mouse.getX()
Viewport.y = Pan.y - love.mouse.getY()
end
if App.mouse_down(1) then
B()
clip_all()
end
end
{"Box_heights":277,"on.textinput":177,"B":314,"on.mouse_pressed":179,"on":1,"Surface":196,"clip_all":265,"clip":236,"on.draw":310,"Viewport":303,"scale":7,"on.update":315,"on.keychord_pressed":311,"Page":312,"on.mouse_released":178,"vy":8,"initialize_editor":313,"box_height":297,"to_text":180,"compute_layout":301,"vx":5,"parent":314,"Cursor_node":172,"on.initialize":304,"font":228,"A":309,"on.code_changed":306}
{"Box_heights":277,"on.textinput":177,"B":314,"on.mouse_pressed":179,"on":1,"Surface":196,"clip_all":265,"clip":236,"on.draw":310,"Viewport":303,"scale":7,"on.update":239,"on.keychord_pressed":311,"Page":312,"on.mouse_released":178,"vy":8,"initialize_editor":313,"box_height":297,"to_text":180,"compute_layout":301,"vx":5,"parent":313,"Cursor_node":172,"on.initialize":304,"font":228,"A":309,"on.code_changed":306}
B = function()
-- editor objects implicitly depend on current font so update it
love.graphics.setFont(love.graphics.newFont(scale(20)))
for _,obj in ipairs(Surface) do
if obj.type == 'line' then
obj.zdata = {}
for i=1,#obj.data,2 do
table.insert(obj.zdata, vx(obj.data[i]))
table.insert(obj.zdata, vy(obj.data[i+1]))
end
elseif obj.type == 'bezier' then
zdata = {}
for i=1,#obj.data,2 do
table.insert(zdata, vx(obj.data[i]))
table.insert(zdata, vy(obj.data[i+1]))
end
obj.zdata = love.math.newBezierCurve(zdata):render()
elseif obj.type == 'text' then
if obj.w then
initialize_editor(obj)
else
obj.text = love.graphics.newText(love.graphics.getFont(), obj.data)
end
end
end
end
{"Viewport":303,"Box_heights":277,"vy":8,"A":309,"on.mouse_pressed":179,"to_text":180,"on.mouse_released":178,"B":308,"on.update":239,"parent":312,"on.keychord_pressed":311,"compute_layout":301,"clip_all":265,"box_height":297,"Cursor_node":172,"initialize_editor":313,"Page":312,"font":228,"on.code_changed":306,"scale":7,"on.textinput":177,"Surface":196,"on":1,"on.initialize":304,"on.draw":310,"clip":236,"vx":5}
initialize_editor = function(obj)
if obj.w then
-- use an editor to wrap the text
local scaled_fontsize = scale(20)
local scaled_lineheight = math.floor(scaled_fontsize*1.3)
print('init', obj.data[1], obj.w)
obj.editor = edit.initialize_state(vy(obj.y), vx(obj.x), vx(obj.x+obj.w), scaled_fontsize, scaled_lineheight)
obj.editor.lines = load_array(obj.data)
Text.redraw_all(obj.editor)
end
end
{"Viewport":303,"Box_heights":277,"vy":8,"A":309,"on.mouse_pressed":179,"to_text":180,"on.mouse_released":178,"B":308,"on.update":239,"parent":311,"on.keychord_pressed":311,"compute_layout":301,"clip_all":265,"box_height":297,"Cursor_node":172,"initialize_editor":300,"Page":312,"font":228,"on.code_changed":306,"scale":7,"on.textinput":177,"Surface":196,"on":1,"on.initialize":304,"on.draw":310,"clip":236,"vx":5}
Page = {
-- page
type='cols', x=0, y=0,
width=800, data={
-- editor covering left side
{
type='text',
name='editor',
doc='prose goes here, on the left half of the window',
margin=Margin_left,
data={
-- "Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
'1',
'2',
'3',
'mno',
'Acb',
'g',
'hij',
'klm',
'nop',
},
width=400, bg={r=1,g=1,b=0}
},
-- a table on the right
{ type='rows', name='searches', margin=50, data={
{ type='text', data={''},},
{ type='cols', data={
{ type='text', data={'search:'},},
{ type='text', name='search', bg={r=0.8,g=0.8,b=0.8}, data={''}, width=90,},
}},
{ type='text', data={'table:'},},
{ type='cols', bg={r=0.8,g=0.8,b=0.8}, data={
{ type='rows', width=90, data={
{type='text', data={'abc'},},
{type='text', data={'abc'},},
}},
{ type='rows', width=90, data={
{type='text', data={'def'},},
{type='text', data={'def'},},
}},
}},
}},
},
}
on.keychord_pressed = function(chord, key)
if chord == 'C-=' then
-- zoom in
Viewport.zoom = Viewport.zoom+0.1
B()
elseif chord == 'C--' then
-- zoom out
Viewport.zoom = Viewport.zoom-0.1
B()
elseif chord == 'C-0' then
-- reset zoom
Viewport.zoom = 1.0
B()
elseif Cursor_node then
edit.keychord_pressed(Cursor_node.editor, chord, key)
end
end
{"Viewport":303,"Box_heights":277,"vy":8,"A":309,"on.mouse_pressed":179,"to_text":180,"on.mouse_released":178,"B":308,"on.update":239,"parent":310,"on.keychord_pressed":311,"compute_layout":301,"clip_all":265,"box_height":297,"Cursor_node":172,"initialize_editor":300,"Page":296,"font":228,"on.code_changed":306,"scale":7,"on.textinput":177,"Surface":196,"on":1,"on.initialize":304,"on.draw":310,"clip":236,"vx":5}
on.draw = function()
love.graphics.setColor(1,0,0)
for y=0,1000,100 do
love.graphics.line(vx(-5),vy(y), vx(5),vy(y))
end
for x=0,1000,100 do
love.graphics.line(vx(x),vy(-5), vx(x),vy(5))
end
love.graphics.setColor(0,1,0)
for _,y in ipairs(Box_heights) do
love.graphics.line(vx(0), vy(y), vx(10), vy(y))
end
for _,obj in ipairs(Surface) do
love.graphics.setColor(obj.r or 0, obj.g or 0, obj.b or 0)
if obj.type == 'rectangle' then
love.graphics.rectangle(obj.drawmode or 'fill', vx(obj.x),vy(obj.y), scale(obj.w),scale(obj.h))
elseif obj.type == 'line' then
love.graphics.line(unpack(obj.zdata))
elseif obj.type == 'circle' then
love.graphics.circle(obj.drawmode or 'fill', vx(obj.x), vy(obj.y), scale(obj.radius))
elseif obj.type == 'arc' then
love.graphics.arc(obj.drawmode or 'line', obj.arctype or 'open', vx(obj.x), vy(obj.y), scale(obj.radius), obj.angle1, obj.angle2, obj.segments)
elseif obj.type == 'ellipse' then
love.graphics.ellipse(obj.drawmode or 'fill', vx(obj.x), vy(obj.y), scale(obj.radiusx), scale(obj.radiusy))
elseif obj.type == 'bezier' then
love.graphics.line(unpack(obj.zdata))
elseif obj.type == 'text' then
if obj.w == nil then
love.graphics.draw(obj.text, vx(obj.x), vy(obj.y))
else
edit.draw(obj.editor, obj.fg or {r=0,g=0,b=0}, not obj.show_cursor)
end
end
end
end
{"Viewport":303,"Box_heights":277,"vy":8,"A":309,"on.mouse_pressed":179,"to_text":180,"on.mouse_released":178,"B":308,"on.update":239,"parent":309,"on.keychord_pressed":200,"compute_layout":301,"clip_all":265,"box_height":297,"Cursor_node":172,"initialize_editor":300,"Page":296,"font":228,"on.code_changed":306,"scale":7,"on.textinput":177,"Surface":196,"on":1,"on.initialize":304,"on.draw":310,"clip":236,"vx":5}
{"Viewport":303,"Box_heights":277,"vy":8,"A":309,"on.mouse_pressed":179,"to_text":180,"on.mouse_released":178,"B":308,"on.update":239,"parent":308,"on.keychord_pressed":200,"compute_layout":301,"clip_all":265,"box_height":297,"Cursor_node":172,"initialize_editor":300,"Page":296,"font":228,"on.code_changed":306,"scale":7,"on.textinput":177,"Surface":196,"on":1,"on.initialize":304,"on.draw":307,"clip":236,"vx":5}
A = function()
-- translate Page to Surface
while #Surface > 3 do table.remove(Surface) end -- HACK
compute_layout(Page, Page.x,Page.y, Surface)
-- continue the pipeline
B()
end
{"Viewport":303,"Box_heights":277,"vy":8,"A":305,"on.mouse_pressed":179,"to_text":180,"on.mouse_released":178,"B":308,"on.update":239,"parent":307,"on.keychord_pressed":200,"compute_layout":301,"clip_all":265,"box_height":297,"Cursor_node":172,"initialize_editor":300,"Page":296,"font":228,"on.code_changed":306,"scale":7,"on.textinput":177,"Surface":196,"on":1,"on.initialize":304,"on.draw":307,"clip":236,"vx":5}
B = function()
for _,obj in ipairs(Surface) do
if obj.type == 'line' then
obj.zdata = {}
for i=1,#obj.data,2 do
table.insert(obj.zdata, vx(obj.data[i]))
table.insert(obj.zdata, vy(obj.data[i+1]))
end
elseif obj.type == 'bezier' then
zdata = {}
for i=1,#obj.data,2 do
table.insert(zdata, vx(obj.data[i]))
table.insert(zdata, vy(obj.data[i+1]))
end
obj.zdata = love.math.newBezierCurve(zdata):render()
elseif obj.type == 'text' then
if obj.w then
initialize_editor(obj)
else
obj.text = love.graphics.newText(love.graphics.getFont(), obj.data)
end
end
end
end
on.draw = function()
love.graphics.setColor(1,0,0)
for y=0,1000,100 do
love.graphics.line(vx(-5),vy(y), vx(5),vy(y))
end
for x=0,1000,100 do
love.graphics.line(vx(x),vy(-5), vx(x),vy(5))
end
love.graphics.setColor(0,1,0)
for _,y in ipairs(Box_heights) do
love.graphics.line(vx(0), vy(y), vx(10), vy(y))
end
for _,obj in ipairs(Surface) do
love.graphics.setColor(obj.r or 0, obj.g or 0, obj.b or 0)
if obj.type == 'rectangle' then
love.graphics.rectangle(obj.drawmode or 'fill', vx(obj.x),vy(obj.y), scale(obj.w),scale(obj.h))
elseif obj.type == 'line' then
if obj.saved_zoom ~= Viewport.zoom or obj.saved_x ~= Viewport.x or obj.saved_y ~= Viewport.y then
obj.saved_zoom = Viewport.zoom
obj.saved_x = Viewport.x
obj.saved_y = Viewport.y
obj.zdata = {}
for i=1,#obj.data,2 do
table.insert(obj.zdata, vx(obj.data[i]))
table.insert(obj.zdata, vy(obj.data[i+1]))
end
end
love.graphics.line(unpack(obj.zdata))
elseif obj.type == 'circle' then
love.graphics.circle(obj.drawmode or 'fill', vx(obj.x), vy(obj.y), scale(obj.radius))
elseif obj.type == 'arc' then
love.graphics.arc(obj.drawmode or 'line', obj.arctype or 'open', vx(obj.x), vy(obj.y), scale(obj.radius), obj.angle1, obj.angle2, obj.segments)
elseif obj.type == 'ellipse' then
love.graphics.ellipse(obj.drawmode or 'fill', vx(obj.x), vy(obj.y), scale(obj.radiusx), scale(obj.radiusy))
elseif obj.type == 'bezier' then
if obj.saved_zoom ~= Viewport.zoom or obj.saved_x ~= Viewport.x or obj.saved_y ~= Viewport.y then
obj.saved_zoom = Viewport.zoom
obj.saved_x = Viewport.x
obj.saved_y = Viewport.y
zdata = {}
for i=1,#obj.data,2 do
table.insert(zdata, vx(obj.data[i]))
table.insert(zdata, vy(obj.data[i+1]))
end
obj.zdata = love.math.newBezierCurve(zdata):render()
end
love.graphics.line(unpack(obj.zdata))
elseif obj.type == 'text' then
local scaled_fontsize = scale(obj.fontsize or 20)
if obj.saved_zoom ~= Viewport.zoom or obj.saved_x ~= Viewport.x or obj.saved_y ~= Viewport.y or obj.scaled_fontsize ~= scaled_fontsize then
obj.saved_zoom = Viewport.zoom
obj.saved_x = Viewport.x
obj.saved_y = Viewport.y
obj.scaled_fontsize = scaled_fontsize
print('setting font', scaled_fontsize)
love.graphics.setFont(font(scaled_fontsize))
obj.text = love.graphics.newText(love.graphics.getFont(), obj.data)
initialize_editor(obj)
end
if obj.w == nil then
love.graphics.draw(obj.text, vx(obj.x), vy(obj.y))
else
edit.draw(obj.editor, obj.fg or {r=0,g=0,b=0}, not obj.show_cursor)
end
end
end
end
{"Viewport":303,"Box_heights":277,"vy":8,"on.mouse_pressed":179,"to_text":180,"on.mouse_released":178,"A":305,"on.update":239,"parent":306,"on.keychord_pressed":200,"compute_layout":301,"clip_all":265,"box_height":297,"Cursor_node":172,"initialize_editor":300,"Page":296,"font":228,"on.code_changed":306,"scale":7,"on.textinput":177,"Surface":196,"on":1,"on.initialize":304,"on.draw":307,"clip":236,"vx":5}
on.code_changed = function()
print('code changed')
A() -- just in case we edited Page
end
{"Viewport":303,"Box_heights":277,"vy":8,"on.mouse_pressed":179,"to_text":180,"on.mouse_released":178,"A":305,"on.update":239,"parent":305,"on.keychord_pressed":200,"compute_layout":301,"clip_all":265,"box_height":297,"Cursor_node":172,"initialize_editor":300,"Page":296,"font":228,"on.code_changed":306,"scale":7,"on.textinput":177,"Surface":196,"on":1,"on.initialize":304,"on.draw":293,"clip":236,"vx":5}
{"Viewport":303,"Box_heights":277,"vy":8,"on.mouse_pressed":179,"to_text":180,"on.mouse_released":178,"A":305,"on.update":239,"parent":304,"on.keychord_pressed":200,"compute_layout":301,"clip_all":265,"box_height":297,"Cursor_node":172,"initialize_editor":300,"Page":296,"font":228,"on.code_changed":299,"scale":7,"on.textinput":177,"Surface":196,"on":1,"on.initialize":304,"on.draw":293,"clip":236,"vx":5}
A = function()
-- translate Page to Surface
while #Surface > 3 do table.remove(Surface) end -- HACK
compute_layout(Page, Page.x,Page.y, Surface)
end
on.initialize = function()
on.code_changed()
end
{"Viewport":303,"Box_heights":277,"vy":8,"on.mouse_pressed":179,"to_text":180,"on.mouse_released":178,"on.update":239,"parent":303,"on.keychord_pressed":200,"compute_layout":301,"clip_all":265,"box_height":297,"Cursor_node":172,"initialize_editor":300,"Page":296,"font":228,"on.code_changed":299,"scale":7,"on.textinput":177,"Surface":196,"on":1,"on.initialize":304,"on.draw":293,"clip":236,"vx":5}
{"Viewport":303,"Box_heights":277,"vy":8,"on.mouse_pressed":179,"to_text":180,"on.mouse_released":178,"on.update":239,"parent":302,"on.keychord_pressed":200,"compute_layout":301,"box_height":297,"Cursor_node":172,"initialize_editor":300,"Page":296,"font":228,"on.code_changed":299,"scale":7,"on.textinput":177,"Surface":196,"on":1,"clip_all":265,"on.draw":293,"clip":236,"vx":5}
Viewport = {x=-50, y=-50, w=800,h=600, zoom=1.0}
{"Viewport":302,"Box_heights":277,"vy":8,"on.mouse_pressed":179,"to_text":180,"on.mouse_released":178,"on.update":239,"parent":301,"on.keychord_pressed":200,"compute_layout":301,"box_height":297,"Cursor_node":172,"initialize_editor":300,"Page":296,"font":228,"on.code_changed":299,"scale":7,"on.textinput":177,"Surface":196,"on":1,"clip_all":265,"on.draw":293,"clip":236,"vx":5}
Viewport = {x=-50, y=-50, w=800,h=600, zoom=1.2}
{"Viewport":298,"Box_heights":277,"vy":8,"on.mouse_pressed":179,"to_text":180,"on.mouse_released":178,"on.update":239,"parent":300,"on.keychord_pressed":200,"compute_layout":301,"box_height":297,"Cursor_node":172,"initialize_editor":300,"Page":296,"font":228,"on.code_changed":299,"scale":7,"on.textinput":177,"Surface":196,"on":1,"clip_all":265,"on.draw":293,"clip":236,"vx":5}
compute_layout = function(node, x,y, nodes_to_render)
-- append to nodes_to_render flattened instructions to render a hierarchy of nodes
-- return x,y rendered until (surface coordinates)
if node.type == 'text' then
-- leaf node containing raw text
node.x = x
node.y = y
-- render background if necessary
local node_to_render
if node.bg then
node_to_render = {type='rectangle', r=node.bg.r, g=node.bg.g, b=node.bg.b, x=node.x, y=node.y}
table.insert(nodes_to_render, node_to_render)
end
-- render contents
if node.width then
node.w = node.width
else
local scaled_fontsize = scale(node.fontsize or 20)
node.w = 0
for i,s in ipairs(node.data) do
local text = love.graphics.newText(font(scaled_fontsize), node.data)
local width = text:getWidth()
print(node.data[i], 'has width', width)
if node.w < width then node.w = width end
end
end
initialize_editor(node)
node.h = box_height(node)
table.insert(nodes_to_render, node)
if node_to_render then
node_to_render.w = node.w
node_to_render.h = node.h
end
elseif node.type == 'rows' then
node.x = x
node.y = y
local node_to_render
if node.bg then
node_to_render = {type='rectangle', r=node.bg.r, g=node.bg.g, b=node.bg.b, x=node.x, y=node.y}
table.insert(nodes_to_render, node_to_render)
end
-- lay out children top to bottom
local subx,suby = x,y
local w,h = 0,0
local subnodes
for _,child in ipairs(node.data) do
if child.margin then
suby = suby+child.margin
h = h+child.margin
end
subx,suby = compute_layout(child, x,suby, nodes_to_render)
if w < child.w then
w = child.w
end
h = h+child.h
end
node.w = w
node.h = h
if node_to_render then
node_to_render.w = w
node_to_render.h = h
end
elseif node.type == 'cols' then
node.x = x
node.y = y
-- lay out children left to right
local node_to_render
if node.bg then
node_to_render = {type='rectangle', r=node.bg.r, g=node.bg.g, b=node.bg.b, x=node.x, y=node.y}
table.insert(nodes_to_render, node_to_render)
end
local subx,suby = x,y
local w,h = 0,0
for _,child in ipairs(node.data) do
if child.margin then
subx = subx+child.margin
w = w+child.margin
end
subx,suby = compute_layout(child, subx,y, nodes_to_render)
w = w + child.w
if h < child.h then
h = child.h
end
end
node.w = w
node.h = h
if node_to_render then
node_to_render.w = w
node_to_render.h = h
end
end
return x+node.w,y+node.h
end
{"Viewport":298,"Box_heights":277,"vy":8,"on.mouse_pressed":179,"to_text":180,"on.mouse_released":178,"on.update":239,"parent":299,"on.keychord_pressed":200,"compute_layout":235,"box_height":297,"Cursor_node":172,"initialize_editor":300,"Page":296,"font":228,"on.code_changed":299,"scale":7,"on.textinput":177,"Surface":196,"on":1,"clip_all":265,"on.draw":293,"clip":236,"vx":5}
initialize_editor = function(obj)
if obj.w then
-- use an editor to wrap the text
local scaled_fontsize = scale(obj.fontsize or 20)
local scaled_lineheight = math.floor(scaled_fontsize*1.3)
print('init', obj.data[1], obj.w)
obj.editor = edit.initialize_state(vy(obj.y), vx(obj.x), vx(obj.x+obj.w), scaled_fontsize, scaled_lineheight)
obj.editor.lines = load_array(obj.data)
Text.redraw_all(obj.editor)
end
end
on.code_changed = function()
while #Surface > 3 do
table.remove(Surface)
end
print('code changed', Page.x,Page.y)
compute_layout(Page, Page.x,Page.y, Surface) -- just in case we edited Page
end
{"Viewport":298,"Box_heights":277,"vy":8,"on.mouse_pressed":179,"to_text":180,"on.mouse_released":178,"on.update":239,"parent":298,"on.keychord_pressed":200,"compute_layout":235,"box_height":297,"Cursor_node":172,"initialize_editor":290,"Page":296,"font":228,"on.code_changed":299,"scale":7,"on.textinput":177,"Surface":196,"on":1,"clip_all":265,"on.draw":293,"clip":236,"vx":5}