diff options
| author | iamcheeseman <[email protected]> | 2026-03-12 14:50:17 -0400 |
|---|---|---|
| committer | iamcheeseman <[email protected]> | 2026-03-12 14:50:17 -0400 |
| commit | 4828c80a92a40067d8f58922c38c4dbeeafc8403 (patch) | |
| tree | b531e615fb767a2aa7212e5b8e8cd3ebea441e5c /src/im.lua | |
| parent | 8b9f468ffaa169788d1488d2640d2e0e49bb50f3 (diff) | |
debug gooey :)
Diffstat (limited to 'src/im.lua')
| -rw-r--r-- | src/im.lua | 454 |
1 files changed, 454 insertions, 0 deletions
diff --git a/src/im.lua b/src/im.lua new file mode 100644 index 0000000..28c7064 --- /dev/null +++ b/src/im.lua @@ -0,0 +1,454 @@ +im = { + cols = { + title_bar = {0.15, 0.15, 0.15}, + border = {0.1, 0.1, 0.1}, + bg = {0.2, 0.2, 0.2}, + hover = {0.3, 0.3, 0.3}, + active = {0.15, 0.15, 0.15}, + title_text = {0.5, 0.5, 0.5}, + text = {0.8, 0.8, 0.8}, + }, + padding = 1, + scroll_speed = 5, + slider_handle_width = 5, + resize_handle_size = 5, +} + +--- if true, then the focused window is already decided +local focus_finalized = false +local next_focused --- next focused window +local focused_win +local wins = {} + +local current_win + +local mouse_down = false +local mouse_just_up = false + +local mx, my = 0, 0 +local mdx, mdy = 0, 0 +local scroll = 0 + +local layout +local layout_i = 1 + +local max_h = 0 +local next_x = 0 +local next_y = 0 +local next_w = 0 + +local function rect_contains(x, y, w, h, px, py) + return + px > x and + py > y and + px < x + w and + py < y + h +end + +local function draw_text(text, font, r, g, b, x, y) + lg.setColor(r, g, b) + lg.setFont(font) + lg.print(text, x, y) +end + +local function draw_rect(mode, x, y, w, h, r, g, b) + lg.setColor(r, g, b) + lg.rectangle(mode, x, y, w, h) +end + +local function draw_img(img, x, y, quad) + lg.setColor(1, 1, 1) + if not quad then + lg.draw(img, x, y) + else + lg.draw(img, quad, x, y) + end +end + +local function draw_stencil(x, y, w, h) + lg.stencil(function() + lg.rectangle("fill", x, y, w, h) + end, "replace", 1) +end + +local function text_cmd(text, x, y, r, g, b) + table.insert(current_win.cmds, { + fn = draw_text, + args = {text, lg.getFont(), r, g, b, x, y}, + }) +end + +local function rect_cmd(x, y, w, h, r, g, b) + table.insert(current_win.cmds, { + fn = draw_rect, + args = {"fill", x, y, w, h, r, g, b}, + }) +end + +local function img_cmd(img, x, y, quad) + table.insert(current_win.cmds, { + fn = draw_img, + args = {img, x, y, quad} + }) +end + +local function stencil_cmd(x, y, w, h) + table.insert(current_win.cmds, { + fn = draw_stencil, + args = {x, y, w, h}, + }) +end + +local function control_stencil(x, y, w, h) + local win = current_win + local tlx, tly = x, y + local brx, bry = x + w, y + h + + tlx = clamp(tlx, win.cx, win.cx + win.cw) + tly = clamp(tly, win.cy, win.cy + win.ch) + + brx = clamp(brx, win.cx, win.cx + win.cw) + bry = clamp(bry, win.cy, win.cy + win.ch) + + return tlx, tly, math.max(brx - tlx, 0), math.max(bry - tly, 0) +end + +local function reset_stencil() + stencil_cmd(current_win.cx, current_win.cy, current_win.cw, current_win.ch) +end + +local function shrink(x, y, w, h, by) + x = x + by + y = y + by + w = w - by * 2 + h = h - by * 2 + return x, y, w, h +end + +function im.layout(new_layout, start) + assert(current_win, "cannot set layout outside of a window") + + new_layout = new_layout or {1} + + local proc = {} + local last = start or 0 + + local win_x = current_win.x + im.padding + local win_w = current_win.w - im.padding * 2 + + for i, v in ipairs(new_layout) do + assert(v >= last, "layout must be sorted low to high") + local w = win_w * (v - last) - im.padding * 2 + proc[i] = {x=win_x + win_w * last + im.padding, w=w} + last = v + end + + layout = proc + layout_i = 1 + im.next_position(0) +end + +function im.next_position(h) + h = h or 0 + + local x, y, w = next_x, next_y, next_w + + max_h = math.max(max_h, h) + + if layout_i > #layout then -- no more columns + next_y = next_y + max_h + im.padding + current_win.content_max = math.max( + current_win.content_max, + next_y + current_win.scroll - current_win.cy) + layout_i = 1 + max_h = 0 + end + next_x = layout[layout_i].x + next_w = layout[layout_i].w + layout_i = layout_i + 1 + + return x, y, w +end + +function im.image(img, quad) + assert(current_win, "a window must be active") + local w, h = img:getDimensions() + if quad then + _, _, w, h = quad:getViewport() + end + local x, y, max_w = im.next_position(h) + + w = math.min(w, max_w) + + stencil_cmd(control_stencil(x, y, w, h)) + img_cmd(img, round(x), round(y), quad) + reset_stencil() +end + +function im.separator() + assert(current_win, "a window must be active") + local x, y, w = im.next_position(1) + rect_cmd(x, y, w, 1, unpack(im.cols.border)) +end + +function im.text(text, r, g, b) + assert(current_win, "a window must be active") + r = r or im.cols.text[1] + g = g or im.cols.text[2] + b = b or im.cols.text[3] + + local font = lg.getFont() + local h = font:getHeight() + local x, y, w = im.next_position(h) + stencil_cmd(control_stencil(x, y, w, h)) + text_cmd(text, x, y, r, g, b) + reset_stencil() +end + +function im.button(text, r, g, b) + assert(current_win, "a window must be active") + r = r or im.cols.text[1] + g = g or im.cols.text[2] + b = b or im.cols.text[3] + + local font = lg.getFont() + local text_w, text_h = font:getWidth(text), font:getHeight() + im.padding + local w, h = text_w * 1.1, text_h * 1.1 + local x, y = im.next_position(h) + + stencil_cmd(control_stencil(x, y, w, h)) + + rect_cmd(x, y, w, h, unpack(im.cols.border)) + + x, y, w, h = shrink(x, y, w, h, 1) + + local col = im.cols.bg + local pressed = false + + if current_win == focused_win and rect_contains(x, y, w, h, mx, my) then + col = im.cols.hover + if mouse_just_up then + col = im.cols.active + pressed = true + end + end + + rect_cmd(x, y, w, h, unpack(col)) + + local text_x, text_y = x + w / 2 - text_w / 2, y + h / 2 - text_h / 2 + text_cmd(text, text_x, text_y, r, g, b) + + reset_stencil() + + local ret = pressed + return ret +end + +function im.slider(val, min, max, step, h) + assert(current_win, "a window must be active") + assert(max > min, "min must be lesser than max") + step = step or 0.1 + h = h or lg.getFont():getHeight() + local p = (val - min) / (max - min) + + local x, y, w = im.next_position(h) + + rect_cmd(x, y, w, h, unpack(im.cols.border)) + x, y, w, h = shrink(x, y, w, h, 1) + rect_cmd(x, y, w, h, unpack(im.cols.bg)) + + local max_w = w - im.slider_handle_width + local handle = max_w * p + + local col = im.cols.bg + if rect_contains(x, y, w, h, mx - mdx, my - mdy) and + current_win == focused_win then + col = im.cols.hover + if mouse_down then + col = im.cols.active + handle = clamp(mx - x, 0, max_w) + end + end + + rect_cmd(x, y, handle, h, unpack(im.cols.hover)) + + x = x + handle + w = im.slider_handle_width + y = y + h = h + rect_cmd(x, y, w, h, unpack(im.cols.border)) + x, y, w, h = shrink(x, y, w, h, 1) + rect_cmd(x, y, w, h, unpack(col)) + + p = handle / max_w + return clamp(p * (max - min) + min, min, max) +end + +function im.begin_window(title, x, y, w, h, opts) + assert(not current_win, "window is already active") + opts = opts or {} + + local win = wins[title] + if not win then + win = { + win = true, + x = x, + y = y, + w = w, + h = h, + opts = opts, + content_max = 0, + scroll = 0, + open = true, + } + wins[title] = win + table.insert(wins, win) + win.idx = #wins + end + + win.cmds = {} + + if rect_contains(win.x, win.y, win.w, win.h, mx, my) then + if (not next_focused or next_focused.idx < win.idx) and + not focus_finalized then + next_focused = win + end + + if mouse_down and focused_win == win then + local last = pop(wins) + wins[win.idx] = last + last.idx = win.idx + table.insert(wins, win) + win.idx = #wins + end + end + + if not win.open then + return false + end + + current_win = win + + local font = lg.getFont() + local title_x, title_y = win.x + 1, win.y + 1 + local title_w, title_h = win.w - 2, font:getHeight() + im.padding + if focused_win == win and + rect_contains(title_x, title_y, title_w, title_h, mx - mdx, my - mdy) and + mouse_down then + next_focused = current_win + focus_finalized = true + win.x = round(win.x + mdx) + win.y = round(win.y + mdy) + title_x, title_y = win.x + 1, win.y + 1 + end + + stencil_cmd(0, 0, viewport:getDimensions()) + + rect_cmd(win.x, win.y, win.w, win.h, unpack(im.cols.border)) + + stencil_cmd(win.x, win.y, win.w, win.h) + + rect_cmd(title_x, title_y, title_w, title_h, unpack(im.cols.title_bar)) + text_cmd(title, title_x + im.padding, title_y, unpack(im.cols.title_text)) + + local cx, cy = win.x + 1, win.y + title_h + local cw, ch = win.w - 2, win.h - title_h - 1 + win.cx, win.cy, win.cw, win.ch = cx, cy, cw, ch + rect_cmd(cx, cy, cw, ch, unpack(im.cols.bg)) + + -- resizing + if focused_win == win then + local o = im.resize_handle_size / 2 + local hs = im.resize_handle_size + if rect_contains(cx + cw - o, cy + ch - o, hs, hs, mx - mdx, my - mdy) or + rect_contains(cx, cy + ch - o, cw, hs, mx - mdx, my - mdy) or + rect_contains(cx + cw - o, cy, hs, ch, mx - mdx, my - mdy) then + if mouse_down then + next_focused = current_win + focus_finalized = true + + win.w = win.w + mdx + win.h = win.h + mdy + end + rect_cmd(cx + cw, cy, 1, ch + 1, unpack(im.cols.hover)) + rect_cmd(cx, cy + ch, cw, 1, unpack(im.cols.hover)) + end + end + + win.w = math.max(win.w, 30) + win.h = math.max(win.h, title_h + 30) + + stencil_cmd(cx, cy, cw, ch) + + if rect_contains(cx, cy, cw, ch, mx, my) then + if win.content_max > win.ch then + win.scroll = clamp(win.scroll - scroll, 0, win.content_max - win.ch) + else + win.scroll = 0 + end + win.content_max = 0 + end + + -- reset layout + im.layout() + next_x, next_y = cx + im.padding, cy - win.scroll + + return true +end + +function im.end_window() + assert(current_win, "a window must be active to end one") + reset_stencil() + current_win = nil +end + +function im.begin_step() +end + +function im.end_step() + mdx, mdy = 0, 0 + mouse_just_up = false + scroll = 0 + focused_win = next_focused + next_focused = nil + focus_finalized = false +end + +function im.draw() + lg.setStencilTest("greater", 0) + for _, win in ipairs(wins) do + for _, cmd in ipairs(win.cmds) do + cmd.fn(unpack(cmd.args)) + end + end + lg.setStencilTest() +end + +function im.has_focus() + -- double not to ensure boolean :) + return not not focused_win +end + +function im.mousereleased(_, _, btn) + if btn == 1 then + mouse_down = false + mouse_just_up = true + end +end + +function im.mousepressed(_, _, btn) + if btn == 1 then + mouse_down = true + end +end + +function im.mousemoved(x, y, dx, dy) + mx, my = x, y + -- accumlate for processing in step + mdx = mdx + dx + mdy = mdy + dy +end + +function im.wheelmoved(_, y) + scroll = scroll + y * im.scroll_speed +end |
