about summary refs log tree commit diff stats
path: root/main.lua
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2022-05-14 13:08:33 -0700
committerKartik K. Agaram <vc@akkartik.com>2022-05-14 13:08:33 -0700
commiteaa3820241476cd485c02ee20703bb0a65477db6 (patch)
tree25eea0e4616219c03ca942bf76f65c80d391ee47 /main.lua
parent422dd3c3b7c0a3a2cf702d9def25fd174397c814 (diff)
downloadlines.love-eaa3820241476cd485c02ee20703bb0a65477db6.tar.gz
beginnings of support for multiple shapes
Diffstat (limited to 'main.lua')
-rw-r--r--main.lua157
1 files changed, 114 insertions, 43 deletions
diff --git a/main.lua b/main.lua
index 85181f5..216f61a 100644
--- a/main.lua
+++ b/main.lua
@@ -3,9 +3,39 @@ require 'button'
 require 'repl'
 local utf8 = require 'utf8'
 
+-- lines is an array of lines
+-- a line is either:
+--    a string containing text
+--    or a drawing
+-- a drawing is a table with:
+--    a (y) coord in pixels,
+--    a (h)eight,
+--    an array of points, and
+--    an array of shapes
+-- a shape is a table containing:
+--    a mode
+--    an array points for mode 'freehand' (raw x,y coords; freehand drawings don't pollute the points array of a drawing)
+--    an array vertices for mode 'polygon', 'rectangle', 'square'
+--    p1, p2 for mode 'line'
+--    p1, p2, arrow-mode for mode 'arrow-line'
+--    cx,cy, r for mode 'circle'
+--    pc, r for mode 'circle'
+--    pc, r, s, e for mode 'arc'
+-- Unless otherwise specified, coord fields are normalized; a drawing is always 256 units wide
+-- The field names are carefully chosen so that switching modes in midstream
+-- remembers previously entered points where that makes sense.
+--
+-- Open question: how to maintain Sketchpad-style constraints? Answer for now:
+-- we don't. Constraints operate only for the duration of a drawing operation.
+-- We'll continue to persist them just to keep the option open to continue
+-- solving for them. But for now, this is a program to create static drawings
+-- once, and read them passively thereafter.
 lines = {}
+
 screenw, screenh, screenflags = 0, 0, nil
 
+current_mode = 'freehand'
+
 -- All drawings span 100% of some conceptual 'page width' and divide it up
 -- into 256 parts. `drawingw` describes their width in pixels.
 drawingw = 400  -- pixels
@@ -57,26 +87,14 @@ function love.draw()
       local mx,my = coord(love.mouse.getX()-12), coord(love.mouse.getY()-line.y)
 
       for _,shape in ipairs(line.shapes) do
-        if on_freehand(mx,my, shape) then
+        if on_shape(mx,my, shape) then
           love.graphics.setColor(1,0,0)
         else
           love.graphics.setColor(0,0,0)
         end
-        prev = nil
-        for _,point in ipairs(shape) do
-          if prev then
-            love.graphics.line(pixels(prev.x)+12,pixels(prev.y)+line.y, pixels(point.x)+12,pixels(point.y)+line.y)
-          end
-          prev = point
-        end
-      end
-      prev = nil
-      for _,point in ipairs(line.pending) do
-        if prev then
-          love.graphics.line(pixels(prev.x)+12,pixels(prev.y)+line.y, pixels(point.x)+12,pixels(point.y)+line.y)
-        end
-        prev = point
+        draw_shape(12,line.y, shape)
       end
+      draw_pending_shape(12,line.y, line.pending)
     else
       love.graphics.draw(text, 25,y, 0, 1.5)
     end
@@ -97,7 +115,9 @@ function love.update(dt)
       if type(drawing) == 'table' then
         local x, y = love.mouse.getX(), love.mouse.getY()
         if y >= drawing.y and y < drawing.y + pixels(drawing.h) and x >= 12 and x < 12+drawingw then
-          table.insert(drawing.pending, {x=coord(love.mouse.getX()-12), y=coord(love.mouse.getY()-drawing.y)})
+          if drawing.pending.mode == 'freehand' then
+            table.insert(drawing.pending.points, {x=coord(love.mouse.getX()-12), y=coord(love.mouse.getY()-drawing.y)})
+          end
         end
       end
     end
@@ -109,20 +129,73 @@ function love.mousepressed(x,y, button)
   propagate_to_drawings(x,y, button)
 end
 
+function love.mousereleased(x,y, button)
+  if lines.current then
+    if lines.current.pending then
+      if lines.current.pending.mode == 'freehand' then
+        -- the last point added during update is good enough
+      elseif lines.current.pending.mode == 'line' then
+        lines.current.pending.x2 = coord(x-12)
+        lines.current.pending.y2 = coord(y-lines.current.y)
+      end
+      table.insert(lines.current.shapes, lines.current.pending)
+      lines.current.pending = {}
+      lines.current = nil
+    end
+  end
+end
+
 function propagate_to_drawings(x,y, button)
   for i,drawing in ipairs(lines) do
     if type(drawing) == 'table' then
       local x, y = love.mouse.getX(), love.mouse.getY()
       if y >= drawing.y and y < drawing.y + pixels(drawing.h) and x >= 12 and x < 12+drawingw then
+        if current_mode == 'freehand' then
+          drawing.pending = {mode='freehand', points={x=coord(x-12), y=coord(y-drawing.y)}}
+        elseif current_mode == 'line' then
+          drawing.pending = {mode='line', x1=coord(x-12), y1=coord(y-drawing.y)}
+        end
         lines.current = drawing
       end
     end
   end
 end
 
+function draw_shape(left,top, shape)
+  if shape.mode == 'freehand' then
+    local prev = nil
+    for _,point in ipairs(shape.points) do
+      if prev then
+        love.graphics.line(pixels(prev.x)+left,pixels(prev.y)+top, pixels(point.x)+left,pixels(point.y)+top)
+      end
+      prev = point
+    end
+  elseif shape.mode == 'line' then
+    love.graphics.line(pixels(shape.x1)+left,pixels(shape.y1)+top, pixels(shape.x2)+left,pixels(shape.y2)+top)
+  end
+end
+
+function draw_pending_shape(left,top, shape)
+  if shape.mode == 'freehand' then
+    draw_shape(left,top, shape)
+  elseif shape.mode == 'line' then
+    love.graphics.line(pixels(line.pending.x1)+left,pixels(line.pending.y1)+top, love.mouse.getX(),love.mouse.getY())
+  end
+end
+
+function on_shape(x,y, shape)
+  if shape.mode == 'freehand' then
+    return on_freehand(x,y, shape)
+  elseif shape.mode == 'line' then
+    return on_line(x,y, shape)
+  else
+    assert(false)
+  end
+end
+
 function on_freehand(x,y, shape)
   local prev
-  for _,p in ipairs(shape) do
+  for _,p in ipairs(shape.points) do
     if prev then
       if on_line(x,y, {x1=prev.x,y1=prev.y, x2=p.x,y2=p.y}) then
         return true
@@ -155,16 +228,6 @@ function on_line(x,y, shape)
   return k > -0.05 and k < 1.05
 end
 
-function love.mousereleased(x,y, button)
-  if lines.current then
-    if lines.current.pending then
-      table.insert(lines.current.shapes, lines.current.pending)
-      lines.current.pending = {}
-      lines.current = nil
-    end
-  end
-end
-
 function love.textinput(t)
   lines[#lines] = lines[#lines]..t
 end
@@ -190,12 +253,12 @@ function keychord_pressed(chord)
   elseif chord == 'C-l' then
     local drawing,i,shape = select_shape_at_mouse()
     if drawing then
-      convert_line(drawing,i,shape)
+      convert_line(shape)
     end
   elseif chord == 'C-m' then
     local drawing,i,shape = select_shape_at_mouse()
     if drawing then
-      convert_horvert(drawing,i,shape)
+      convert_horvert(shape)
     end
   elseif chord == 'C-s' then
     local drawing,i,shape = select_shape_at_mouse()
@@ -212,7 +275,7 @@ function select_shape_at_mouse()
       if y >= drawing.y and y < drawing.y + pixels(drawing.h) and x >= 12 and x < 12+drawingw then
         local mx,my = coord(love.mouse.getX()-12), coord(love.mouse.getY()-drawing.y)
         for i,shape in ipairs(drawing.shapes) do
-          if on_freehand(mx,my, shape) then
+          if on_shape(mx,my, shape) then
             return drawing,i,shape
           end
         end
@@ -221,31 +284,39 @@ function select_shape_at_mouse()
   end
 end
 
-function convert_line(drawing, i, shape)
+function convert_line(shape)
   -- Perhaps we should do a more sophisticated "simple linear regression"
   -- here:
   --   https://en.wikipedia.org/wiki/Linear_regression#Simple_and_multiple_linear_regression
   -- But this works well enough for close-to-linear strokes.
-  drawing.shapes[i] = {shape[1], shape[#shape]}
+  assert(shape.mode == 'freehand')
+  shape.mode = 'line'
+  shape.x1 = shape.points[1].x
+  shape.y1 = shape.points[1].y
+  shape.x2 = shape.points[#shape.points].x
+  shape.y2 = shape.points[#shape.points].y
 end
 
--- turn a stroke into either a horizontal or vertical line
-function convert_horvert(drawing, i, shape)
-  local x1,y1 = shape[1].x, shape[1].y
-  local x2,y2 = shape[#shape].x, shape[#shape].y
-  if math.abs(x1-x2) > math.abs(y1-y2) then
-    drawing.shapes[i] = {{x=x1, y=y1}, {x=x2, y=y1}}
+-- turn a line either horizontal or vertical
+function convert_horvert(shape)
+  if shape.mode == 'freehand' then
+    convert_line(shape)
+  end
+  assert(shape.mode == 'line')
+  if math.abs(shape.x1-shape.x2) > math.abs(shape.y1-shape.y2) then
+    shape.y2 = shape.y1
   else
-    drawing.shapes[i] = {{x=x1, y=y1}, {x=x1, y=y2}}
+    shape.x2 = shape.x1
   end
 end
 
 function smoothen(shape)
+  assert(shape.mode == 'freehand')
   for _=1,7 do
-    for i=2,#shape-1 do
-      local a = shape[i-1]
-      local b = shape[i]
-      local c = shape[i+1]
+    for i=2,#shape.points-1 do
+      local a = shape.points[i-1]
+      local b = shape.points[i]
+      local c = shape.points[i+1]
       b.x = (a.x + b.x + c.x)/3
       b.y = (a.y + b.y + c.y)/3
     end