Refactored ui entirely
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 7 Apr 2020 21:18:15 +0000 (16:18 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 7 Apr 2020 21:18:15 +0000 (16:18 -0500)
app.lua
main.lua
src/old-ui/components.lua [deleted file]
src/old-ui/regions.lua [deleted file]
src/old-ui/text.lua [deleted file]
src/old-ui/ui.lua [deleted file]
src/ui.lua
src/ui/text.lua [new file with mode: 0644]
src/utils.lua
src/wasm/text.lua

diff --git a/app.lua b/app.lua
index 494be00f8ba2572e72db828a461a3dabdcb59935..b45a755368a755aeb4381277e150115260db6611 100644 (file)
--- 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;
index 390bd39afd2f3b6dd110bf338d45ef7d11ce2d48..72155101d67ab03616283bb7a614061e61c6947f 100644 (file)
--- 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 (file)
index 2890572..0000000
+++ /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 (file)
index 8e79c32..0000000
+++ /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/text.lua b/src/old-ui/text.lua
deleted file mode 100644 (file)
index 8eee352..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-import { }
-
-FONTS = {}
-
-Text = {}
-function Text.render_text(x, y, text)
-       local curr_font = love.graphics.getFont()
-
-       local origx = x
-       local fheight = curr_font:getHeight()
-       local indent_level = 0
-       local indentation = ""
-       local fresh_newline = true
-
-       for _, txt in ipairs(text) do
-               if txt.text then
-                       local printed = txt.text
-                       if fresh_newline then
-                               if txt.post_indent then
-                                       printed = txt.text .. indentation
-                               else
-                                       printed = indentation .. txt.text
-                               end
-                               fresh_newline = false
-                       end
-
-                       if txt.background then
-                               love.graphics.setColor(txt.background)
-                               love.graphics.rectangle("fill", x, y, curr_font:getWidth(txt.text), fheight + 2);
-                       end
-
-                       if txt.color then
-                               love.graphics.setColor(txt.color)
-                       end
-                       love.graphics.print(printed, x, y)
-
-                       x = x + curr_font:getWidth(printed)
-               end
-
-               if txt.newline then
-                       y = y + fheight + 2;
-                       x = origx
-                       fresh_newline = true
-               end
-
-               if txt.change_indent then
-                       indent_level = indent_level + txt.change_indent
-                       indentation = ""
-                       for i=1,indent_level do
-                               indentation = indentation .. "  "
-                       end
-               end
-       end
-
-       return x, y
-end
-
-function Text.register_font(name, size)
-       local fonts = FONTS[name] or {}
-       fonts[size] = love.graphics.newFont(("res/%s.ttf"):format(name), size)
-       FONTS[name] = fonts
-end
-
-function Text.set_font(name, size)
-       if FONTS[name][size] then
-               love.graphics.setFont(FONTS[name][size])
-       end
-end
-
-return module { Text }
diff --git a/src/old-ui/ui.lua b/src/old-ui/ui.lua
deleted file mode 100644 (file)
index 23f0c05..0000000
+++ /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 }
index 99c3c79cb68e683b8131f213e4fa312e2c5e0815..88ad1c859e9b95e03819da5b7ddfbacb398c8abf 100644 (file)
 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/ui/text.lua b/src/ui/text.lua
new file mode 100644 (file)
index 0000000..8eee352
--- /dev/null
@@ -0,0 +1,70 @@
+import { }
+
+FONTS = {}
+
+Text = {}
+function Text.render_text(x, y, text)
+       local curr_font = love.graphics.getFont()
+
+       local origx = x
+       local fheight = curr_font:getHeight()
+       local indent_level = 0
+       local indentation = ""
+       local fresh_newline = true
+
+       for _, txt in ipairs(text) do
+               if txt.text then
+                       local printed = txt.text
+                       if fresh_newline then
+                               if txt.post_indent then
+                                       printed = txt.text .. indentation
+                               else
+                                       printed = indentation .. txt.text
+                               end
+                               fresh_newline = false
+                       end
+
+                       if txt.background then
+                               love.graphics.setColor(txt.background)
+                               love.graphics.rectangle("fill", x, y, curr_font:getWidth(txt.text), fheight + 2);
+                       end
+
+                       if txt.color then
+                               love.graphics.setColor(txt.color)
+                       end
+                       love.graphics.print(printed, x, y)
+
+                       x = x + curr_font:getWidth(printed)
+               end
+
+               if txt.newline then
+                       y = y + fheight + 2;
+                       x = origx
+                       fresh_newline = true
+               end
+
+               if txt.change_indent then
+                       indent_level = indent_level + txt.change_indent
+                       indentation = ""
+                       for i=1,indent_level do
+                               indentation = indentation .. "  "
+                       end
+               end
+       end
+
+       return x, y
+end
+
+function Text.register_font(name, size)
+       local fonts = FONTS[name] or {}
+       fonts[size] = love.graphics.newFont(("res/%s.ttf"):format(name), size)
+       FONTS[name] = fonts
+end
+
+function Text.set_font(name, size)
+       if FONTS[name][size] then
+               love.graphics.setFont(FONTS[name][size])
+       end
+end
+
+return module { Text }
index 86167b3311bce8eb96b692526de09d9826351fe7..a08a676603ecf932c6b94db70238c2cd091e56d7 100644 (file)
@@ -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
index c753387dc45fce969ec2af4c9433fd0c8761e170..3797827b4ba5e0fe4ce09a575edb935250ad041b 100644 (file)
@@ -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;
 }
-