From: Brendan Hansen Date: Tue, 7 Apr 2020 00:24:26 +0000 (-0500) Subject: Continuing to work on this X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=a183e39bed976e0d552d6e3ad4317dd62cb2bf72;p=wasm-analyzer.git Continuing to work on this --- diff --git a/Makefile b/Makefile index 4218884..31dea2a 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ clib/%.o: clib/%.c clib/*.h - gcc -DLUA51_TARGET -O2 -o $@ -fPIC -c $< -llua + gcc -DLUA51_TARGET -O2 -o $@ -fPIC -c $< -llua clib/%.so: clib/%.o gcc -shared -o $@ $< -llua diff --git a/app.lua b/app.lua new file mode 100644 index 0000000..494be00 --- /dev/null +++ b/app.lua @@ -0,0 +1,281 @@ +import { + Rectangle = "src.utils:Rectangle"; + draw_arrow = "src.utils:draw_arrow"; + + wasm_decompile = "src.wasm.decompile:"; + wasm_analyze = "src.wasm.analyze:"; + + COLORS = "conf:COLOR_SCHEME"; +} + +class "FunctionBlock" [DraggableRect] { + init = function(self, func, x, y) + DraggableRect.init(self, x, y, 400, 400) + + self.func = func + funcBlocks[func.funcidx] = self + + WasmCodeGenerator:setup(func, mod, COLORS) + self.header_text = WasmCodeGenerator:build_header() + + if func.body then + self.body_text = WasmCodeGenerator:build_body() + else + self.body_text = { + { text = "imported function", color = COLORS.code } + } + end + + self.scroll = 0 + self.body_visible = true + self.old_height = 400 + + self.canvas = love.graphics.newCanvas(800, 800) + self.redraw = true + 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 + end; + + onhover = function(self, x, y) + self.redraw = not self.selected or self.redraw + self.selected = true + end; + + onunhover = function(self) + self.selected = false + self.redraw = true + 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) + end + self.redraw = true + end; + + onwheel = function(self, _, dy) + self.scroll = self.scroll - dy * 10; + if self.scroll < 0 then self.scroll = 0 end + self.redraw = true + end; + + ondrag = function(self, button, x, y, dx, dy) + DraggableRect.ondrag(self, button, x, y, dx, dy) + + 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; + + 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 + + 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 + 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 + + self.layer = 100 + end; + + onmouseleave = function(self) + self.should_delete = true + 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); + + background_color = COLORS.dark_background; + layer = 10; + + onresize = function(region, new_width, _) + -- Fill the width of the screen + region.rect.w = new_width + 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; + + rect = Rectangle(10, 10, 80, 30); + } + + top_bar:add_component(test) + return top_bar +end + +-- Responsible for containing and managing all +-- global-ish objects for the application +class "Application" { + init = function(self) + self.ui = Ui() + self.wasm = nil + end; + + create = function(self) + self.scrolling_region = ScrollingRegion(self.ui) + with(self.scrolling_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; + } + self.ui:add_region(self.scrolling_region) + + self.ui:add_region(make_top_bar(self)) + 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 + end; + + open_file = function(self, path) + local wasm_mod = wasm_decompile(path) + wasm_mod = wasm_analyze(wasm_mod) + self.wasm = wasm_mod + + -- TODO DO SOMETHING HERE + end; + + update = function(self, dt) + if love.keyboard.isDown "escape" then + love.event.quit() + end + + self.ui:update(dt) + end; + + draw = function(self) + love.graphics.setBackgroundColor(0, 0, 0) + love.graphics.clear() + + self.ui:draw() + + love.graphics.setScissor() + end; +} + +-- Delay the execution of the init function +-- until everything has been imported and +-- resolved +app = Application() + +return module { + app +} diff --git a/conf.lua b/conf.lua index 8b13689..752af6d 100644 --- a/conf.lua +++ b/conf.lua @@ -50,5 +50,5 @@ end return { COLOR_SCHEMES = COLOR_SCHEMES; - COLOR_SCHEME = COLOR_SCHEMES.DARK; + COLOR_SCHEME = COLOR_SCHEMES.LIGHT; } diff --git a/docs/todo b/docs/todo index a679a74..5ca193e 100644 --- a/docs/todo +++ b/docs/todo @@ -7,3 +7,24 @@ [X] Implement auto resizing [ ] Focus a function * Big window with better details + +GOALS FOR TODAY: +[ ] Get the app back to the state that it was in, but with slightly better code +[ ] Decide the features I want to implement +[ ] Look into how text input works so textboxes can be a thing + +FIRST THING: +[ ] Refactor to not use classes in the ui + * UI object will of the form: + +type UIObject a = { + type :: string; + layer :: string number; + data :: a; + + children :: [UiObject b]; // This is sorted by the layer of the object +} + + * Traversal will be pre-order for everything + * Drawing will be split into two parts, pre and post, for whether on the "left" or "right" of the node + * Events will be depth first, and return true if they consumed the action, and false if the action should propagate diff --git a/main.lua b/main.lua index c7311be..390bd39 100644 --- a/main.lua +++ b/main.lua @@ -2,276 +2,53 @@ require "lualib.oop" import { - pprint = "lualib.pprint"; - - decompile = "src.wasm.decompile:"; - analyze = "src.wasm.analyze:"; - WasmCodeGenerator = "src.wasm.text:"; - - Ui = "src.ui.ui:Ui"; - UiRegion = "src.ui.ui:UiRegion"; - UiButton = "src.ui.ui:UiButton"; - ScrollingUiRegion = "src.ui.ui:ScrollingUiRegion"; - DraggableRect = "src.ui.ui:DraggableRect"; - - render_text = "src.ui.ui:render_text"; - - scissor_points = "src.utils:scissor_points"; - Rectangle = "src.utils:Rectangle"; - - COLORS = "conf:COLOR_SCHEME"; -} - -local funcBlocks = {} - -class "FunctionBlock" [DraggableRect] { - init = function(self, func, x, y) - DraggableRect.init(self, x, y, 400, 400) - - self.func = func - funcBlocks[func.funcidx] = self - - WasmCodeGenerator:setup(func, mod, COLORS) - self.header_text = WasmCodeGenerator:build_header() - - if func.body then - self.body_text = WasmCodeGenerator:build_body() - else - self.body_text = { - { text = "imported function", color = COLORS.code } - } - end - - self.scroll = 0 - self.body_visible = true - self.old_height = 400 - - self.canvas = love.graphics.newCanvas(800, 800) - self.redraw = true - 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 - end; - - onhover = function(self, x, y) - self.redraw = not self.selected or self.redraw - self.selected = true - end; - - onunhover = function(self) - self.selected = false - self.redraw = true - end; - - onclick = function(self, button, x, y, ox, oy) - DraggableRect.onclick(self, button, x, y, ox, oy) - - if button == 2 then - local cmr - cmr = FunctionBlockContextMenu(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) - end - self.redraw = true - end; - - onwheel = function(self, _, dy) - self.scroll = self.scroll - dy * 10; - if self.scroll < 0 then self.scroll = 0 end - self.redraw = true - end; - - ondrag = function(self, button, x, y, dx, dy) - DraggableRect.ondrag(self, button, x, y, dx, dy) - - 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; - - 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 = 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) - 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 - - love.graphics.setColor(1, 1, 1) - love.graphics.draw(self.canvas, self.rect.x, self.rect.y) - - 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() - love.graphics.line(sx, sy, tx, ty) - end - end - if self.func.callers then - love.graphics.setColor(COLORS.secondary_dark) - for _, caller in ipairs(self.func.callers) do - local sx, sy = self.rect:center() - local tx, ty = funcBlocks[caller].rect:center() - love.graphics.line(sx, sy, tx, ty) - end - end - end; -} - -class "FunctionBlockContextMenu" [UiRegion] { - init = function(self, ui, cx, cy, options) - UiRegion.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 - - self.layer = 100 - end; - - onmouseleave = function(self) - self.should_delete = true - end; + app = "app:" } -mod = nil -ui = nil function love.load() - 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() * 10000, math.random() * 10000) - 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 + app:create() + app:bootstrap() -function love.update(dt) - if love.keyboard.isDown "escape" then - love.event.quit() + if arg[2] then + app:open_file(arg[2]) end - ui:update(dt) -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.draw() - love.graphics.setBackgroundColor(0, 0, 0) - love.graphics.clear() - - ui:draw() - - love.graphics.setScissor() + -- 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/res/FiraCode-Regular.ttf b/res/FiraCode-Regular.ttf deleted file mode 100644 index 97c1159..0000000 Binary files a/res/FiraCode-Regular.ttf and /dev/null differ diff --git a/res/fonts/FiraCode-Regular.ttf b/res/fonts/FiraCode-Regular.ttf new file mode 100644 index 0000000..97c1159 Binary files /dev/null and b/res/fonts/FiraCode-Regular.ttf differ diff --git a/src/old-ui/components.lua b/src/old-ui/components.lua new file mode 100644 index 0000000..2890572 --- /dev/null +++ b/src/old-ui/components.lua @@ -0,0 +1,99 @@ +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 new file mode 100644 index 0000000..8e79c32 --- /dev/null +++ b/src/old-ui/regions.lua @@ -0,0 +1,287 @@ +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 new file mode 100644 index 0000000..8eee352 --- /dev/null +++ b/src/old-ui/text.lua @@ -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 } diff --git a/src/old-ui/ui.lua b/src/old-ui/ui.lua new file mode 100644 index 0000000..23f0c05 --- /dev/null +++ b/src/old-ui/ui.lua @@ -0,0 +1,114 @@ +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 new file mode 100644 index 0000000..99c3c79 --- /dev/null +++ b/src/ui.lua @@ -0,0 +1,12 @@ +import { +} + +ui_class = {} +function ui_class:make() +end + +ui = singleton(ui_class) + +return module { + ui +} diff --git a/src/ui/ui.lua b/src/ui/ui.lua deleted file mode 100644 index 73d3c43..0000000 --- a/src/ui/ui.lua +++ /dev/null @@ -1,546 +0,0 @@ -import { - pprint = "lualib.pprint"; - Rectangle = "src.utils:Rectangle"; - uuid = "src.utils:uuidv4"; - 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; -} - -class "UiRegion" { - init = function(self, ui) - self.active = false - self.ui = ui - - self.selected_object = nil - self.objects = {} - - 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_object = nil - end; - - add_object = function(self, obj) - obj.ui = self.ui - table.insert(self.objects, 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_object = function(self, new_selected) - if self.selected_object ~= new_selected then - if self.selected_object ~= nil then - self.selected_object:onunselect() - end - - self.selected_object = new_selected - - if self.selected_object ~= nil then - self.selected_object.order = 0 - - for _, obj in ipairs(self.objects) do - if obj ~= self.selected_obj then - obj.order = obj.order + 1 - end - end - - table.sort(self.objects, function(a, b) - return a.order > b.order - end) - - self.selected_object: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.objects) do - if self:_obj_rect(obj):contains(x, y) then - new_selected = obj - break - end - end - - self:set_selected_object(new_selected) - - if self.selected_object then - self.selected_object:onmousedown(button, x, y, ox, oy) - end - end; - - onmouseup = function(self, button, x, y, ox, oy) - self.is_mouse_down = false - - if self.selected_object ~= nil and self.fire_onclick then - local obj_rect = self:_obj_rect(self.selected_object) - self.selected_object: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_object - - self.hovered_object = nil - for _, obj in revipairs(self.objects) do - if self:_obj_rect(obj):contains(x, y) then - obj:onhover(x, y) - self.hovered_object = obj - break - end - end - - if old_hovered and self.hovered_object ~= old_hovered then - old_hovered:onunhover() - end - - self:set_selected_object(self.hovered_object) - - if self.is_mouse_down and self.selected_object ~= nil then - local obj_rect = self:_obj_rect(self.selected_object) - self.selected_object: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_object(nil) - end; - - onmousewheel = function(self, dx, dy) - if self.selected_object ~= nil then - self.selected_object: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.objects) 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 _, obj in ipairs(self.objects) do - love.graphics.push() - -- love.graphics.translate(obj.rect.x, obj.rect.y) - obj:draw() - love.graphics.pop() - end - end; -} - -class "ScrollingUiRegion" [UiRegion] { - init = function(self, ui) - UiRegion.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) - UiRegion.onmousedown(self, button, mx, my, ox, oy) - end; - - onmouseup = function(self, button, x, y, ox, oy) - local mx, my = self:_transform_mouse(x, y) - UiRegion.onmouseup(self, button, mx, my, ox, oy) - end; - - onmousemove = function(self, x, y, dx, dy) - local mx, my = self:_transform_mouse(x, y) - UiRegion.onmousemove(self, mx, my, dx / self.zoom, dy / self.zoom) - - if self.is_mouse_down and self.selected_object == 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) - UiRegion.onmousewheel(self, dx, dy) - - if self.selected_object == 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) - UiRegion.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) - UiRegion.draw(self) - love.graphics.pop() - - self.background_color = tmpbgcolor - end; -} - -class "UiObject" { - init = function(self) - self.rect = Rectangle(0, 0, 0, 0) - self.uuid = uuid() - 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 "UiButton" [UiObject] { - init = function(self) - UiObject.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", 0, 0, self.rect.w, self.rect.h) - - love.graphics.setColor(self.foreground) - love.graphics.printf(self.text, 2, 2, self.rect.w, "center") - end; -} - -class "DraggableRect" [UiObject] { - init = function(self, x, y, w, h) - UiObject.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; -} - -programming_font = false -function render_text(x, y, text) - if not programming_font then - programming_font = love.graphics.newFont("res/FiraCode-Regular.ttf", 14) - love.graphics.setFont(programming_font) - end - - local origx = x - local fheight = programming_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, programming_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 + programming_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 - -return module { - Ui = Ui; - UiRegion = UiRegion; - ScrollingUiRegion = ScrollingUiRegion; - UiObject = UiObject; - UiButton = UiButton; - DraggableRect = DraggableRect; - - render_text = render_text; -} diff --git a/src/utils.lua b/src/utils.lua index 812847a..86167b3 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -131,6 +131,26 @@ function wrap(f, ...) return function() f(unpack(a)) end end +function draw_arrow(sx, sy, tx, ty) + love.graphics.line(sx, sy, tx, ty) + + local dx = tx - sx + local dy = ty - sy + local d = math.sqrt(dx * dx + dy * dy) + dx = 18 * dx / d + dy = 18 * dy / d + + ox = dy + oy = -dx + + love.graphics.polygon( + "fill", + tx, ty, + tx - dx + ox, ty - dy + oy, + tx - dx - ox, ty - dy - oy + ) +end + return module { buffered_read = buffered_read; build_str = build_str; @@ -141,6 +161,7 @@ return module { wrap = wrap; scissor_points = scissor_points; + draw_arrow = draw_arrow; Rectangle = Rectangle; Stack = Stack;