Cleaned up some code; performance improvements
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 8 Apr 2020 17:36:26 +0000 (12:36 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 8 Apr 2020 17:36:26 +0000 (12:36 -0500)
app.lua
src/ui.lua
src/ui/components.lua [new file with mode: 0644]

diff --git a/app.lua b/app.lua
index 81d86ced61b6ceb568977aadc3115b63dfb56d8b..f1ea2bcee82b2c8498a0f0de410e7f79c561e84d 100644 (file)
--- a/app.lua
+++ b/app.lua
 import {
        Rectangle = "src.utils:Rectangle";
-       draw_arrow = "src.utils:draw_arrow";
-
        wasm_decompile = "src.wasm.decompile:";
        wasm_analyze = "src.wasm.analyze:";
-       wasm_text = "src.wasm.text";
 
        ui = "src.ui:";
        ui_text = "src.ui.text:";
+       ui_components = "src.ui.components"; -- Just imported to add the functionality
 
        COLORS = "conf:COLOR_SCHEME";
 }
 
--- A node reacts to no events, just helps with the rendering / event processing ordering
-ui.register_component "node" {}
-
-ui.register_component "root" {
-       resize = function(self, w, h)
-               self.rect.w = w
-               self.rect.h = h
-       end;
-}
-
-ui.register_component "rect" {
-       mousepressed_trans = function(self, button, x, y)
-               return button, x - self.rect.x, y - self.rect.y
-       end;
-
-       mousereleased_trans = function(self, button, x, y)
-               return button, x - self.rect.x, y - self.rect.y
-       end;
-
-       mousemoved_trans = function(self, x, y, dx, dy)
-               return x - self.rect.x, y - self.rect.y, dx, dy
-       end;
-
-       mousepressed = function(self, button, x, y)
-               if self.rect:contains_trans(x, y) then
-                       ui.focus(self)
-                       return true
-               end
-       end;
-
-       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;
-
-       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
-
-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
-               result = true
-       end
-       if self.rect:contains_trans(x, y) and button == 2 then
-               self.resize_down = true
-               result = true
-       end
-
-       return result
-end
-
-function function_block.mousereleased(self, button, x, y)
-       drag_rect.mousereleased(self, button, x, y)
-       if ui.focused == self and button == 2 then self.resize_down = false end
-
-       if ui.focused == self and button == 3 then
-               ui.insert_child(self, with(ui.make_element "rect") {
-                       rect = Rectangle(50, 40, 80, 60);
-                       color = { 1, 0, 0 };
-               })
-       end
-end
-
-function function_block.focus(self)
-       self.layer = 0
-end
-
-function function_block.unfocus(self)
-       self.resize_down = false
-       self.redraw = true
-
-       for _, chld in ipairs(self.parent.children) do
-               chld.layer = chld.layer + 1
-       end
-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
-
-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
-
-       if love.keyboard.isDown "down" then
-               self.scroll = self.scroll + 300 * dt
-               self.redraw = true
-       end
-       if love.keyboard.isDown "up" then
-               self.scroll = self.scroll - 300 * dt
-               if self.scroll < 0 then self.scroll = 0 end
-               self.redraw = true
-       end
-end
-
-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)
-
-               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)
-
-               local x, y = ui_text.render_text(2, 2, self.header_text)
-
-               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
-
-               love.graphics.pop()
-               love.graphics.setScissor(unpack(oldscissor))
-               love.graphics.setCanvas()
-               self.redraw = false
-       end
-
-       love.graphics.setColor(1, 1, 1)
-       love.graphics.push()
-       love.graphics.translate(self.rect.x, self.rect.y)
-       love.graphics.draw(self.canvas, 0, 0)
-end
-
-function function_block.postdraw(self)
-       love.graphics.pop()
-end
-
-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
-
-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
-
-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
-
-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
-
-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
-
-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
-
-               return true
-       end
-
-       return false
-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))
-               return true
-       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" {
@@ -370,7 +21,7 @@ class "Application" {
                        rect = Rectangle(0, 0, 0, 0);
                }
 
-               self.scroller = with(ui.make_element "scrolling") {
+               self.scroller = with(ui.make_element "scrolling_area") {
                        rect = Rectangle(0, 0, 1200, 900);
                        background_color = COLORS.background;
                        line_color = COLORS.dark_background;
@@ -417,7 +68,7 @@ class "Application" {
                                })
 
                                i = i + 1
-                               if i > 20 then break end
+                               if i > 25 then break end
                        end
                end
        end;
index ddcfc04c33b492acde6e53f19bbb7bb007d5ed22..992de4c6c526278551f2ce9f7a9e15cc4d19096f 100644 (file)
@@ -37,6 +37,7 @@ function ui.make_element(type_, ...)
                rect = Rectangle(0, 0, 0, 0);   -- Rectangle bounds of the object
                children = {};                                  -- List of children, sorted by layer
                parent = nil;                                   -- The parent node of this child
+               sort_dirty = false;                             -- Whether or not the children might be unsorted
 
                -- Any other data can be appended to this object
        }
@@ -74,6 +75,15 @@ function ui.sort_children(elem)
        table.sort(elem.children, function(a, b)
                return a.layer < b.layer
        end)
+
+       elem.sort_dirty = false
+end
+
+function ui.change_layer(elem, layer)
+       elem.layer = layer
+       if elem.parent then
+               elem.parent.sort_dirty = true
+       end
 end
 
 function ui.fire_stoppable_event(elem, eventname, ...)
@@ -92,7 +102,9 @@ function ui.fire_stoppable_event(elem, eventname, ...)
                                        table.remove(elem.children, i)
                                end
 
-                               ui.sort_children(elem)
+                               if elem.sort_dirty then
+                                       ui.sort_children(elem)
+                               end
 
                                return true
                        end
@@ -121,9 +133,9 @@ function ui.fire_propagating_event(elem, eventname, ...)
                        end
                end
 
-               -- TODO: Maybe make this a flag?
-               -- ui.resort = true?
-               ui.sort_children(elem)
+               if elem.sort_dirty then
+                       ui.sort_children(elem)
+               end
        end
 
        local event_func = component_lookup(elem.type, eventname)
diff --git a/src/ui/components.lua b/src/ui/components.lua
new file mode 100644 (file)
index 0000000..7b862ad
--- /dev/null
@@ -0,0 +1,363 @@
+import {
+       ui = "src.ui:";
+       ui_text = "src.ui.text:";
+
+       Rectangle = "src.utils:Rectangle";
+       wasm_text = "src.wasm.text";
+
+       COLORS = "conf:COLOR_SCHEME";
+}
+
+-- A node reacts to no events, just helps with the rendering / event processing ordering
+ui.register_component "node" {}
+
+ui.register_component "root" {
+       resize = function(self, w, h)
+               self.rect.w = w
+               self.rect.h = h
+       end;
+}
+
+local rect = {}
+function rect:mousepressed_trans(button, x, y)
+       return button, x - self.rect.x, y - self.rect.y
+end
+
+function rect:mousereleased_trans(button, x, y)
+       return button, x - self.rect.x, y - self.rect.y
+end
+
+function rect:mousemoved_trans(x, y, dx, dy)
+       return x - self.rect.x, y - self.rect.y, dx, dy
+end
+
+function rect:mousepressed(button, x, y)
+       if self.rect:contains_trans(x, y) then
+               ui.focus(self)
+               return true
+       end
+end
+
+function rect:predraw()
+       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
+
+function rect:postdraw()
+       love.graphics.pop()
+end
+
+local drag_rect = { extends = "rect" }
+function drag_rect:init() self.mouse_down = false end
+
+function drag_rect:mousepressed(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(button, x, y)
+       if button == 1 then self.mouse_down = false end
+end
+
+function drag_rect:unfocus()
+       self.mouse_down = false
+end
+
+function drag_rect:mousemoved(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(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
+
+function function_block:toggle_body_visible()
+       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(button, x, y)
+       local result = false
+       if drag_rect.mousepressed(self, button, x, y) then
+               self.redraw = true
+               result = true
+       end
+       if self.rect:contains_trans(x, y) and button == 2 then
+               self.resize_down = true
+               result = true
+       end
+
+       return result
+end
+
+function function_block:mousereleased(button, x, y)
+       drag_rect.mousereleased(self, button, x, y)
+       if ui.focused == self and button == 2 then self.resize_down = false end
+
+       if ui.focused == self and button == 3 then
+               ui.insert_child(self, with(ui.make_element "rect") {
+                       rect = Rectangle(50, 40, 80, 60);
+                       color = { 1, 0, 0 };
+               })
+       end
+end
+
+function function_block:focus()
+       ui.change_layer(self, 0)
+end
+
+function function_block:unfocus()
+       self.resize_down = false
+       self.redraw = true
+
+       for _, chld in ipairs(self.parent.children) do
+               ui.change_layer(chld, chld.layer + 1)
+       end
+end
+
+function function_block:mousemoved(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
+
+function function_block:wheelmoved(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(dt)
+       if ui.focused ~= self then return end
+
+       if love.keyboard.isDown "down" then
+               self.scroll = self.scroll + 300 * dt
+               self.redraw = true
+       end
+       if love.keyboard.isDown "up" then
+               self.scroll = self.scroll - 300 * dt
+               if self.scroll < 0 then self.scroll = 0 end
+               self.redraw = true
+       end
+end
+
+function function_block:predraw()
+       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(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)
+
+               local x, y = ui_text.render_text(2, 2, self.header_text)
+
+               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
+
+               love.graphics.pop()
+               love.graphics.setScissor(unpack(oldscissor))
+               love.graphics.setCanvas()
+               self.redraw = false
+       end
+
+       love.graphics.setColor(1, 1, 1)
+       love.graphics.push()
+       love.graphics.translate(self.rect.x, self.rect.y)
+       love.graphics.draw(self.canvas, 0, 0)
+end
+
+function function_block:postdraw()
+       love.graphics.pop()
+end
+
+local scrolling_area = {}
+function scrolling_area:init()
+       self.offset = { x = 0; y = 0 }
+       self.line_color = { 0, 0, 0 }
+       self.zoom = 1
+       self.mouse_down = false
+end
+
+function scrolling_area:mouse_trans(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
+
+function scrolling_area:mousepressed_trans(button, x, y)
+       local mx, my = scrolling_area.mouse_trans(self, x, y)
+       return button, mx, my
+end
+
+function scrolling_area:mousereleased_trans(button, x, y)
+       local mx, my = scrolling_area.mouse_trans(self, x, y)
+       return button, mx, my
+end
+
+function scrolling_area:mousemoved_trans(x, y, dx, dy)
+       local mx, my = scrolling_area.mouse_trans(self, x, y)
+       return mx, my, dx / self.zoom, dy / self.zoom
+end
+
+function scrolling_area:mousepressed(button, x, y)
+       ui.focus(self)
+       self.mouse_down = true
+       return true
+end
+
+function scrolling_area:mousereleased(button, x, y)
+       self.mouse_down = false
+end
+
+function scrolling_area:unfocus()
+       self.mouse_down = false
+end
+
+function scrolling_area:mousemoved(x, y, dx, dy)
+       if self.mouse_down then
+               self.offset.x = self.offset.x + dx
+               self.offset.y = self.offset.y + dy
+
+               return true
+       end
+
+       return false
+end
+
+function scrolling_area:change_zoom(multiplier)
+       self.zoom = self.zoom * multiplier
+       if self.zoom <= 0.2 then self.zoom = 0.2 end
+end
+
+function scrolling_area:wheelmoved(dx, dy)
+       if ui.focused == self then
+               scrolling_area.change_zoom(self, dy > 0 and 1.05 or (1 / 1.05))
+               return true
+       end
+end
+
+function scrolling_area:resize(w, h)
+       self.rect.w = w
+       self.rect.h = h - self.rect.y
+end
+
+function scrolling_area:update(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_area.change_zoom(self, 1 + 0.5 * dt)
+       end
+       if love.keyboard.isDown "a" then
+               scrolling_area.change_zoom(self, 1 - 0.5 * dt)
+       end
+end
+
+function scrolling_area:predraw()
+       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_area:postdraw()
+       love.graphics.pop()
+       love.graphics.setScissor()
+end
+
+ui.register_component "rect" (rect)
+ui.register_component "drag_rect" (drag_rect)
+ui.register_component "function_block" (function_block)
+ui.register_component "scrolling_area" (scrolling_area)
+
+return module {
+       rect = rect;
+       drag_rect = drag_rect;
+       function_block = function_block;
+       scrolling_area = scrolling_area;
+}