Skip to content

Instantly share code, notes, and snippets.

@akkartik
Last active April 24, 2024 05:05
Show Gist options
  • Save akkartik/20a0c7e7589c6179666fc78bc04b28b4 to your computer and use it in GitHub Desktop.
Save akkartik/20a0c7e7589c6179666fc78bc04b28b4 to your computer and use it in GitHub Desktop.
Little UI for compass-and-straightedge constructions using https://love2d.org
-- To run:
-- Download LÖVE from https://love2d.org
-- Download this file to a directory and rename it to `main.lua`
-- Run the program from that directory.
-- * on Linux (using the appimage binary): `chmod +x path/to/love-11.4-x86_64.AppImage; path/to/love-11.4-x86_64.AppImage .`
-- * on Mac: `path/to/love.app/Contents/MacOS/love .`
-- maximize window
love.window.setMode(0, 0)
width, height, flags = love.window.getMode()
-- shrink slightly to account for window decoration
width = width-100
height = height-100
love.window.setMode(width, height)
love.window.setTitle('Love Geometry')
-- the window has 3 major parts: menu, drawing area and status bar
status_height = 20
menux, menuy = 5, 5
menuw, menuh = width-10, 100
shapex, shapey = 5, 110
shapew, shapeh = width-10, height-status_height-115
-- state that affects the shape currently being drawn
current_mode = 'line' -- valid modes: dot, line, radius, center, arcbegin, arcend
current_red = 0 -- red component of current color (green and blue are always 0)
current_radius = 100
current_end_angle = nil
pending = {}
-- shapes drawn in the past
shapes_committed = {}
function love.draw()
draw_shapes()
draw_menu()
end
-- ### Rendering shapes
-- This section takes current_mode largely for granted.
function draw_shapes()
love.graphics.setColor(1,1,1)
love.graphics.rectangle('fill', shapex,shapey, shapew,shapeh)
for i, v in ipairs(shapes_committed) do
love.graphics.setColor(v.R, 0, 0)
if v.shape == 'line' then
love.graphics.line(v.x1,v.y1, v.x2,v.y2)
elseif v.shape == 'point' then
love.graphics.circle('fill', v.x,v.y, 2)
elseif v.shape == 'arc' then
love.graphics.arc('line', 'open', v.cx,v.cy, v.r, v.s,v.e, 360)
end
end
if pending.shape == 'arc' then
love.graphics.setColor(0.75, 0.75, 0.75)
love.graphics.circle('line', pending.cx,pending.cy, pending.r)
end
if love.mouse.isDown('1') then
if pending.shape == 'line' then
love.graphics.setColor(pending.R, 0, 0)
love.graphics.line(pending.x1,pending.y1, love.mouse.getX(),love.mouse.getY())
elseif pending.shape == 'radius' then
love.graphics.setColor(0.75, 0.75, 0.75)
love.graphics.line(pending.x1,pending.y1, love.mouse.getX(),love.mouse.getY())
elseif pending.shape == 'arc' then
if pending.s then
current_end_angle = angle_with_hint(pending.cx,pending.cy, love.mouse.getX(),love.mouse.getY(), current_end_angle)
love.graphics.setColor(pending.R, 0, 0)
love.graphics.arc('line', 'open', pending.cx,pending.cy, pending.r, pending.s, current_end_angle, 360)
end
end
end
end
-- Summary of mouse events:
-- drawing lines: press(x1,y1) -> release(x2,y2)
-- setting radius: press -> release(radius)
-- drawing arcs: press(center), press(start_angle) -> press(end_angle)
-- (current_mode changes here from 'center' to 'arcbegin' to 'arcend')
function love.mousepressed(x, y, button)
menu_pressed(x, y, button)
if button == 1 then
if x>shapex and x<shapex+shapew and y>shapey and y<shapey+shapeh then
if current_mode == 'point' then
table.insert(shapes_committed, {shape='point', x=x, y=y, R=current_red})
elseif current_mode == 'line' then
pending = {shape = 'line', x1=x,y1=y, R=current_red}
elseif current_mode == 'radius' then
pending = {shape = 'radius', x1=x,y1=y}
elseif current_mode == 'center' then
table.insert(shapes_committed, {shape='point', x=x, y=y, R=current_red})
pending = {shape='arc', cx=x,cy=y, r=current_radius, R=current_red}
current_mode = 'arcbegin'
elseif current_mode == 'arcbegin' then
assert(pending.shape == 'arc')
pending.s = angle(pending.cx,pending.cy, x,y)
current_mode = 'arcend'
end
end
end
end
function love.mousereleased(x,y, button)
if x>shapex and x<shapex+shapew and y>shapey and y<shapey+shapeh then
if pending.shape == 'line' then
pending.x2 = x
pending.y2 = y
table.insert(shapes_committed, pending)
pending = {}
elseif current_mode == 'radius' then
-- radius widget doesn't draw anything
current_radius = math.dist(pending.x1,pending.y1, x,y)
pending = {}
current_end_angle = nil
elseif current_mode == 'arcend' then
pending.e = current_end_angle
table.insert(shapes_committed, pending)
current_mode = 'center'
pending = {}
current_end_angle = nil
end
end
end
function angle_with_hint(x1, y1, x2, y2, hint)
local result = angle(x1,y1, x2,y2)
if hint then
-- Smooth the discontinuity where angle goes from positive to negative.
-- The hint is a memory of which way we drew it last time.
while result > hint+math.pi/10 do
result = result-math.pi*2
end
while result < hint-math.pi/10 do
result = result+math.pi*2
end
end
return result
end
-- result is from -π/2 to 3π/2, approximately adding math.atan2 from Lua 5.3
-- (LÖVE is Lua 5.1)
function angle(x1,y1, x2,y2)
local result = math.atan((y2-y1)/(x2-x1))
if x2 < x1 then
result = result+math.pi
end
return result
end
function math.dist(x1,y1, x2,y2) return ((x2-x1)^2+(y2-y1)^2)^0.5 end
-- ### Menu area and widgets
-- Here we update current_mode.
events = {}
function draw_menu()
events = {}
love.graphics.setColor(0.2, 0.2, 0.2)
love.graphics.rectangle('fill', menux,menuy, menuw,menuh)
local width, height, flags = love.window.getMode()
love.graphics.setColor(1, 1, 1)
love.graphics.print(love.mouse.getX(), 5, height-status_height)
love.graphics.print("|", 35,height-status_height)
love.graphics.print(love.mouse.getY(), 45, height-status_height)
button('color', {x=25,y=25, w=50,h=50, color={current_red,0,0},
onpress1 = function() current_red = 1 - current_red end})
button('point', {x=100,y=25, w=50,h=50,
color = (current_mode == 'point' and {1,1,1} or {0.75,0.75,0.75}),
icon = function()
love.graphics.setColor(current_mode == 'point' and current_red or 0, 0, 0)
love.graphics.circle('fill', 100+50/2,25+50/2, 2)
end,
onpress1 = function() current_mode = 'point' end})
button('line', {x=175,y=25, w=50,h=50,
color = (current_mode == 'line' and {1,1,1} or {0.75,0.75,0.75}),
icon = function()
love.graphics.setColor(current_mode == 'line' and current_red or 0, 0, 0)
love.graphics.line(175+5,25, 175+50-5,25+50)
end,
onpress1 = function() current_mode = 'line' end})
button('radius', {x=250,y=25, w=50,h=50,
color = (current_mode == 'radius' and {1,1,1} or {0.75,0.75,0.75}),
icon = function()
local y = 25+50/2
love.graphics.setColor(current_mode == 'radius' and current_red or 0, 0, 0)
love.graphics.circle('fill', 250+10,y, 2)
love.graphics.line(250+10,y, 250+40,y)
love.graphics.circle('fill', 250+40,y, 2)
love.graphics.setColor(0.5, 0.5, 0.5)
love.graphics.arc('line', 'open', 250+10,y, 30, 1/2,-1/2)
end,
onpress1 = function() current_mode = 'radius' end})
local arc_pressed = one_of(current_mode, {'center', 'arcbegin', 'arcend'})
button('arc', {x=325,y=25, w=50,h=50,
color = (arc_pressed and {1,1,1} or {0.75,0.75,0.75}),
icon = function()
local y = 25+50/2
love.graphics.setColor(0.5, 0.5, 0.5)
love.graphics.line(325+10,y, 325+40,y)
love.graphics.setColor(arc_pressed and current_red or 0, 0, 0)
love.graphics.arc('line', 'open', 325+10,y, 30, 1/2,-1/2)
end,
onpress1 = function() current_mode = 'center' end})
end
-- draw button and queue up event handlers
function button(name, params)
love.graphics.setColor(params.color[1], params.color[2], params.color[3])
love.graphics.rectangle('fill', params.x,params.y, params.w,params.h)
if params.icon then params.icon() end
table.insert(events, params)
end
-- process button event handlers
function menu_pressed(x, y, button)
for _,ev in ipairs(events) do
if x>ev.x and x<ev.x+ev.w and y>ev.y and y<ev.y+ev.h then
if ev.onpress1 and button == 1 then ev.onpress1() end
end
end
end
function one_of(v, t)
for _,x in ipairs(t) do
if v == x then return true end
end
return false
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment