about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2022-05-17 22:36:10 -0700
committerKartik K. Agaram <vc@akkartik.com>2022-05-17 22:43:41 -0700
commit222a11a8dd0c8b5b68b5628928a38f05d0a3d13c (patch)
tree5454f0927eac37ea4d35774acc633f766554c130
parent82742330f8760865bbfc21705a809dcb61403d7d (diff)
downloadlines.love-222a11a8dd0c8b5b68b5628928a38f05d0a3d13c.tar.gz
split keyboard handling between Text and Drawing
-rw-r--r--drawing.lua165
-rw-r--r--geom.lua6
-rw-r--r--main.lua290
-rw-r--r--text.lua132
4 files changed, 302 insertions, 291 deletions
diff --git a/drawing.lua b/drawing.lua
index 30182a2..9592587 100644
--- a/drawing.lua
+++ b/drawing.lua
@@ -49,6 +49,159 @@ function Drawing.draw(line, y)
   Drawing.draw_pending_shape(16,line.y, line)
 end
 
+function Drawing.keychord_pressed(chord)
+  if chord == 'C-=' then
+    Drawing_width = Drawing_width/Zoom
+    Zoom = Zoom+0.5
+    Drawing_width = Drawing_width*Zoom
+  elseif chord == 'C--' then
+    Drawing_width = Drawing_width/Zoom
+    Zoom = Zoom-0.5
+    Drawing_width = Drawing_width*Zoom
+  elseif chord == 'C-0' then
+    Drawing_width = Drawing_width/Zoom
+    Zoom = 1.5
+    Drawing_width = Drawing_width*Zoom
+  elseif chord == 'escape' and love.mouse.isDown('1') then
+    local drawing = Drawing.current_drawing()
+    drawing.pending = {}
+  elseif chord == 'C-f' and not love.mouse.isDown('1') then
+    Current_drawing_mode = 'freehand'
+  elseif chord == 'C-g' and not love.mouse.isDown('1') then
+    Current_drawing_mode = 'polygon'
+  elseif love.mouse.isDown('1') and chord == 'g' then
+    Current_drawing_mode = 'polygon'
+    local drawing = Drawing.current_drawing()
+    if drawing.pending.mode == 'freehand' then
+      drawing.pending.vertices = {Drawing.insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y)}
+    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 == 'circle' or drawing.pending.mode == 'arc' then
+      drawing.pending.vertices = {drawing.pending.center}
+    end
+    drawing.pending.mode = 'polygon'
+  elseif love.mouse.isDown('1') and chord == 'p' and Current_drawing_mode == 'polygon' then
+    local drawing = Drawing.current_drawing()
+    local mx,my = Drawing.coord(love.mouse.getX()-16), Drawing.coord(love.mouse.getY()-drawing.y)
+    local j = Drawing.insert_point(drawing.points, mx,my)
+    table.insert(drawing.pending.vertices, j)
+  elseif chord == 'C-c' and not love.mouse.isDown('1') then
+    Current_drawing_mode = 'circle'
+  elseif love.mouse.isDown('1') and chord == 'a' and Current_drawing_mode == 'circle' then
+    local drawing = Drawing.current_drawing()
+    drawing.pending.mode = 'arc'
+    local mx,my = Drawing.coord(love.mouse.getX()-16), Drawing.coord(love.mouse.getY()-drawing.y)
+    local j = Drawing.insert_point(drawing.points, mx,my)
+    local center = drawing.points[drawing.pending.center]
+    drawing.pending.radius = geom.dist(center.x,center.y, mx,my)
+    drawing.pending.start_angle = geom.angle(center.x,center.y, mx,my)
+  elseif love.mouse.isDown('1') and chord == 'c' then
+    Current_drawing_mode = 'circle'
+    local drawing = Drawing.current_drawing()
+    if drawing.pending.mode == 'freehand' then
+      drawing.pending.center = Drawing.insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y)
+    elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
+      drawing.pending.center = drawing.pending.p1
+    elseif drawing.pending.mode == 'polygon' then
+      drawing.pending.center = drawing.pending.vertices[1]
+    end
+    drawing.pending.mode = 'circle'
+  elseif love.mouse.isDown('1') and chord == 'l' then
+    Current_drawing_mode = 'line'
+    local drawing = Drawing.current_drawing()
+    if drawing.pending.mode == 'freehand' then
+      drawing.pending.p1 = Drawing.insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y)
+    elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
+      drawing.pending.p1 = drawing.pending.center
+    elseif drawing.pending.mode == 'polygon' then
+      drawing.pending.p1 = drawing.pending.vertices[1]
+    end
+    drawing.pending.mode = 'line'
+  elseif chord == 'C-l' then
+    Current_drawing_mode = 'line'
+    local drawing,_,shape = Drawing.select_shape_at_mouse()
+    if drawing then
+      convert_line(drawing, shape)
+    end
+  elseif love.mouse.isDown('1') and chord == 'm' then
+    Current_drawing_mode = 'manhattan'
+    local drawing = Drawing.select_drawing_at_mouse()
+    if drawing.pending.mode == 'freehand' then
+      drawing.pending.p1 = Drawing.insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y)
+    elseif drawing.pending.mode == 'line' then
+      -- do nothing
+    elseif drawing.pending.mode == 'polygon' 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 love.mouse.isDown('1') then
+    Current_drawing_mode = 'manhattan'
+    local drawing,_,shape = Drawing.select_shape_at_mouse()
+    if drawing then
+      convert_horvert(drawing, shape)
+    end
+  elseif chord == 'C-s' and not love.mouse.isDown('1') then
+    local drawing,_,shape = Drawing.select_shape_at_mouse()
+    if drawing then
+      smoothen(shape)
+    end
+  elseif chord == 'C-v' and not love.mouse.isDown('1') then
+    local drawing,_,p = Drawing.select_point_at_mouse()
+    if drawing then
+      Previous_drawing_mode = Current_drawing_mode
+      Current_drawing_mode = 'move'
+      drawing.pending = {mode=Current_drawing_mode, target_point=p}
+      Lines.current = drawing
+    end
+  elseif love.mouse.isDown('1') and chord == 'v' then
+    local drawing,_,p = Drawing.select_point_at_mouse()
+    if drawing then
+      Previous_drawing_mode = Current_drawing_mode
+      Current_drawing_mode = 'move'
+      drawing.pending = {mode=Current_drawing_mode, target_point=p}
+      Lines.current = drawing
+    end
+  elseif chord == 'C-d' and not love.mouse.isDown('1') then
+    local drawing,i,p = Drawing.select_point_at_mouse()
+    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()
+    if drawing then
+      shape.mode = 'deleted'
+    end
+  elseif chord == 'C-h' and not love.mouse.isDown('1') then
+    local drawing = Drawing.select_drawing_at_mouse()
+    if drawing then
+      drawing.show_help = true
+    end
+  elseif chord == 'escape' and not love.mouse.isDown('1') then
+    for _,line in ipairs(Lines) do
+      if line.mode == 'drawing' then
+        line.show_help = false
+      end
+    end
+  end
+end
+
 function Drawing.current_drawing()
   local x, y = love.mouse.getX(), love.mouse.getY()
   for _,drawing in ipairs(Lines) do
@@ -223,7 +376,7 @@ end
 function Drawing.draw_pending_shape(left,top, drawing)
   local shape = drawing.pending
   if shape.mode == 'freehand' then
-    draw_shape(left,top, drawing, shape)
+    Drawing.draw_shape(left,top, drawing, shape)
   elseif shape.mode == 'line' then
     local p1 = drawing.points[shape.p1]
     local mx,my = Drawing.coord(love.mouse.getX()-16), Drawing.coord(love.mouse.getY()-drawing.y)
@@ -260,7 +413,7 @@ function Drawing.draw_pending_shape(left,top, drawing)
       return
     end
     local cx,cy = Drawing.pixels(center.x)+left, Drawing.pixels(center.y)+top
-    love.graphics.circle('line', cx,cy, math.dist(cx,cy, love.mouse.getX(),love.mouse.getY()))
+    love.graphics.circle('line', cx,cy, geom.dist(cx,cy, love.mouse.getX(),love.mouse.getY()))
   elseif shape.mode == 'arc' then
     local center = drawing.points[shape.center]
     local mx,my = Drawing.coord(love.mouse.getX()-16), Drawing.coord(love.mouse.getY()-drawing.y)
@@ -280,4 +433,12 @@ function Drawing.coord(n)  -- pixels to parts
   return math.floor(n*256/Drawing_width)
 end
 
+function table.find(h, x)
+  for k,v in pairs(h) do
+    if v == x then
+      return k
+    end
+  end
+end
+
 return Drawing
diff --git a/geom.lua b/geom.lua
index 7b71df3..20cc5dd 100644
--- a/geom.lua
+++ b/geom.lua
@@ -11,10 +11,10 @@ function geom.on_shape(x,y, drawing, shape)
     return geom.on_polygon(x,y, drawing, shape)
   elseif shape.mode == 'circle' then
     local center = drawing.points[shape.center]
-    return math.dist(center.x,center.y, x,y) == shape.radius
+    return geom.dist(center.x,center.y, x,y) == shape.radius
   elseif shape.mode == 'arc' then
     local center = drawing.points[shape.center]
-    local dist = math.dist(center.x,center.y, x,y)
+    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
@@ -109,7 +109,7 @@ 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 = math.angle(ox,oy, x,y)
+  local angle = geom.angle(ox,oy, x,y)
   if s > e then
     s,e = e,s
   end
diff --git a/main.lua b/main.lua
index 884a2cf..36ff345 100644
--- a/main.lua
+++ b/main.lua
@@ -245,295 +245,13 @@ function love.mousereleased(x,y, button)
   save_to_disk(Lines, Filename)
 end
 
-function love.textinput(t)
-  if love.mouse.isDown('1') then return end
-  if Lines[Cursor_line].mode == 'drawing' then return end
-  local byte_offset
-  if Cursor_pos > 1 then
-    byte_offset = utf8.offset(Lines[Cursor_line].data, Cursor_pos-1)
-  else
-    byte_offset = 0
-  end
-  Lines[Cursor_line].data = string.sub(Lines[Cursor_line].data, 1, byte_offset)..t..string.sub(Lines[Cursor_line].data, byte_offset+1)
-  Cursor_pos = Cursor_pos+1
-  save_to_disk(Lines, Filename)
-end
-
 function keychord_pressed(chord)
-  -- Don't handle any keys here that would trigger love.textinput above.
-  -- shortcuts for text
-  if chord == 'return' then
-    local byte_offset = utf8.offset(Lines[Cursor_line].data, Cursor_pos)
-    table.insert(Lines, Cursor_line+1, {mode='text', data=string.sub(Lines[Cursor_line].data, byte_offset)})
-    Lines[Cursor_line].data = string.sub(Lines[Cursor_line].data, 1, byte_offset-1)
-    Cursor_line = Cursor_line+1
-    Cursor_pos = 1
-    save_to_disk(Lines, Filename)
-  elseif chord == 'left' then
-    assert(Lines[Cursor_line].mode == 'text')
-    if Cursor_pos > 1 then
-      Cursor_pos = Cursor_pos-1
-    else
-      local new_cursor_line = Cursor_line
-      while new_cursor_line > 1 do
-        new_cursor_line = new_cursor_line-1
-        if Lines[new_cursor_line].mode == 'text' then
-          Cursor_line = new_cursor_line
-          Cursor_pos = #Lines[Cursor_line].data+1
-          break
-        end
-      end
-    end
-  elseif chord == 'right' then
-    assert(Lines[Cursor_line].mode == 'text')
-    if Cursor_pos <= #Lines[Cursor_line].data then
-      Cursor_pos = Cursor_pos+1
-    else
-      local new_cursor_line = Cursor_line
-      while new_cursor_line <= #Lines-1 do
-        new_cursor_line = new_cursor_line+1
-        if Lines[new_cursor_line].mode == 'text' then
-          Cursor_line = new_cursor_line
-          Cursor_pos = 1
-          break
-        end
-      end
-    end
-  elseif chord == 'home' then
-    Cursor_pos = 1
-  elseif chord == 'end' then
-    Cursor_pos = #Lines[Cursor_line].data+1
-  -- transitioning between drawings and text
-  elseif chord == 'backspace' then
-    if Cursor_pos > 1 then
-      local byte_start = utf8.offset(Lines[Cursor_line].data, Cursor_pos-1)
-      local byte_end = utf8.offset(Lines[Cursor_line].data, Cursor_pos)
-      if byte_start then
-        if byte_end then
-          Lines[Cursor_line].data = string.sub(Lines[Cursor_line].data, 1, byte_start-1)..string.sub(Lines[Cursor_line].data, byte_end)
-        else
-          Lines[Cursor_line].data = string.sub(Lines[Cursor_line].data, 1, byte_start-1)
-        end
-        Cursor_pos = Cursor_pos-1
-      end
-    elseif Cursor_line > 1 then
-      if Lines[Cursor_line-1].mode == 'drawing' then
-        table.remove(Lines, Cursor_line-1)
-      else
-        -- join lines
-        Cursor_pos = utf8.len(Lines[Cursor_line-1].data)+1
-        Lines[Cursor_line-1].data = Lines[Cursor_line-1].data..Lines[Cursor_line].data
-        table.remove(Lines, Cursor_line)
-      end
-      Cursor_line = Cursor_line-1
-    end
-    save_to_disk(Lines, Filename)
-  elseif chord == 'delete' then
-    if Cursor_pos <= #Lines[Cursor_line].data then
-      local byte_start = utf8.offset(Lines[Cursor_line].data, Cursor_pos)
-      local byte_end = utf8.offset(Lines[Cursor_line].data, Cursor_pos+1)
-      if byte_start then
-        if byte_end then
-          Lines[Cursor_line].data = string.sub(Lines[Cursor_line].data, 1, byte_start-1)..string.sub(Lines[Cursor_line].data, byte_end)
-        else
-          Lines[Cursor_line].data = string.sub(Lines[Cursor_line].data, 1, byte_start-1)
-        end
-        -- no change to Cursor_pos
-      end
-    elseif Cursor_line < #Lines then
-      if Lines[Cursor_line+1].mode == 'drawing' then
-        table.remove(Lines, Cursor_line+1)
-      else
-        -- join lines
-        Lines[Cursor_line].data = Lines[Cursor_line].data..Lines[Cursor_line+1].data
-        table.remove(Lines, Cursor_line+1)
-      end
-    end
-    save_to_disk(Lines, Filename)
-  elseif chord == 'up' then
-    assert(Lines[Cursor_line].mode == 'text')
-    local new_cursor_line = Cursor_line
-    while new_cursor_line > 1 do
-      new_cursor_line = new_cursor_line-1
-      if Lines[new_cursor_line].mode == 'text' then
-        local old_x = Text.cursor_x(Lines[new_cursor_line].data, Cursor_pos)
-        Cursor_line = new_cursor_line
-        Cursor_pos = Text.nearest_cursor_pos(Lines[Cursor_line].data, old_x, Cursor_pos)
-        break
-      end
-    end
-  elseif chord == 'down' then
-    assert(Lines[Cursor_line].mode == 'text')
-    local new_cursor_line = Cursor_line
-    while new_cursor_line < #Lines do
-      new_cursor_line = new_cursor_line+1
-      if Lines[new_cursor_line].mode == 'text' then
-        local old_x = Text.cursor_x(Lines[new_cursor_line].data, Cursor_pos)
-        Cursor_line = new_cursor_line
-        Cursor_pos = Text.nearest_cursor_pos(Lines[Cursor_line].data, old_x, Cursor_pos)
-        break
-      end
-    end
-  elseif chord == 'C-=' then
-    Drawing_width = Drawing_width/Zoom
-    Zoom = Zoom+0.5
-    Drawing_width = Drawing_width*Zoom
-  elseif chord == 'C--' then
-    Drawing_width = Drawing_width/Zoom
-    Zoom = Zoom-0.5
-    Drawing_width = Drawing_width*Zoom
-  elseif chord == 'C-0' then
-    Drawing_width = Drawing_width/Zoom
-    Zoom = 1.5
-    Drawing_width = Drawing_width*Zoom
-  -- shortcuts for drawings
-  elseif chord == 'escape' and love.mouse.isDown('1') then
-    local drawing = Drawing.current_drawing()
-    drawing.pending = {}
-  elseif chord == 'C-f' and not love.mouse.isDown('1') then
-    Current_drawing_mode = 'freehand'
-  elseif chord == 'C-g' and not love.mouse.isDown('1') then
-    Current_drawing_mode = 'polygon'
-  elseif love.mouse.isDown('1') and chord == 'g' then
-    Current_drawing_mode = 'polygon'
-    local drawing = Drawing.current_drawing()
-    if drawing.pending.mode == 'freehand' then
-      drawing.pending.vertices = {Drawing.insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y)}
-    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 == 'circle' or drawing.pending.mode == 'arc' then
-      drawing.pending.vertices = {drawing.pending.center}
-    end
-    drawing.pending.mode = 'polygon'
-  elseif love.mouse.isDown('1') and chord == 'p' and Current_drawing_mode == 'polygon' then
-    local drawing = Drawing.current_drawing()
-    local mx,my = Drawing.coord(love.mouse.getX()-16), Drawing.coord(love.mouse.getY()-drawing.y)
-    local j = Drawing.insert_point(drawing.points, mx,my)
-    table.insert(drawing.pending.vertices, j)
-  elseif chord == 'C-c' and not love.mouse.isDown('1') then
-    Current_drawing_mode = 'circle'
-  elseif love.mouse.isDown('1') and chord == 'a' and Current_drawing_mode == 'circle' then
-    local drawing = Drawing.current_drawing()
-    drawing.pending.mode = 'arc'
-    local mx,my = Drawing.coord(love.mouse.getX()-16), Drawing.coord(love.mouse.getY()-drawing.y)
-    local j = Drawing.insert_point(drawing.points, mx,my)
-    local center = drawing.points[drawing.pending.center]
-    drawing.pending.radius = math.dist(center.x,center.y, mx,my)
-    drawing.pending.start_angle = geom.angle(center.x,center.y, mx,my)
-  elseif love.mouse.isDown('1') and chord == 'c' then
-    Current_drawing_mode = 'circle'
-    local drawing = Drawing.current_drawing()
-    if drawing.pending.mode == 'freehand' then
-      drawing.pending.center = Drawing.insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y)
-    elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
-      drawing.pending.center = drawing.pending.p1
-    elseif drawing.pending.mode == 'polygon' then
-      drawing.pending.center = drawing.pending.vertices[1]
-    end
-    drawing.pending.mode = 'circle'
-  elseif love.mouse.isDown('1') and chord == 'l' then
-    Current_drawing_mode = 'line'
-    local drawing = Drawing.current_drawing()
-    if drawing.pending.mode == 'freehand' then
-      drawing.pending.p1 = Drawing.insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y)
-    elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
-      drawing.pending.p1 = drawing.pending.center
-    elseif drawing.pending.mode == 'polygon' then
-      drawing.pending.p1 = drawing.pending.vertices[1]
-    end
-    drawing.pending.mode = 'line'
-  elseif chord == 'C-l' then
-    Current_drawing_mode = 'line'
-    local drawing,_,shape = Drawing.select_shape_at_mouse()
-    if drawing then
-      convert_line(drawing, shape)
-    end
-  elseif love.mouse.isDown('1') and chord == 'm' then
-    Current_drawing_mode = 'manhattan'
-    local drawing = Drawing.select_drawing_at_mouse()
-    if drawing.pending.mode == 'freehand' then
-      drawing.pending.p1 = Drawing.insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y)
-    elseif drawing.pending.mode == 'line' then
-      -- do nothing
-    elseif drawing.pending.mode == 'polygon' 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 love.mouse.isDown('1') then
-    Current_drawing_mode = 'manhattan'
-    local drawing,_,shape = Drawing.select_shape_at_mouse()
-    if drawing then
-      convert_horvert(drawing, shape)
-    end
-  elseif chord == 'C-s' and not love.mouse.isDown('1') then
-    local drawing,_,shape = Drawing.select_shape_at_mouse()
-    if drawing then
-      smoothen(shape)
-    end
-  elseif chord == 'C-v' and not love.mouse.isDown('1') then
-    local drawing,_,p = Drawing.select_point_at_mouse()
-    if drawing then
-      Previous_drawing_mode = Current_drawing_mode
-      Current_drawing_mode = 'move'
-      drawing.pending = {mode=Current_drawing_mode, target_point=p}
-      Lines.current = drawing
-    end
-  elseif love.mouse.isDown('1') and chord == 'v' then
-    local drawing,_,p = Drawing.select_point_at_mouse()
-    if drawing then
-      Previous_drawing_mode = Current_drawing_mode
-      Current_drawing_mode = 'move'
-      drawing.pending = {mode=Current_drawing_mode, target_point=p}
-      Lines.current = drawing
-    end
-  elseif chord == 'C-d' and not love.mouse.isDown('1') then
-    local drawing,i,p = Drawing.select_point_at_mouse()
-    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()
-    if drawing then
-      shape.mode = 'deleted'
-    end
-  elseif chord == 'C-h' and not love.mouse.isDown('1') then
-    local drawing = Drawing.select_drawing_at_mouse()
-    if drawing then
-      drawing.show_help = true
-    end
-  elseif chord == 'escape' and not love.mouse.isDown('1') then
-    for _,line in ipairs(Lines) do
-      if line.mode == 'drawing' then
-        line.show_help = false
-      end
-    end
+  if love.mouse.isDown('1') or chord:sub(1,2) == 'C-' then
+    Drawing.keychord_pressed(chord)
+  else
+    Text.keychord_pressed(chord)
   end
 end
 
 function love.keyreleased(key, scancode)
 end
-
-function table.find(h, x)
-  for k,v in pairs(h) do
-    if v == x then
-      return k
-    end
-  end
-end
diff --git a/text.lua b/text.lua
index a9cfcbd..d46edca 100644
--- a/text.lua
+++ b/text.lua
@@ -1,6 +1,8 @@
 -- primitives for editing text
 Text = {}
 
+local utf8 = require 'utf8'
+
 function Text.draw(line, line_index, cursor_line, y, cursor_pos)
   love.graphics.setColor(0,0,0)
   local love_text = love.graphics.newText(love.graphics.getFont(), line.data)
@@ -11,6 +13,136 @@ function Text.draw(line, line_index, cursor_line, y, cursor_pos)
   end
 end
 
+function love.textinput(t)
+  if love.mouse.isDown('1') then return end
+  if Lines[Cursor_line].mode == 'drawing' then return end
+  local byte_offset
+  if Cursor_pos > 1 then
+    byte_offset = utf8.offset(Lines[Cursor_line].data, Cursor_pos-1)
+  else
+    byte_offset = 0
+  end
+  Lines[Cursor_line].data = string.sub(Lines[Cursor_line].data, 1, byte_offset)..t..string.sub(Lines[Cursor_line].data, byte_offset+1)
+  Cursor_pos = Cursor_pos+1
+  save_to_disk(Lines, Filename)
+end
+
+-- Don't handle any keys here that would trigger love.textinput above.
+function Text.keychord_pressed(chord)
+  if chord == 'return' then
+    local byte_offset = utf8.offset(Lines[Cursor_line].data, Cursor_pos)
+    table.insert(Lines, Cursor_line+1, {mode='text', data=string.sub(Lines[Cursor_line].data, byte_offset)})
+    Lines[Cursor_line].data = string.sub(Lines[Cursor_line].data, 1, byte_offset-1)
+    Cursor_line = Cursor_line+1
+    Cursor_pos = 1
+    save_to_disk(Lines, Filename)
+  elseif chord == 'left' then
+    assert(Lines[Cursor_line].mode == 'text')
+    if Cursor_pos > 1 then
+      Cursor_pos = Cursor_pos-1
+    else
+      local new_cursor_line = Cursor_line
+      while new_cursor_line > 1 do
+        new_cursor_line = new_cursor_line-1
+        if Lines[new_cursor_line].mode == 'text' then
+          Cursor_line = new_cursor_line
+          Cursor_pos = #Lines[Cursor_line].data+1
+          break
+        end
+      end
+    end
+  elseif chord == 'right' then
+    assert(Lines[Cursor_line].mode == 'text')
+    if Cursor_pos <= #Lines[Cursor_line].data then
+      Cursor_pos = Cursor_pos+1
+    else
+      local new_cursor_line = Cursor_line
+      while new_cursor_line <= #Lines-1 do
+        new_cursor_line = new_cursor_line+1
+        if Lines[new_cursor_line].mode == 'text' then
+          Cursor_line = new_cursor_line
+          Cursor_pos = 1
+          break
+        end
+      end
+    end
+  elseif chord == 'home' then
+    Cursor_pos = 1
+  elseif chord == 'end' then
+    Cursor_pos = #Lines[Cursor_line].data+1
+  elseif chord == 'backspace' then
+    if Cursor_pos > 1 then
+      local byte_start = utf8.offset(Lines[Cursor_line].data, Cursor_pos-1)
+      local byte_end = utf8.offset(Lines[Cursor_line].data, Cursor_pos)
+      if byte_start then
+        if byte_end then
+          Lines[Cursor_line].data = string.sub(Lines[Cursor_line].data, 1, byte_start-1)..string.sub(Lines[Cursor_line].data, byte_end)
+        else
+          Lines[Cursor_line].data = string.sub(Lines[Cursor_line].data, 1, byte_start-1)
+        end
+        Cursor_pos = Cursor_pos-1
+      end
+    elseif Cursor_line > 1 then
+      if Lines[Cursor_line-1].mode == 'drawing' then
+        table.remove(Lines, Cursor_line-1)
+      else
+        -- join lines
+        Cursor_pos = utf8.len(Lines[Cursor_line-1].data)+1
+        Lines[Cursor_line-1].data = Lines[Cursor_line-1].data..Lines[Cursor_line].data
+        table.remove(Lines, Cursor_line)
+      end
+      Cursor_line = Cursor_line-1
+    end
+    save_to_disk(Lines, Filename)
+  elseif chord == 'delete' then
+    if Cursor_pos <= #Lines[Cursor_line].data then
+      local byte_start = utf8.offset(Lines[Cursor_line].data, Cursor_pos)
+      local byte_end = utf8.offset(Lines[Cursor_line].data, Cursor_pos+1)
+      if byte_start then
+        if byte_end then
+          Lines[Cursor_line].data = string.sub(Lines[Cursor_line].data, 1, byte_start-1)..string.sub(Lines[Cursor_line].data, byte_end)
+        else
+          Lines[Cursor_line].data = string.sub(Lines[Cursor_line].data, 1, byte_start-1)
+        end
+        -- no change to Cursor_pos
+      end
+    elseif Cursor_line < #Lines then
+      if Lines[Cursor_line+1].mode == 'drawing' then
+        table.remove(Lines, Cursor_line+1)
+      else
+        -- join lines
+        Lines[Cursor_line].data = Lines[Cursor_line].data..Lines[Cursor_line+1].data
+        table.remove(Lines, Cursor_line+1)
+      end
+    end
+    save_to_disk(Lines, Filename)
+  elseif chord == 'up' then
+    assert(Lines[Cursor_line].mode == 'text')
+    local new_cursor_line = Cursor_line
+    while new_cursor_line > 1 do
+      new_cursor_line = new_cursor_line-1
+      if Lines[new_cursor_line].mode == 'text' then
+        local old_x = Text.cursor_x(Lines[new_cursor_line].data, Cursor_pos)
+        Cursor_line = new_cursor_line
+        Cursor_pos = Text.nearest_cursor_pos(Lines[Cursor_line].data, old_x, Cursor_pos)
+        break
+      end
+    end
+  elseif chord == 'down' then
+    assert(Lines[Cursor_line].mode == 'text')
+    local new_cursor_line = Cursor_line
+    while new_cursor_line < #Lines do
+      new_cursor_line = new_cursor_line+1
+      if Lines[new_cursor_line].mode == 'text' then
+        local old_x = Text.cursor_x(Lines[new_cursor_line].data, Cursor_pos)
+        Cursor_line = new_cursor_line
+        Cursor_pos = Text.nearest_cursor_pos(Lines[Cursor_line].data, old_x, Cursor_pos)
+        break
+      end
+    end
+  end
+end
+
 function Text.nearest_cursor_pos(line, x, hint)
   if x == 0 then
     return 1