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 settingslove.graphics.setFont(love.graphics.newFont(scale(20))) -- editor objects implicitly depend on current font so update itfor _,obj in ipairs(Surface) doif obj.type == 'line' thenobj.zdata = {}for i=1,#obj.data,2 dotable.insert(obj.zdata, vx(obj.data[i]))table.insert(obj.zdata, vy(obj.data[i+1]))endelseif obj.type == 'bezier' thenzdata = {}for i=1,#obj.data,2 dotable.insert(zdata, vx(obj.data[i]))table.insert(zdata, vy(obj.data[i+1]))endobj.zdata = love.math.newBezierCurve(zdata):render()elseif obj.type == 'text' thenif obj.w theninitialize_editor(obj)elseobj.text = love.graphics.newText(love.graphics.getFont(), obj.data)endendendend
on.update = function(dt)if Pan thenViewport.x = Pan.x - love.mouse.getX()Viewport.y = Pan.y - love.mouse.getY()endif App.mouse_down(1) thenB()clip_all()endend
{"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 itlove.graphics.setFont(love.graphics.newFont(scale(20)))for _,obj in ipairs(Surface) doif obj.type == 'line' thenobj.zdata = {}for i=1,#obj.data,2 dotable.insert(obj.zdata, vx(obj.data[i]))table.insert(obj.zdata, vy(obj.data[i+1]))endelseif obj.type == 'bezier' thenzdata = {}for i=1,#obj.data,2 dotable.insert(zdata, vx(obj.data[i]))table.insert(zdata, vy(obj.data[i+1]))endobj.zdata = love.math.newBezierCurve(zdata):render()elseif obj.type == 'text' thenif obj.w theninitialize_editor(obj)elseobj.text = love.graphics.newText(love.graphics.getFont(), obj.data)endendendend
{"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 textlocal 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)endend
{"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 = {-- pagetype='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 inViewport.zoom = Viewport.zoom+0.1B()elseif chord == 'C--' then-- zoom outViewport.zoom = Viewport.zoom-0.1B()elseif chord == 'C-0' then-- reset zoomViewport.zoom = 1.0B()elseif Cursor_node thenedit.keychord_pressed(Cursor_node.editor, chord, key)endend
{"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 dolove.graphics.line(vx(-5),vy(y), vx(5),vy(y))endfor x=0,1000,100 dolove.graphics.line(vx(x),vy(-5), vx(x),vy(5))endlove.graphics.setColor(0,1,0)for _,y in ipairs(Box_heights) dolove.graphics.line(vx(0), vy(y), vx(10), vy(y))endfor _,obj in ipairs(Surface) dolove.graphics.setColor(obj.r or 0, obj.g or 0, obj.b or 0)if obj.type == 'rectangle' thenlove.graphics.rectangle(obj.drawmode or 'fill', vx(obj.x),vy(obj.y), scale(obj.w),scale(obj.h))elseif obj.type == 'line' thenlove.graphics.line(unpack(obj.zdata))elseif obj.type == 'circle' thenlove.graphics.circle(obj.drawmode or 'fill', vx(obj.x), vy(obj.y), scale(obj.radius))elseif obj.type == 'arc' thenlove.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' thenlove.graphics.ellipse(obj.drawmode or 'fill', vx(obj.x), vy(obj.y), scale(obj.radiusx), scale(obj.radiusy))elseif obj.type == 'bezier' thenlove.graphics.line(unpack(obj.zdata))elseif obj.type == 'text' thenif obj.w == nil thenlove.graphics.draw(obj.text, vx(obj.x), vy(obj.y))elseedit.draw(obj.editor, obj.fg or {r=0,g=0,b=0}, not obj.show_cursor)endendendend
{"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 Surfacewhile #Surface > 3 do table.remove(Surface) end -- HACKcompute_layout(Page, Page.x,Page.y, Surface)-- continue the pipelineB()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) doif obj.type == 'line' thenobj.zdata = {}for i=1,#obj.data,2 dotable.insert(obj.zdata, vx(obj.data[i]))table.insert(obj.zdata, vy(obj.data[i+1]))endelseif obj.type == 'bezier' thenzdata = {}for i=1,#obj.data,2 dotable.insert(zdata, vx(obj.data[i]))table.insert(zdata, vy(obj.data[i+1]))endobj.zdata = love.math.newBezierCurve(zdata):render()elseif obj.type == 'text' thenif obj.w theninitialize_editor(obj)elseobj.text = love.graphics.newText(love.graphics.getFont(), obj.data)endendendend
on.draw = function()love.graphics.setColor(1,0,0)for y=0,1000,100 dolove.graphics.line(vx(-5),vy(y), vx(5),vy(y))endfor x=0,1000,100 dolove.graphics.line(vx(x),vy(-5), vx(x),vy(5))endlove.graphics.setColor(0,1,0)for _,y in ipairs(Box_heights) dolove.graphics.line(vx(0), vy(y), vx(10), vy(y))endfor _,obj in ipairs(Surface) dolove.graphics.setColor(obj.r or 0, obj.g or 0, obj.b or 0)if obj.type == 'rectangle' thenlove.graphics.rectangle(obj.drawmode or 'fill', vx(obj.x),vy(obj.y), scale(obj.w),scale(obj.h))elseif obj.type == 'line' thenif obj.saved_zoom ~= Viewport.zoom or obj.saved_x ~= Viewport.x or obj.saved_y ~= Viewport.y thenobj.saved_zoom = Viewport.zoomobj.saved_x = Viewport.xobj.saved_y = Viewport.yobj.zdata = {}for i=1,#obj.data,2 dotable.insert(obj.zdata, vx(obj.data[i]))table.insert(obj.zdata, vy(obj.data[i+1]))endendlove.graphics.line(unpack(obj.zdata))elseif obj.type == 'circle' thenlove.graphics.circle(obj.drawmode or 'fill', vx(obj.x), vy(obj.y), scale(obj.radius))elseif obj.type == 'arc' thenlove.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' thenlove.graphics.ellipse(obj.drawmode or 'fill', vx(obj.x), vy(obj.y), scale(obj.radiusx), scale(obj.radiusy))elseif obj.type == 'bezier' thenif obj.saved_zoom ~= Viewport.zoom or obj.saved_x ~= Viewport.x or obj.saved_y ~= Viewport.y thenobj.saved_zoom = Viewport.zoomobj.saved_x = Viewport.xobj.saved_y = Viewport.yzdata = {}for i=1,#obj.data,2 dotable.insert(zdata, vx(obj.data[i]))table.insert(zdata, vy(obj.data[i+1]))endobj.zdata = love.math.newBezierCurve(zdata):render()endlove.graphics.line(unpack(obj.zdata))elseif obj.type == 'text' thenlocal 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 thenobj.saved_zoom = Viewport.zoomobj.saved_x = Viewport.xobj.saved_y = Viewport.yobj.scaled_fontsize = scaled_fontsizeprint('setting font', scaled_fontsize)love.graphics.setFont(font(scaled_fontsize))obj.text = love.graphics.newText(love.graphics.getFont(), obj.data)initialize_editor(obj)endif obj.w == nil thenlove.graphics.draw(obj.text, vx(obj.x), vy(obj.y))elseedit.draw(obj.editor, obj.fg or {r=0,g=0,b=0}, not obj.show_cursor)endendendend
{"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 Pageend
{"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 Surfacewhile #Surface > 3 do table.remove(Surface) end -- HACKcompute_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 textnode.x = xnode.y = y-- render background if necessarylocal node_to_renderif node.bg thennode_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 contentsif node.width thennode.w = node.widthelselocal scaled_fontsize = scale(node.fontsize or 20)node.w = 0for i,s in ipairs(node.data) dolocal 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 endendendinitialize_editor(node)node.h = box_height(node)table.insert(nodes_to_render, node)if node_to_render thennode_to_render.w = node.wnode_to_render.h = node.hendelseif node.type == 'rows' thennode.x = xnode.y = ylocal node_to_renderif node.bg thennode_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 bottomlocal subx,suby = x,ylocal w,h = 0,0local subnodesfor _,child in ipairs(node.data) doif child.margin thensuby = suby+child.marginh = h+child.marginendsubx,suby = compute_layout(child, x,suby, nodes_to_render)if w < child.w thenw = child.wendh = h+child.hendnode.w = wnode.h = hif node_to_render thennode_to_render.w = wnode_to_render.h = hendelseif node.type == 'cols' thennode.x = xnode.y = y-- lay out children left to rightlocal node_to_renderif node.bg thennode_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)endlocal subx,suby = x,ylocal w,h = 0,0for _,child in ipairs(node.data) doif child.margin thensubx = subx+child.marginw = w+child.marginendsubx,suby = compute_layout(child, subx,y, nodes_to_render)w = w + child.wif h < child.h thenh = child.hendendnode.w = wnode.h = hif node_to_render thennode_to_render.w = wnode_to_render.h = hendendreturn x+node.w,y+node.hend
{"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 textlocal 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)endend
on.code_changed = function()while #Surface > 3 dotable.remove(Surface)endprint('code changed', Page.x,Page.y)compute_layout(Page, Page.x,Page.y, Surface) -- just in case we edited Pageend
{"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}