Last active
April 24, 2024 05:05
-
-
Save akkartik/20a0c7e7589c6179666fc78bc04b28b4 to your computer and use it in GitHub Desktop.
Little UI for compass-and-straightedge constructions using https://love2d.org
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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