From ace0b192b4c71442eb1c435b40d34b0eb2820c86 Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Tue, 7 Apr 2020 16:18:15 -0500 Subject: [PATCH] Refactored ui entirely --- app.lua | 532 +++++++++++++++++++++++------------- main.lua | 40 --- src/old-ui/components.lua | 99 ------- src/old-ui/regions.lua | 287 ------------------- src/old-ui/ui.lua | 114 -------- src/ui.lua | 159 ++++++++++- src/{old-ui => ui}/text.lua | 0 src/utils.lua | 9 +- src/wasm/text.lua | 253 ++++++++--------- 9 files changed, 633 insertions(+), 860 deletions(-) delete mode 100644 src/old-ui/components.lua delete mode 100644 src/old-ui/regions.lua delete mode 100644 src/old-ui/ui.lua rename src/{old-ui => ui}/text.lua (100%) diff --git a/app.lua b/app.lua index 494be00..b45a755 100644 --- a/app.lua +++ b/app.lua @@ -4,245 +4,385 @@ import { wasm_decompile = "src.wasm.decompile:"; wasm_analyze = "src.wasm.analyze:"; + wasm_text = "src.wasm.text"; + + ui = "src.ui:"; + ui_text = "src.ui.text:"; COLORS = "conf:COLOR_SCHEME"; } -class "FunctionBlock" [DraggableRect] { - init = function(self, func, x, y) - DraggableRect.init(self, x, y, 400, 400) +-- A node reacts to no events, just helps with the rendering / event processing ordering +ui.register_component "node" {} - self.func = func - funcBlocks[func.funcidx] = self +ui.register_component "root" { + resize = function(self, w, h) + self.rect.w = w + self.rect.h = h + end; +} - WasmCodeGenerator:setup(func, mod, COLORS) - self.header_text = WasmCodeGenerator:build_header() +ui.register_component "rect" { + mousepressed_trans = function(self, button, x, y) + return button, x - self.rect.x, y - self.rect.y + end; - if func.body then - self.body_text = WasmCodeGenerator:build_body() - else - self.body_text = { - { text = "imported function", color = COLORS.code } - } - end + mousereleased_trans = function(self, button, x, y) + return button, x - self.rect.x, y - self.rect.y + end; - self.scroll = 0 - self.body_visible = true - self.old_height = 400 + mousemoved_trans = function(self, x, y, dx, dy) + return x - self.rect.x, y - self.rect.y, dx, dy + end; - self.canvas = love.graphics.newCanvas(800, 800) - self.redraw = true + mousepressed = function(self, button, x, y) + if self.rect:contains_trans(x, y) then + ui.focus(self) + return true + end end; - toggle_body_visible = function(self) - self.body_visible = not self.body_visible - self.rect.h = self.body_visible and self.old_height or 22 - self.redraw = true + predraw = function(self) + love.graphics.push() + love.graphics.translate(self.rect.x, self.rect.y) + love.graphics.setColor(self.color) + love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h) end; - onhover = function(self, x, y) - self.redraw = not self.selected or self.redraw - self.selected = true + postdraw = function(self) + love.graphics.pop() end; +} + +local drag_rect = { extends = "rect" } +function drag_rect.init(self) self.mouse_down = false end + +function drag_rect.mousepressed(self, button, x, y) + if not self.rect:contains_trans(x, y) then return false end + + ui.focus(self) + if button == 1 then self.mouse_down = true end + return true +end; + +function drag_rect.mousereleased(self, button, x, y) + if button == 1 then self.mouse_down = false end +end + +function drag_rect.unfocus(self) + self.mouse_down = false +end + +function drag_rect.mousemoved(self, x, y, dx, dy) + if ui.focused == self and self.mouse_down then + self.rect.x = self.rect.x + dx + self.rect.y = self.rect.y + dy + end +end + + +local function_block = { extends = "drag_rect" } +function function_block.init(self, wasm_function, wasm_mod) + self.func = wasm_function + self.mod = wasm_mod + + local header_text, body_text = wasm_text.generate_text_format(self.func, self.mod, COLORS) + self.header_text = header_text + self.body_text = body_text + + self.scroll = 0 + self.body_visible = true + self.old_height = 400 + self.resize_down = false + + self.canvas = love.graphics.newCanvas(800, 800) + self.redraw = true +end - onunhover = function(self) - self.selected = false +function function_block.toggle_body_visible(self) + self.body_visible = not self.body_visible + self.rect.h = self.body_visible and self.old_height or 22 + self.redraw = true +end + +function function_block.mousepressed(self, button, x, y) + local result = false + if drag_rect.mousepressed(self, button, x, y) then self.redraw = true - end; + result = true + end + if self.rect:contains_trans(x, y) and button == 2 then + self.resize_down = true + result = true + end + + return result +end - onclick = function(self, button, x, y, ox, oy) - DraggableRect.onclick(self, button, x, y, ox, oy) - - if button == 2 then - local cmr - cmr = ContextMenu(self.ui, ox, oy, { - { text = "Toggle open", action = function() - cmr.should_delete = true - self:toggle_body_visible() - end - }; - { text = "Resize", action = function() cmr.should_delete = true end }; - { text = "Hide", action = function() end; }; - }) - self.ui:add_region(cmr) +function function_block.mousereleased(self, button, x, y) + drag_rect.mousereleased(self, button, x, y) + if button == 2 then self.resize_down = false end +end + +function function_block.focus(self) + self.layer = 0 +end + +function function_block.unfocus(self) + self.resize_down = false + self.redraw = true + self.layer = self.layer + 1 +end + +function function_block.mousemoved(self, x, y, dx, dy) + drag_rect.mousemoved(self, x, y, dx, dy) + + if self.resize_down then + self.rect.w = math.min(800, math.max(100, x)) + if self.body_visible then + self.rect.h = math.min(800, math.max(22, y)) + self.old_height = self.rect.h end self.redraw = true - end; + end +end + +function function_block.wheelmoved(self, dx, dy) + if ui.focused ~= self then return false end + + self.scroll = self.scroll - dy * 10 + if self.scroll < 0 then self.scroll = 0 end + self.redraw = true + + return true +end + +function function_block.update(self, dt) + if ui.focused ~= self then return end - onwheel = function(self, _, dy) - self.scroll = self.scroll - dy * 10; - if self.scroll < 0 then self.scroll = 0 end + if love.keyboard.isDown "down" then + self.scroll = self.scroll + 300 * dt self.redraw = true - end; + end + if love.keyboard.isDown "up" then + self.scroll = self.scroll - 300 * dt + self.redraw = true + end +end - ondrag = function(self, button, x, y, dx, dy) - DraggableRect.ondrag(self, button, x, y, dx, dy) +function function_block.predraw(self) + if self.redraw then + love.graphics.setCanvas(self.canvas) + local oldscissor = { love.graphics.getScissor() } + love.graphics.push() + love.graphics.origin() + love.graphics.setScissor(0, 0, self.canvas:getWidth(), self.canvas:getHeight()) + love.graphics.setBackgroundColor(0, 0, 0, 0) + love.graphics.clear() + love.graphics.setScissor(0, 0, self.rect.w, self.rect.h) - if y >= 24 and button == 3 then - self.rect.w = math.min(800, math.max(100, self.rect.w + dx)) - if self.body_visible then - self.rect.h = math.min(800, math.max(22, self.rect.h + dy)) - self.old_height = self.rect.h - end - self.redraw = true - end - end; + love.graphics.setColor(0, 0, 0) + love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h) + love.graphics.setColor(ui.focused == self and COLORS.background or COLORS.dark_background) + love.graphics.rectangle("fill", 2, 2, self.rect.w - 4, self.rect.h - 4) + love.graphics.setColor(COLORS.dark_background) + love.graphics.rectangle("fill", 2, 2, self.rect.w - 4, 18) - draw = function(self) - if self.redraw then - love.graphics.setCanvas(self.canvas) - local oldscissor = { love.graphics.getScissor() } - love.graphics.push() - love.graphics.origin() - love.graphics.setScissor(0, 0, self.canvas:getWidth(), self.canvas:getHeight()) - love.graphics.setBackgroundColor(0, 0, 0, 0) - love.graphics.clear() - love.graphics.setScissor(0, 0, self.rect.w, self.rect.h) - - love.graphics.setColor(0, 0, 0) - love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h) - love.graphics.setColor(self.selected and COLORS.background or COLORS.dark_background) - love.graphics.rectangle("fill", 2, 2, self.rect.w - 4, self.rect.h - 4) - love.graphics.setColor(COLORS.dark_background) - love.graphics.rectangle("fill", 2, 2, self.rect.w - 4, 18) - - local x, y = Text.render_text(2, 2, self.header_text) - - if self.body_visible and self.body_text then - love.graphics.intersectScissor(2, y - 1, self.rect.w - 4, self.rect.h - 22) - Text.render_text(2, y + 4 - self.scroll, self.body_text) - end - - love.graphics.pop() - love.graphics.setScissor(unpack(oldscissor)) - love.graphics.setCanvas() - self.redraw = false - end + local x, y = ui_text.render_text(2, 2, self.header_text) - love.graphics.setColor(1, 1, 1) - love.graphics.draw(self.canvas, self.rect.x, self.rect.y) - - if self.selected then - if self.func.callees then - love.graphics.setColor(COLORS.primary_light) - for _, callee in ipairs(self.func.callees) do - local sx, sy = self.rect:center() - local tx, ty = funcBlocks[callee].rect:center() - draw_arrow(sx, sy, tx, ty) - end - end - if self.func.callers then - love.graphics.setColor(COLORS.secondary) - for _, caller in ipairs(self.func.callers) do - local sx, sy = self.rect:center() - local tx, ty = funcBlocks[caller].rect:center() - draw_arrow(tx, ty, sx, sy) - end - end + if self.body_visible and self.body_text then + love.graphics.intersectScissor(2, y - 4, self.rect.w - 4, self.rect.h - 22) + ui_text.render_text(2, y - self.scroll, self.body_text) end - end; -} -class "ContextMenu" [Region] { - init = function(self, ui, cx, cy, options) - Region.init(self, ui) - - self.rect.w = 200 - self.rect.h = 25 * #options - self.rect.x = cx - self.rect.w / 2 - self.rect.y = cy - self.rect.h / 2 - - self.background = COLORS.secondary_dark - - for i, opt in ipairs(options) do - self:add_object(with(UiButton()) { - text = opt.text; - background = COLORS.secondary_dark; - hover_background = COLORS.secondary; - click_background = COLORS.secondary_light; - foreground = COLORS.secondary_text; - rect = Rectangle(0, (i - 1) * 25, 200, 25); - click = opt.action - }) - end + love.graphics.pop() + love.graphics.setScissor(unpack(oldscissor)) + love.graphics.setCanvas() + self.redraw = false + end - self.layer = 100 - end; + love.graphics.setColor(1, 1, 1) + love.graphics.draw(self.canvas, self.rect.x, self.rect.y) +end - onmouseleave = function(self) - self.should_delete = true - end; -} +function function_block.postdraw(self) end -function make_top_bar(app) - local top_bar = Region() - with(top_bar) { - -- Maybe don't hard code this? - rect = Rectangle(0, 0, 1200, 50); +local scrolling = {} +function scrolling.init(self) + self.offset = { x = 0; y = 0 } + self.line_color = { 0, 0, 0 } + self.zoom = 1 + self.mouse_down = false +end - background_color = COLORS.dark_background; - layer = 10; +function scrolling.mouse_trans(self, x, y) + return + ((x - self.rect.x) - self.rect.w / 2) / self.zoom + self.rect.w / 2 - self.offset.x, + ((y - self.rect.y) - self.rect.h / 2) / self.zoom + self.rect.h / 2 - self.offset.y +end - onresize = function(region, new_width, _) - -- Fill the width of the screen - region.rect.w = new_width - end - } +function scrolling.mousepressed_trans(self, button, x, y) + local mx, my = scrolling.mouse_trans(self, x, y) + return button, mx, my +end + +function scrolling.mousereleased_trans(self, button, x, y) + local mx, my = scrolling.mouse_trans(self, x, y) + return button, mx, my +end - local test = with(Button()) { - text = "Test Button"; - background = COLORS.primary_dark; - hovered_background = COLORS.primary; - click_background = COLORS.primary_light; - foreground = COLORS.primary_text; +function scrolling.mousemoved_trans(self, x, y, dx, dy) + local mx, my = scrolling.mouse_trans(self, x, y) + return mx, my, dx / self.zoom, dy / self.zoom +end - rect = Rectangle(10, 10, 80, 30); - } +function scrolling.mousepressed(self, button, x, y) + ui.focus(self) + self.mouse_down = true + return true +end + +function scrolling.mousereleased(self, button, x, y) + self.mouse_down = false +end - top_bar:add_component(test) - return top_bar +function scrolling.unfocus(self) + self.mouse_down = false end +function scrolling.mousemoved(self, x, y, dx, dy) + if self.mouse_down then + self.offset.x = self.offset.x + dx + self.offset.y = self.offset.y + dy + end + + return true +end + +function scrolling.change_zoom(self, multiplier) + self.zoom = self.zoom * multiplier + if self.zoom <= 0.2 then self.zoom = 0.2 end +end + +function scrolling.wheelmoved(self, dx, dy) + if ui.focused == self then + scrolling.change_zoom(self, dy > 0 and 1.05 or (1 / 1.05)) + end +end + +function scrolling.resize(self, w, h) + self.rect.w = w + self.rect.h = h - self.rect.y +end + +function scrolling.update(self, dt) + if ui.focused ~= self then return end + + local speed = 600 / self.zoom + if love.keyboard.isDown "left" then + self.offset.x = self.offset.x + speed * dt + end + if love.keyboard.isDown "right" then + self.offset.x = self.offset.x - speed * dt + end + if love.keyboard.isDown "up" then + self.offset.y = self.offset.y + speed * dt + end + if love.keyboard.isDown "down" then + self.offset.y = self.offset.y - speed * dt + end + + if love.keyboard.isDown "q" then + scrolling.change_zoom(self, 1 + 0.5 * dt) + end + if love.keyboard.isDown "a" then + scrolling.change_zoom(self, 1 - 0.5 * dt) + end +end + +function scrolling.predraw(self) + love.graphics.push() + love.graphics.translate(self.rect.x, self.rect.y) + love.graphics.setScissor(self.rect.x, self.rect.y, self.rect.w, self.rect.h) + + if self.background_color ~= nil then + love.graphics.setColor(self.background_color) + love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h) + end + love.graphics.translate(self.rect.w / 2, self.rect.h / 2) + love.graphics.scale(self.zoom, self.zoom) + love.graphics.translate(-self.rect.w / 2, -self.rect.h / 2) + + local xoff = self.offset.x + local yoff = self.offset.y + local spacing = 64 + + while xoff >= spacing do xoff = xoff - spacing end + while yoff >= spacing do yoff = yoff - spacing end + while xoff <= -spacing do xoff = xoff + spacing end + while yoff <= -spacing do yoff = yoff + spacing end + + love.graphics.setColor(self.line_color) + for x = -60, 100 do + love.graphics.line(x * spacing + xoff, -self.rect.h * 2, x * spacing + xoff, self.rect.h * 4) + end + for y = -60, 100 do + love.graphics.line(-self.rect.w * 2, y * spacing + yoff, self.rect.w * 4, y * spacing + yoff) + end + + love.graphics.translate(self.offset.x, self.offset.y) +end + +function scrolling.postdraw(self) + love.graphics.pop() + love.graphics.setScissor() +end + +ui.register_component "drag_rect" (drag_rect) +ui.register_component "function_block" (function_block) +ui.register_component "scrolling" (scrolling) + -- Responsible for containing and managing all -- global-ish objects for the application class "Application" { init = function(self) - self.ui = Ui() - self.wasm = nil - end; + ui_text.register_font("fonts/FiraCode-Regular", 16) + ui_text.set_font("fonts/FiraCode-Regular", 16) - create = function(self) - self.scrolling_region = ScrollingRegion(self.ui) - with(self.scrolling_region) { + self.ui = with(ui.make_element "root") { + rect = Rectangle(0, 0, 0, 0); + } + + -- ui.insert_child(self.ui, with(ui.make_element "rect") { + -- rect = Rectangle(0, 0, 1200, 50); + -- color = COLORS.dark_background; + -- }) + + self.scroller = with(ui.make_element "scrolling") { rect = Rectangle(0, 0, 1200, 900); background_color = COLORS.background; - line_color = COLORS.dark_background; - layer = 0; - - onresize = function(region, new_width, new_height) - region.rect.w = new_width - region.rect.h = new_height - end; + line_color = COLORS.primary_dark; + layer = 10; } - self.ui:add_region(self.scrolling_region) - self.ui:add_region(make_top_bar(self)) + ui.insert_child(self.ui, self.scroller) + + self.wasm = nil end; bootstrap = function(self) - local ui = self.ui - -- Sets all Love callbacks function love.filedropped(file) self:open_file(file:getFilename()) end - function love.mousepressed(x, y, button) ui:onmousedown(button, x, y) end - function love.mousereleased(x, y, button) ui:onmouseup(button, x, y) end - function love.mousemoved(x, y, dx, dy) ui:onmousemove(x, y, dx, dy) end - function love.wheelmoved(dx, dy) ui:onmousewheel(dx, dy) end - function love.keypressed(key) ui:onkeydown(key) end - function love.keyreleased(key) ui:onkeyup(key) end - function love.resize(w, h) ui:onresize(w, h) end function love.update(dt) self:update(dt) end function love.draw() self:draw() end + + function love.mousepressed(x, y, button) ui.mousepressed(self.ui, button, x, y) end + function love.mousereleased(x, y, button) ui.mousereleased(self.ui, button, x, y) end + function love.mousemoved(x, y, dx, dy) ui.mousemoved(self.ui, x, y, dx, dy) end + function love.wheelmoved(dx, dy) ui.wheelmoved(self.ui, dx, dy) end + function love.keypressed(key) ui.keypressed(self.ui, key) end + function love.keyreleased(key) ui.keyreleased(self.ui, key) end + function love.resize(w, h) ui.resize(self.ui, w, h) end end; open_file = function(self, path) @@ -250,7 +390,21 @@ class "Application" { wasm_mod = wasm_analyze(wasm_mod) self.wasm = wasm_mod - -- TODO DO SOMETHING HERE + -- Delete all old children + for _, chld in ipairs(self.scroller.children) do + chld.remove = true + end + ui.fire_propagating_event(self.scroller, "dummy") + + local i = 0 + for _, func in ipairs(wasm_mod.funcs) do + ui.insert_child(self.scroller, with(ui.make_element("function_block", func, wasm_mod)) { + rect = Rectangle(i * 400, 0, 400, 400); + layer = i + 1 + }) + + i = i + 1 + end end; update = function(self, dt) @@ -258,14 +412,14 @@ class "Application" { love.event.quit() end - self.ui:update(dt) + ui.update(self.ui, dt) end; draw = function(self) love.graphics.setBackgroundColor(0, 0, 0) love.graphics.clear() - self.ui:draw() + ui.draw(self.ui) love.graphics.setScissor() end; diff --git a/main.lua b/main.lua index 390bd39..7215510 100644 --- a/main.lua +++ b/main.lua @@ -6,49 +6,9 @@ import { } function love.load() - app:create() app:bootstrap() if arg[2] then app:open_file(arg[2]) end - - -- ui = Ui() - - -- mod = decompile(arg[2]) - -- analyze(mod) - - -- ui_region = ScrollingUiRegion(ui) - -- with(ui_region) { - -- rect = Rectangle(0, 0, 1200, 900); - -- background_color = COLORS.background; - -- line_color = COLORS.dark_background; - -- layer = 0; - - -- onresize = function(region, new_width, new_height) - -- region.rect.w = new_width - -- region.rect.h = new_height - -- end; - -- } - - -- local x = 0 - -- local y = 0 - -- for _, func in pairs(mod.funcs) do - -- local block = FunctionBlock(func, math.random() * 20000, math.random() * 20000) - -- block.order = #mod.funcs * 20 + 1 - y - -- ui_region:add_object(block) - -- y = y + 20 - -- end - - -- ui:add_region(ui_region) - - -- ui:add_region(with(UiRegion()) { - -- rect = Rectangle(0, 0, 1200, 50); - -- background_color = COLORS.dark_background; - -- layer = 10; - - -- onresize = function(region, new_width, _) - -- region.rect.w = new_width - -- end; - -- }) end diff --git a/src/old-ui/components.lua b/src/old-ui/components.lua deleted file mode 100644 index 2890572..0000000 --- a/src/old-ui/components.lua +++ /dev/null @@ -1,99 +0,0 @@ -import { - Rectangle = "src.utils:Rectangle"; - uuid = "src.utils:uuidv4"; -} - -class "Component" { - init = function(self) - self.uuid = uuid() - - self.rect = Rectangle(0, 0, 0, 0) - self.selected = false - self.order = math.random(1, 1024) - self.ui = {} - end; - - onclick = function(self, button, x, y, ox, oy) end; - ondrag = function(self, button, x, y, dx, dy) end; - onhover = function(self, x, y) end; - onunhover = function(self) end; - onwheel = function(self, dx, dy) end; - onmousedown = function(self, button, x, y, ox, oy) - self.selected = true - end; - - onselect = function(self) - self.selected = true - end; - onunselect = function(self) - self.selected = false - end; - - onkey = function(self, button) end; - - update = function(self, dt) end; - draw = function(self) end; -} - -class "Button" [Component] { - init = function(self) - Component.init(self) - - self.text = "Placeholder" - self.hovered = false - self.background = { 0, 0, 0 } - self.hover_background = { 0.2, 0.2, 0.2 } - self.click_background = { 0.4, 0.4, 0.4 } - self.foreground = { 1, 1, 1 } - end; - - onclick = function(self, button, x, y, ox, oy) - if button == 1 and self.click then - self.click(self, x, y) - end - self.selected = false - end; - - onhover = function(self, x, y) - self.hovered = true - end; - - onunhover = function(self) - self.hovered = false - end; - - draw = function(self) - if self.selected then - love.graphics.setColor(self.click_background) - elseif self.hovered then - love.graphics.setColor(self.hover_background) - else - love.graphics.setColor(self.background) - end - love.graphics.rectangle("fill", self.rect.x, self.rect.y, self.rect.w, self.rect.h) - - love.graphics.setColor(self.foreground) - love.graphics.printf(self.text, self.rect.x + 2, self.rect.y + 2, self.rect.w, "center") - end; -} - -class "DraggableRect" [Component] { - init = function(self, x, y, w, h) - Component.init(self) - - self.rect = Rectangle(x, y, w, h) - end; - - ondrag = function(self, button, x, y, dx, dy) - if button == 1 then - self.rect.x = self.rect.x + dx - self.rect.y = self.rect.y + dy - end - end; -} - -return module { - Component = Component; - Button = Button; - DraggableRect = DraggableRect; -} diff --git a/src/old-ui/regions.lua b/src/old-ui/regions.lua deleted file mode 100644 index 8e79c32..0000000 --- a/src/old-ui/regions.lua +++ /dev/null @@ -1,287 +0,0 @@ -import { - revipairs = "src.utils:revipairs"; - Rectangle = "src.utils:Rectangle"; - scissor_points = "src.utils:scissor_points"; -} - -class "Region" { - init = function(self, ui) - self.active = false - self.ui = ui - - self.selected_comp = nil - self.comps = {} - - self.rect = Rectangle(0, 0, 0, 0) - self.background_color = { 0, 0, 0 } - self.layer = 0 - - -- Internals - self.is_mouse_down = false - self.fire_onclick = false - self.should_delete = false - self.hovered_comp = nil - end; - - add_component = function(self, obj) - obj.ui = self.ui - table.insert(self.comps, obj) - end; - - -- Used for sorting in the Ui - __lt = function(self, other) - return self.layer < other.layer - end; - - _obj_rect = function(self, obj) - return obj.rect - end; - - set_selected_comp = function(self, new_selected) - if self.selected_comp ~= new_selected then - if self.selected_comp ~= nil then - self.selected_comp:onunselect() - end - - self.selected_comp = new_selected - - if self.selected_comp ~= nil then - self.selected_comp.order = 0 - - for _, obj in ipairs(self.comps) do - if obj ~= self.selected_comp then - obj.order = obj.order + 1 - end - end - - table.sort(self.comps, function(a, b) - return a.order > b.order - end) - - self.selected_comp:onselect() - end - end - end; - - onmousedown = function(self, button, x, y, ox, oy) - self.is_mouse_down = button - self.fire_onclick = true - local new_selected = nil - - for _, obj in revipairs(self.comps) do - if self:_obj_rect(obj):contains(x, y) then - new_selected = obj - break - end - end - - self:set_selected_comp(new_selected) - - if self.selected_comp then - self.selected_comp:onmousedown(button, x, y, ox, oy) - end - end; - - onmouseup = function(self, button, x, y, ox, oy) - self.is_mouse_down = false - - if self.selected_comp ~= nil and self.fire_onclick then - local obj_rect = self:_obj_rect(self.selected_comp) - self.selected_comp:onclick(button, x - obj_rect.x, y - obj_rect.y, ox, oy) - end - end; - - onmousemove = function(self, x, y, dx, dy) - local old_hovered = self.hovered_comp - - self.hovered_comp = nil - for _, obj in revipairs(self.comps) do - if self:_obj_rect(obj):contains(x, y) then - obj:onhover(x, y) - self.hovered_comp = obj - break - end - end - - if old_hovered and self.hovered_comp ~= old_hovered then - old_hovered:onunhover() - end - - self:set_selected_comp(self.hovered_comp) - - if self.is_mouse_down and self.selected_comp ~= nil then - local obj_rect = self:_obj_rect(self.selected_comp) - self.selected_comp:ondrag(self.is_mouse_down, x - obj_rect.x, y - obj_rect.y, dx, dy) - self.fire_onclick = self.fire_onclick and (math.abs(dx) + math.abs(dy)) < 2 - end - end; - - onmouseenter = function(self) - self.active = true - end; - - onmouseleave = function(self) - self.active = false; - self.is_mouse_down = false - self.fire_onclick = false - - self:set_selected_comp(nil) - end; - - onmousewheel = function(self, dx, dy) - if self.selected_comp ~= nil then - self.selected_comp:onwheel(dx, dy) - end - end; - - onresize = function(self, new_width, new_height) end; - - onkeydown = function(self, key) end; - onkeyup = function(self, key) end; - - update = function(self, dt) - for _, obj in ipairs(self.comps) do - obj:update(dt) - end - end; - - draw = function(self) - if self.background_color ~= nil then - love.graphics.setColor(self.background_color) - love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h) - end - - for _, comp in ipairs(self.comps) do - love.graphics.push() - -- love.graphics.translate(obj.rect.x, obj.rect.y) - comp:draw() - love.graphics.pop() - end - end; -} - -class "ScrollingRegion" [Region] { - init = function(self, ui) - Region.init(self, ui) - - self.offset = { x = 0; y = 0; } - self.line_color = { 0, 0, 0 } - - self.zoom = 1 - end; - - _obj_rect = function(self, obj) - return obj.rect - end; - - _transform_mouse = function(self, x, y) - return - (x - self.rect.w / 2) / self.zoom + self.rect.w / 2 - self.offset.x, - (y - self.rect.h / 2) / self.zoom + self.rect.h / 2 - self.offset.y - end; - - onmousedown = function(self, button, x, y, ox, oy) - local mx, my = self:_transform_mouse(x, y) - Region.onmousedown(self, button, mx, my, ox, oy) - end; - - onmouseup = function(self, button, x, y, ox, oy) - local mx, my = self:_transform_mouse(x, y) - Region.onmouseup(self, button, mx, my, ox, oy) - end; - - onmousemove = function(self, x, y, dx, dy) - local mx, my = self:_transform_mouse(x, y) - Region.onmousemove(self, mx, my, dx / self.zoom, dy / self.zoom) - - if self.is_mouse_down and self.selected_comp == nil then - self.offset.x = self.offset.x + dx / self.zoom - self.offset.y = self.offset.y + dy / self.zoom - end - end; - - onmousewheel = function(self, dx, dy) - Region.onmousewheel(self, dx, dy) - - if self.selected_comp == nil then - self:change_zoom(dy > 0 and 1.05 or (1 / 1.05)) - end - end; - - change_zoom = function(self, multiplier) - self.zoom = self.zoom * multiplier - -- if self.zoom >= 1 then self.zoom = 1 end - if self.zoom <= 0.2 then self.zoom = 0.2 end - end; - - update = function(self, dt) - Region.update(self, dt) - - if self.active then - local speed = 600 / self.zoom - if love.keyboard.isDown "left" then - self.offset.x = self.offset.x + speed * dt - end - if love.keyboard.isDown "right" then - self.offset.x = self.offset.x - speed * dt - end - if love.keyboard.isDown "up" then - self.offset.y = self.offset.y + speed * dt - end - if love.keyboard.isDown "down" then - self.offset.y = self.offset.y - speed * dt - end - - if love.keyboard.isDown "q" then - self:change_zoom(1 + 0.5 * dt) - end - if love.keyboard.isDown "a" then - self:change_zoom(1 - 0.5 * dt) - end - end - end; - - draw = function(self) - love.graphics.push() - - if self.background_color ~= nil then - love.graphics.setColor(self.background_color) - love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h) - end - local tmpbgcolor = self.background_color - self.background_color = nil - - love.graphics.translate(self.rect.w / 2, self.rect.h / 2) - love.graphics.scale(self.zoom, self.zoom) - love.graphics.translate(-self.rect.w / 2, -self.rect.h / 2) - - - local xoff = self.offset.x - local yoff = self.offset.y - local spacing = 64 - - while xoff >= spacing do xoff = xoff - spacing end - while yoff >= spacing do yoff = yoff - spacing end - while xoff <= -spacing do xoff = xoff + spacing end - while yoff <= -spacing do yoff = yoff + spacing end - - love.graphics.setColor(self.line_color) - for x = -60, 100 do - love.graphics.line(x * spacing + xoff, -self.rect.h * 2, x * spacing + xoff, self.rect.h * 4) - end - for y = -60, 100 do - love.graphics.line(-self.rect.w * 2, y * spacing + yoff, self.rect.w * 4, y * spacing + yoff) - end - - love.graphics.translate(self.offset.x, self.offset.y) - Region.draw(self) - love.graphics.pop() - - self.background_color = tmpbgcolor - end; -} - -return module { - Region = Region; - ScrollingRegion = ScrollingRegion; -} diff --git a/src/old-ui/ui.lua b/src/old-ui/ui.lua deleted file mode 100644 index 23f0c05..0000000 --- a/src/old-ui/ui.lua +++ /dev/null @@ -1,114 +0,0 @@ -import { - revipairs = "src.utils:revipairs"; - scissor_points = "src.utils:scissor_points"; -} - -class "Ui" { - init = function(self) - self.regions = {} - self.active_region = nil - end; - - add_region = function(self, region) - table.insert(self.regions, region) - table.sort(self.regions) - end; - - onmousedown = function(self, button, x, y) - local region = nil - for _, r in revipairs(self.regions) do - if r.rect:contains(x, y) then - region = r - break - end - end - - if region ~= nil then - region:onmousedown(button, x - region.rect.x, y - region.rect.y, x, y) - end - end; - - onmouseup = function(self, button, x, y) - local region = nil - for _, r in revipairs(self.regions) do - if r.rect:contains(x, y) then - region = r - break - end - end - - if region ~= nil then - region:onmouseup(button, x - region.rect.x, y - region.rect.y, x, y) - end - end; - - onmousemove = function(self, x, y, dx, dy) - local region = nil - for _, r in revipairs(self.regions) do - if r.rect:contains(x, y) then - region = r - break - end - end - - if region ~= self.active_region then - if self.active_region then - self.active_region:onmouseleave() - end - - if region ~= nil then - region:onmouseenter() - end - end - - if region ~= nil then - region:onmousemove(x - region.rect.x, y - region.rect.y, dx, dy) - end - - self.active_region = region - end; - - onmousewheel = function(self, dx, dy) - if not self.active_region then return end - self.active_region:onmousewheel(dx, dy) - end; - - onkeydown = function(self, key) - if not self.active_region then return end - self.active_region:onkeydown(key) - end; - - onkeyup = function(self, key) - if not self.active_region then return end - self.active_region:onkeyup(key) - end; - - onresize = function(self, new_width, new_height) - for _, region in ipairs(self.regions) do - region:onresize(new_width, new_height) - end - end; - - update = function(self, dt) - for i, region in ipairs(self.regions) do - region:update(dt) - - if region.should_delete then - table.remove(self.regions, i) - end - end - end; - - draw = function(self) - for _, region in ipairs(self.regions) do - love.graphics.push() - love.graphics.setScissor(scissor_points(region.rect.x, region.rect.y, region.rect.w, region.rect.h)) - love.graphics.translate(region.rect.x, region.rect.y) - region:draw() - love.graphics.setScissor() - love.graphics.pop() - end - end; -} - -return module { Ui } diff --git a/src/ui.lua b/src/ui.lua index 99c3c79..88ad1c8 100644 --- a/src/ui.lua +++ b/src/ui.lua @@ -1,11 +1,164 @@ import { + Rectangle = "src.utils:Rectangle"; + revipairs = "src.utils:revipairs"; } -ui_class = {} -function ui_class:make() +components = {} +function component_lookup(type_, name) + if type(components[type_][name]) == "function" then + return components[type_][name] + end + + if components[type_].extends then + local l = component_lookup(components[type_].extends, name) + if l then return l end + return false + end + + return false +end + +ui = {} +function ui.register_component(name) + if components[name] ~= nil then + error("Component with name " .. name .. " already registered") + end + + return function(component) + components[name] = component + end +end + +function ui.make_element(type_, ...) + local o = { + type = type_; -- Type used to look up component functions + layer = layer or 0; -- Used for sorting children + remove = false; -- Whether to remove this element + rect = Rectangle(0, 0, 0, 0); -- Rectangle bounds of the object + children = {}; -- List of children, sorted by layer + + -- Any other data can be appended to this object + } + + local init_func = component_lookup(type_, "init") + if init_func then init_func(o, ...) end + + return o +end + +ui.focused = nil +function ui.focus(elem) + if ui.focused == elem then return end + + if ui.focused then + local unfocus_func = component_lookup(ui.focused.type, "unfocus") + if unfocus_func then unfocus_func(ui.focused) end + end + + ui.focused = elem + + if ui.focused then + local focus_func = component_lookup(ui.focused.type, "focus") + if focus_func then focus_func(ui.focused) end + end +end + +function ui.insert_child(parent, child) + table.insert(parent.children, child) + ui.sort_children(parent) +end + +function ui.sort_children(elem) + table.sort(elem.children, function(a, b) + return a.layer < b.layer + end) +end + +function ui.fire_stoppable_event(elem, eventname, ...) + local args + + local args_trans_func = component_lookup(elem.type, eventname .. "_trans") + if args_trans_func then + args = { args_trans_func(elem, ...) } + else + args = { ... } + end + if #elem.children > 0 then + for _, chld in ipairs(elem.children) do + if ui.fire_stoppable_event(chld, eventname, unpack(args)) then + if chld.remove then + table.remove(elem.children, i) + end + + ui.sort_children(elem) + + return true + end + end + end + + local event_func = component_lookup(elem.type, eventname) + if event_func then return event_func(elem, unpack(args)) end + return false +end + +function ui.fire_propagating_event(elem, eventname, ...) + local args + local args_trans_func = component_lookup(elem.type, eventname .. "_trans") + if args_trans_func then + args = { args_trans_func(elem, ...) } + else + args = { ... } + end + if #elem.children > 0 then + for i, chld in ipairs(elem.children) do + ui.fire_propagating_event(chld, eventname, unpack(args)) + + if chld.remove then + table.remove(elem.children, i) + end + end + + -- TODO: Maybe make this a flag? + -- ui.resort = true? + ui.sort_children(elem) + end + + local event_func = component_lookup(elem.type, eventname) + if event_func then event_func(elem, unpack(args)) end end -ui = singleton(ui_class) +local events = { + "mousepressed", + "mousereleased", + "mousemoved", + "wheelmoved", + "keypressed", + "keyreleased", +} +for _, ev in ipairs(events) do + ui[ev] = function(elem, ...) ui.fire_stoppable_event(elem, ev, ...) end +end + +function ui.update(elem, dt) + ui.fire_propagating_event(elem, "update", dt) +end + +function ui.resize(elem, w, h) + ui.fire_propagating_event(elem, "resize", w, h) +end + +function ui.draw(elem) + local predraw_func = component_lookup(elem.type, "predraw") + if predraw_func then predraw_func(elem) end + + for _, chld in revipairs(elem.children) do + ui.draw(chld) + end + + local postdraw_func = component_lookup(elem.type, "postdraw") + if postdraw_func then postdraw_func(elem) end +end return module { ui diff --git a/src/old-ui/text.lua b/src/ui/text.lua similarity index 100% rename from src/old-ui/text.lua rename to src/ui/text.lua diff --git a/src/utils.lua b/src/utils.lua index 86167b3..a08a676 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -58,10 +58,7 @@ function random_str(n) end function revipairs(t) - local max = 1 - while t[max] ~= nil do - max = max + 1 - end + local max = #t + 1 local function ripairs_it(t, i) i = i - 1 local v = t[i] @@ -87,6 +84,10 @@ class "Rectangle" { and y >= self.y and y <= self.y + self.h end; + contains_trans = function(self, x, y) + return x >= 0 and y >= 0 and x <= self.w and y <= self.h + end; + intersects = function(self, other) return self.x <= other.x + other.w and self.x + self.w >= other.x diff --git a/src/wasm/text.lua b/src/wasm/text.lua index c753387..3797827 100644 --- a/src/wasm/text.lua +++ b/src/wasm/text.lua @@ -2,6 +2,12 @@ import { pprint = "lualib.pprint"; } +-- The current line number during processing +-- This is a global to the functions and is shared +-- This processed can not be parallelized for this variable +-- (but I wouldn't paralleize it right now anyway) +local line = 1 + function build_type(text, type_, color) for _, t in ipairs(type_.param_types) do table.insert(text, { text = t .. " ", color = color }) @@ -22,152 +28,151 @@ function build_type(text, type_, color) end end -WasmCodeGenerator = singleton { - init = function(self) end; +function build_header(func, mod, colors) + local text = {} - setup = function(self, func, mod, colors) - self.func = func - self.mod = mod - self.colors = colors; - self.line = 1 - end; + if mod.start ~= nil and mod.start.contents.func == func.funcidx then + table.insert(text, { text = "*START* ", color = colors.primary_text }) + end + table.insert(text, { text = "func[" .. func.funcidx .. "] ", color = colors.code }) + table.insert(text, { text = func.name, color = colors.keyword }) + table.insert(text, { text = ": ", color = colors.code }) - build_header = function(self) - local text = {} + build_type(text, func.type_, colors.code) + table.insert(text, { newline = true }) - if self.mod.start ~= nil and self.mod.start.contents.func == self.func.funcidx then - table.insert(text, { text = "*START* ", color = self.colors.primary_text }) - end - table.insert(text, { text = "func[" .. self.func.funcidx .. "] ", color = self.colors.code }) - table.insert(text, { text = self.func.name, color = self.colors.keyword }) - table.insert(text, { text = ": ", color = self.colors.code }) - - build_type(text, self.func.type_, self.colors.code) - table.insert(text, { newline = true }) - - return text - end; - - add_line_number = function(self, textlist, newline) - if newline == nil then newline = true end - - table.insert(textlist, { newline = newline }) - table.insert(textlist, { - text = ("%4d"):format(line), - color = self.colors.code, - background = self.colors.dark_background, - post_indent = true - }) - table.insert(textlist, { text = " ", post_indent = true }) - - line = line + 1 - end; - - instr_to_text = function(self, textlist, instr) - if instr[1] == "block" then - table.insert(textlist, { change_indent = 1 }) - for _, ins in ipairs(instr.instrs) do - self:instr_to_text(textlist, ins) - end - table.insert(textlist, { change_indent = -1 }) - self:add_line_number(textlist) - table.insert(textlist, { text = "label ", color = self.colors.jumppoint }) - table.insert(textlist, { text = instr.label, color = self.colors.value }) + return text +end - elseif instr[1] == "loop" then - self:add_line_number(textlist) - table.insert(textlist, { text = "loop ", color = self.colors.jumppoint }) - table.insert(textlist, { text = instr.label, color = self.colors.value, change_indent = 1 }) +function add_line_number(textlist, colors, newline) + if newline == nil then newline = true end - for _, ins in ipairs(instr.instrs) do - self:instr_to_text(textlist, ins) - end - table.insert(textlist, { change_indent = -1 }) + table.insert(textlist, { newline = newline }) + table.insert(textlist, { + text = ("%4d"):format(line), + color = colors.code, + background = colors.dark_background, + post_indent = true + }) + table.insert(textlist, { text = " ", post_indent = true }) - elseif instr[1] == "if" then - self:add_line_number(textlist) - table.insert(textlist, { text = "if", color = self.colors.jumppoint, change_indent = 1 }) - for _, ins in ipairs(instr.instrs) do - self:instr_to_text(textlist, ins) + line = line + 1 +end + +function instr_to_text(textlist, instr, func, mod, colors) + if instr[1] == "block" then + table.insert(textlist, { change_indent = 1 }) + for _, ins in ipairs(instr.instrs) do + instr_to_text(textlist, ins, func, mod, colors) + end + table.insert(textlist, { change_indent = -1 }) + add_line_number(textlist, colors) + table.insert(textlist, { text = "label ", color = colors.jumppoint }) + table.insert(textlist, { text = instr.label, color = colors.value }) + + elseif instr[1] == "loop" then + add_line_number(textlist, colors) + table.insert(textlist, { text = "loop ", color = colors.jumppoint }) + table.insert(textlist, { text = instr.label, color = colors.value, change_indent = 1 }) + + for _, ins in ipairs(instr.instrs) do + instr_to_text(textlist, ins, func, mod, colors) + end + table.insert(textlist, { change_indent = -1 }) + + elseif instr[1] == "if" then + add_line_number(textlist, colors) + table.insert(textlist, { text = "if", color = colors.jumppoint, change_indent = 1 }) + for _, ins in ipairs(instr.instrs) do + instr_to_text(textlist, ins, func, mod, colors) + end + table.insert(textlist, { change_indent = -1 }) + + if #instr.else_instrs > 0 then + add_line_number(textlist, colors) + table.insert(textlist, { text = "else", color = colors.jumppoint, change_indent = 1 }) + for _, ins in ipairs(instr.else_instrs) do + instr_to_text(textlist, ins, func, mod, colors) end table.insert(textlist, { change_indent = -1 }) + end - if #instr.else_instrs > 0 then - self:add_line_number(textlist) - table.insert(textlist, { text = "else", color = self.colors.jumppoint, change_indent = 1 }) - for _, ins in ipairs(instr.else_instrs) do - self:instr_to_text(textlist, ins) + elseif instr[1] == "call" then + add_line_number(textlist, colors) + table.insert(textlist, { text = instr[1] .. " ", color = colors.keyword }) + table.insert(textlist, { text = mod.funcs[instr.x].name, color = colors.value }) + + elseif instr[1] == "call_indirect" then + add_line_number(textlist, colors) + table.insert(textlist, { text = ("%s [%d] "):format(instr[1], instr.table), color = colors.keyword }) + build_type(textlist, mod.types.contents[instr.x + 1], colors.code) + + elseif instr[1]:match("local") then + add_line_number(textlist, colors) + table.insert(textlist, { text = instr[1] .. " ", color = colors.keyword }) + table.insert(textlist, { text = func.locals[instr.x + 1].name, color = colors.value }) + + else + add_line_number(textlist, colors) + table.insert(textlist, { text = instr[1] .. " ", color = colors.keyword }) + if instr.x then + if type(instr.x) == "table" then + if instr.x[1] == "memarg" then + table.insert(textlist, { + text = ("align=0x%02x offset=0x%04x"):format(instr.x.align, instr.x.offset); + color = colors.value + }) end - table.insert(textlist, { change_indent = -1 }) - end - elseif instr[1] == "call" then - self:add_line_number(textlist) - table.insert(textlist, { text = instr[1] .. " ", color = self.colors.keyword }) - table.insert(textlist, { text = self.mod.funcs[instr.x].name, color = self.colors.value }) - - elseif instr[1] == "call_indirect" then - self:add_line_number(textlist) - table.insert(textlist, { text = ("%s [%d] "):format(instr[1], instr.table), color = self.colors.keyword }) - build_type(textlist, self.mod.types.contents[instr.x + 1], self.colors.code) - - elseif instr[1]:match("local") then - self:add_line_number(textlist) - table.insert(textlist, { text = instr[1] .. " ", color = self.colors.keyword }) - table.insert(textlist, { text = self.func.locals[instr.x + 1].name, color = self.colors.value }) - - else - self:add_line_number(textlist) - table.insert(textlist, { text = instr[1] .. " ", color = self.colors.keyword }) - if instr.x then - if type(instr.x) == "table" then - if instr.x[1] == "memarg" then - table.insert(textlist, { - text = ("align=0x%02x offset=0x%04x"):format(instr.x.align, instr.x.offset); - color = self.colors.value - }) + if instr.x[1] == "labelidx" then + if instr.x.block then + table.insert(textlist, { text = instr.x.block.label .. " ", color = colors.value }) end - - if instr.x[1] == "labelidx" then - if instr.x.block then - table.insert(textlist, { text = instr.x.block.label .. " ", color = self.colors.value }) - end - table.insert(textlist, { text = ("[0x%02x]"):format(instr.x.labelidx), color = self.colors.code }) - end - else - table.insert(textlist, { text = tostring(instr.x), color = self.colors.value }) + table.insert(textlist, { text = ("[0x%02x]"):format(instr.x.labelidx), color = colors.code }) end + else + table.insert(textlist, { text = tostring(instr.x), color = colors.value }) end end + end - return line - end; + return line +end - build_body = function(self) - local text = {} - line = 1 +function build_body(func, mod, colors) + if func.imported then + return { text = "Imported function", colors = colors.code, newline = true } + end - table.insert(text, { text = " Locals:", color = self.colors.keyword, newline = true, change_indent = 2 }) - for _, loc in ipairs(self.func.locals) do - table.insert(text, { text = loc.type_ .. " " .. loc.name, color = self.colors.code , newline = true }) - end + local text = {} + table.insert(text, { text = " Locals:", color = colors.keyword, newline = true, change_indent = 2 }) + for _, loc in ipairs(func.locals) do + table.insert(text, { text = loc.type_ .. " " .. loc.name, color = colors.code , newline = true }) + end - if #self.func.locals == 0 then - table.insert(text, { text = "no locals", color = self.colors.code, newline = true }) - end + if #func.locals == 0 then + table.insert(text, { text = "no locals", color = colors.code, newline = true }) + end - table.insert(text, { newline = true, change_indent = -2 }) - table.insert(text, { text = " Body:", color = self.colors.keyword }) + table.insert(text, { newline = true, change_indent = -2 }) + table.insert(text, { text = " Body:", color = colors.keyword }) - for _, instr in ipairs(self.func.body) do - self:instr_to_text(text, instr) - end + for _, instr in ipairs(func.body) do + instr_to_text(text, instr, func, mod, colors) + end - return text - end; -} + return text +end + +function generate_text_format(func, mod, colors, line_start) + line = line_start or 1 + local heading = build_header(func, mod, colors) + local body = build_body(func, mod, colors) + return heading, body +end return module { - WasmCodeGenerator; + generate_text_format = generate_text_format; + build_header = build_header; + build_body = build_body; } - -- 2.25.1