diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/ecs.lua | 1 | ||||
| -rw-r--r-- | src/im.lua | 454 | ||||
| -rw-r--r-- | src/init.lua | 1 | ||||
| -rw-r--r-- | src/input.lua | 20 | ||||
| -rw-r--r-- | src/objs/player.lua | 44 | ||||
| -rw-r--r-- | src/utils.lua | 12 |
6 files changed, 513 insertions, 19 deletions
diff --git a/src/ecs.lua b/src/ecs.lua index 24603c0..373d338 100644 --- a/src/ecs.lua +++ b/src/ecs.lua @@ -20,6 +20,7 @@ function new_scene() -- Events on_draw = new_event(), on_update = new_event(), + on_ui = new_event(), comp_removeq = {}, comp_entq = {}, 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 diff --git a/src/init.lua b/src/init.lua index 37010ef..5b707a9 100644 --- a/src/init.lua +++ b/src/init.lua @@ -5,6 +5,7 @@ require "src.input" require "src.textures" require "src.phys" require "src.sprite" +require "src.im" SCR_WIDTH = 320 SCR_HEIGHT = 180 diff --git a/src/input.lua b/src/input.lua index 8029be2..c5c9012 100644 --- a/src/input.lua +++ b/src/input.lua @@ -55,8 +55,26 @@ function love.keypressed(key) keyEvents[key] = true end -function love.mousepressed(x, y, btn) +function love.mousepressed(_, _, btn) mouseEvents[btn] = true + local sx, sy = get_mouse_pos() + im.mousepressed(sx, sy, btn) +end + +function love.mousereleased(_, _, btn) + local sx, sy = get_mouse_pos() + im.mousereleased(sx, sy, btn) +end + +function love.mousemoved(_, _, dx, dy) + local sx, sy = get_mouse_pos() + local scrw, scrh = love.graphics.getDimensions() + local rdx, rdy = dx / scrw * SCR_WIDTH, dy / scrh * SCR_HEIGHT + im.mousemoved(sx, sy, rdx, rdy) +end + +function love.wheelmoved(...) + im.wheelmoved(...) end function get_mouse_pos() diff --git a/src/objs/player.lua b/src/objs/player.lua index bc71ea7..0903372 100644 --- a/src/objs/player.lua +++ b/src/objs/player.lua @@ -14,6 +14,8 @@ function body_sys(ent, dt) ent.y = ent.box.y end +local tile = 1 + function player_movement_sys(player, dt) local inpx, inpy = input_direction("Left", "Right", "Up", "Down") inpx, inpy = normalize(inpx, inpy) @@ -21,24 +23,40 @@ function player_movement_sys(player, dt) player.vy = dlerp(player.vy, inpy * PLAYER_SPEED, 25 * dt) -- Testicle stuff, remove when testising is no longer needed - if is_input_pressed("Right_Click") then - local scn = get_current_scene() - assert(scn, "no scene set.") + if not im.has_focus() then + if is_input_pressed("Right_Click") then + local scn = get_current_scene() + assert(scn, "no scene set.") - local mx, my = get_mouse_pos() - local tx, ty = to_tile_coords(mx, my) - set_tile(scn.tilemap, tx, ty, 1) - end - if is_input_pressed("Left_Click") then - local scn = get_current_scene() - assert(scn, "no scene set.") + local mx, my = get_mouse_pos() + local tx, ty = to_tile_coords(mx, my) + set_tile(scn.tilemap, tx, ty, tile) + end + if is_input_pressed("Left_Click") then + local scn = get_current_scene() + assert(scn, "no scene set.") - local mx, my = get_mouse_pos() - local tx, ty = to_tile_coords(mx, my) - remove_tile(scn.tilemap, tx, ty) + local mx, my = get_mouse_pos() + local tx, ty = to_tile_coords(mx, my) + remove_tile(scn.tilemap, tx, ty) + end end end +function player_ui_sys(_) + im.begin_window("Room Editor", 5, 5, 50, 50, {}) + im.layout({0.5, 0.75, 1}) + im.text("Tile: " .. tostring(tile)) + if im.button(" - ") then + tile = math.max(tile - 1, 0) + end + if im.button("+ ") then + tile = tile + 1 + end + im.layout() + im.end_window() +end + function new_player(x, y) local ent = new_entity() add_comp(ent, "Body", x, y, 16, 16, { diff --git a/src/utils.lua b/src/utils.lua index 29c3024..33ca7e8 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -18,6 +18,12 @@ function min(a, b) return b end +function pop(tbl) + local last = tbl[#tbl] + tbl[#tbl] = nil + return last +end + function normalize(x, y) if x == 0 and y == 0 then return 0, 0 @@ -38,11 +44,7 @@ function dist(x1, y1, x2, y2) return (dx*dx + dy*dy)^0.5 end -mathx = {} - -mathx.tau = math.pi * 2 - -function mathx.sign(x) +function sign(x) if x == 0 then return 0 end |
