Add a widget hierarchy implementation
A widget hierarchy describes the position of widgets. The hierarchy is a recursive tree of widget hierarchy instances. This functionality depends on a :layout function that is not yet implemented on widgets, but will be added later. Signed-off-by: Uli Schlachter <psychon@znc.in>
This commit is contained in:
parent
4785b63755
commit
02f67b61b4
|
@ -0,0 +1,285 @@
|
|||
---------------------------------------------------------------------------
|
||||
-- Management of widget hierarchies. Each widget hierarchy object has a widget
|
||||
-- for which it saves e.g. size and transformation in its parent. Also, each
|
||||
-- widget has a number of children.
|
||||
--
|
||||
-- @author Uli Schlachter
|
||||
-- @copyright 2015 Uli Schlachter
|
||||
-- @release @AWESOME_VERSION@
|
||||
-- @module wibox.hierarchy
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
local matrix = require("gears.matrix")
|
||||
local cairo = require("lgi").cairo
|
||||
local base = require("wibox.widget.base")
|
||||
|
||||
local hierarchy = {}
|
||||
|
||||
--- Create a new widget hierarchy that has no parent.
|
||||
-- @param context The context in which we are laid out.
|
||||
-- @param widget The widget that is at the base of the hierarchy.
|
||||
-- @param width The available width for this hierarchy
|
||||
-- @param height The available height for this hierarchy
|
||||
-- @param redraw_callback Callback that is called with the corresponding widget
|
||||
-- hierarchy on widget::redraw_needed on some widget.
|
||||
-- @param layout_callback Callback that is called with the corresponding widget
|
||||
-- hierarchy on widget::layout_changed on some widget.
|
||||
-- @param callback_arg A second argument that is given to the above callbacks.
|
||||
-- @param root The root of the widget hierarchy or nil if this creates the root.
|
||||
-- @return A new widget hierarchy
|
||||
local function hierarchy_new(context, widget, width, height, redraw_callback, layout_callback, callback_arg, root)
|
||||
local children = base.layout_widget(context, widget, width, height)
|
||||
local draws_x1, draws_y1, draws_x2, draws_y2 = 0, 0, width, height
|
||||
local result = {
|
||||
_parent = nil,
|
||||
_root = nil,
|
||||
_matrix = cairo.Matrix.create_identity(),
|
||||
_widget = widget,
|
||||
_size = {
|
||||
width = width,
|
||||
height = height
|
||||
},
|
||||
_draw_extents = nil,
|
||||
_children = {}
|
||||
}
|
||||
|
||||
result._root = root or result
|
||||
result._redraw = function() redraw_callback(result, callback_arg) end
|
||||
result._layout = function() layout_callback(result, callback_arg) end
|
||||
widget:weak_connect_signal("widget::redraw_needed", result._redraw)
|
||||
widget:weak_connect_signal("widget::layout_changed", result._layout)
|
||||
|
||||
for _, w in ipairs(children or {}) do
|
||||
local r = hierarchy_new(context, w._widget, w._width, w._height,
|
||||
redraw_callback, layout_callback, callback_arg, result._root)
|
||||
r._matrix = w._matrix
|
||||
r._parent = result
|
||||
table.insert(result._children, r)
|
||||
|
||||
-- Update our drawing extents
|
||||
local s = r._draw_extents
|
||||
local px, py, pwidth, pheight = matrix.transform_rectangle(r._matrix,
|
||||
s.x, s.y, s.width, s.height)
|
||||
local px2, py2 = px + pwidth, py + pheight
|
||||
draws_x1 = math.min(draws_x1, px)
|
||||
draws_y1 = math.min(draws_y1, py)
|
||||
draws_x2 = math.max(draws_x2, px2)
|
||||
draws_y2 = math.max(draws_y2, py2)
|
||||
end
|
||||
result._draw_extents = {
|
||||
x = draws_x1,
|
||||
y = draws_y1,
|
||||
width = draws_x2 - draws_x1,
|
||||
height = draws_y2 - draws_y1
|
||||
}
|
||||
|
||||
for k, f in pairs(hierarchy) do
|
||||
if type(f) == "function" then
|
||||
result[k] = f
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
--- Create a new widget hierarchy that has no parent.
|
||||
-- @param context The context in which we are laid out.
|
||||
-- @param widget The widget that is at the base of the hierarchy.
|
||||
-- @param width The available width for this hierarchy.
|
||||
-- @param height The available height for this hierarchy.
|
||||
-- @param redraw_callback Callback that is called with the corresponding widget
|
||||
-- hierarchy on widget::redraw_needed on some widget.
|
||||
-- @param layout_callback Callback that is called with the corresponding widget
|
||||
-- hierarchy on widget::layout_changed on some widget.
|
||||
-- @param callback_arg A second argument that is given to the above callbacks.
|
||||
-- @return A new widget hierarchy
|
||||
function hierarchy.new(context, widget, width, height, redraw_callback, layout_callback, callback_arg)
|
||||
return hierarchy_new(context, widget, width, height, redraw_callback, layout_callback, callback_arg, nil)
|
||||
end
|
||||
|
||||
--- Get the parent hierarchy of this widget hierarchy (or nil).
|
||||
function hierarchy:get_parent()
|
||||
return self._parent
|
||||
end
|
||||
|
||||
--- Get the widget that this hierarchy manages.
|
||||
function hierarchy:get_widget()
|
||||
return self._widget
|
||||
end
|
||||
|
||||
--- Get a cairo matrix that transforms to the parent's coordinate space from
|
||||
-- this hierarchy's coordinate system.
|
||||
-- @return A cairo matrix describing the transformation.
|
||||
function hierarchy:get_matrix_to_parent()
|
||||
return matrix.copy(self._matrix)
|
||||
end
|
||||
|
||||
--- Get a cairo matrix that transforms to the base of this hierarchy's
|
||||
-- coordinate system (aka the coordinate system of the device that this
|
||||
-- hierarchy is applied upon) from this hierarchy's coordinate system.
|
||||
-- @return A cairo matrix describing the transformation.
|
||||
function hierarchy:get_matrix_to_device()
|
||||
if not self._matrix_to_device then
|
||||
local m = cairo.Matrix.create_identity()
|
||||
local state = self
|
||||
while state ~= nil do
|
||||
m:multiply(m, state._matrix)
|
||||
state = state._parent
|
||||
end
|
||||
self._matrix_to_device = m
|
||||
end
|
||||
return matrix.copy(self._matrix_to_device)
|
||||
end
|
||||
|
||||
--- Get a cairo matrix that transforms from the parent's coordinate space into
|
||||
-- this hierarchy's coordinate system.
|
||||
-- @return A cairo matrix describing the transformation.
|
||||
function hierarchy:get_matrix_from_parent()
|
||||
local m = self:get_matrix_to_parent()
|
||||
m:invert()
|
||||
return m
|
||||
end
|
||||
|
||||
--- Get a cairo matrix that transforms from the base of this hierarchy's
|
||||
-- coordinate system (aka the coordinate system of the device that this
|
||||
-- hierarchy is applied upon) into this hierarchy's coordinate system.
|
||||
-- @return A cairo matrix describing the transformation.
|
||||
function hierarchy:get_matrix_from_device()
|
||||
local m = self:get_matrix_to_device()
|
||||
m:invert()
|
||||
return m
|
||||
end
|
||||
|
||||
--- Get the extents that this hierarchy possibly draws to (in the current coordinate space).
|
||||
-- This includes the size of this element plus the size of all children
|
||||
-- (after applying the corresponding transformation).
|
||||
-- @return x, y, width, height
|
||||
function hierarchy:get_draw_extents()
|
||||
local ext = self._draw_extents
|
||||
return ext.x, ext.y, ext.width, ext.height
|
||||
end
|
||||
|
||||
--- Get the size that this hierarchy logically covers (in the current coordinate space).
|
||||
-- @return width, height
|
||||
function hierarchy:get_size()
|
||||
local ext = self._size
|
||||
return ext.width, ext.height
|
||||
end
|
||||
|
||||
--- Get a list of all children.
|
||||
-- @return List of all children hierarchies.
|
||||
function hierarchy:get_children()
|
||||
return self._children
|
||||
end
|
||||
|
||||
--- Compare two widget hierarchies and compute a cairo Region that contains all
|
||||
-- rectangles that aren't the same between both hierarchies.
|
||||
-- @param other The hierarchy to compare with
|
||||
-- @return A cairo Region containing the differences.
|
||||
function hierarchy:find_differences(other)
|
||||
local region = cairo.Region.create()
|
||||
local function needs_redraw(h)
|
||||
local m = h:get_matrix_to_device()
|
||||
local p = h._draw_extents
|
||||
local x, y, width, height = matrix.transform_rectangle(m, p.x, p.y, p.width, p.height)
|
||||
local x1, y1 = math.floor(x), math.floor(y)
|
||||
local x2, y2 = math.ceil(x + width), math.ceil(y + height)
|
||||
region:union_rectangle(cairo.RectangleInt({
|
||||
x = x1, y = y1, width = x2 - x1, height = y2 - y1
|
||||
}))
|
||||
end
|
||||
local compare
|
||||
compare = function(self, other)
|
||||
local s_size, o_size = self._size, other._size
|
||||
if s_size.width ~= o_size.width or s_size.height ~= o_size.height or
|
||||
#self._children ~= #other._children or self._widget ~= other._widget or
|
||||
not matrix.equals(self._matrix, other._matrix) then
|
||||
needs_redraw(self)
|
||||
needs_redraw(other)
|
||||
else
|
||||
for i = 1, #self._children do
|
||||
compare(self._children[i], other._children[i])
|
||||
end
|
||||
end
|
||||
end
|
||||
compare(self, other)
|
||||
return region
|
||||
end
|
||||
|
||||
--- Does the given cairo context have an empty clip (aka "no drawing possible")?
|
||||
local function empty_clip(cr)
|
||||
local x, y, width, height = cr:clip_extents()
|
||||
return width == 0 or height == 0
|
||||
end
|
||||
|
||||
--- Draw a hierarchy to some cairo context.
|
||||
-- This function draws the widgets in this widget hierarchy to the given cairo
|
||||
-- context. The context's clip is used to skip parts that aren't visible.
|
||||
-- @param context The context in which widgets are drawn.
|
||||
-- @param cr The cairo context that is used for drawing.
|
||||
function hierarchy:draw(context, cr)
|
||||
local widget = self:get_widget()
|
||||
if not widget.visible then
|
||||
return
|
||||
end
|
||||
|
||||
cr:save()
|
||||
cr:transform(self:get_matrix_to_parent())
|
||||
|
||||
-- Clip to the draw extents
|
||||
cr:rectangle(self:get_draw_extents())
|
||||
cr:clip()
|
||||
|
||||
-- Draw if needed
|
||||
if not empty_clip(cr) then
|
||||
local opacity = widget.opacity
|
||||
local function call(func, extra_arg1, extra_arg2)
|
||||
if not func then return end
|
||||
local function error_function(err)
|
||||
print(debug.traceback("Error while drawing widget: " .. tostring(err), 2))
|
||||
end
|
||||
if not extra_arg2 then
|
||||
xpcall(function()
|
||||
func(widget, arg, cr, self:get_size())
|
||||
end, error_function)
|
||||
else
|
||||
xpcall(function()
|
||||
func(widget, arg, extra_arg1, extra_arg2, cr, self:get_size())
|
||||
end, error_function)
|
||||
end
|
||||
end
|
||||
|
||||
-- Prepare opacity handling
|
||||
if opacity ~= 1 then
|
||||
cr:push_group()
|
||||
end
|
||||
|
||||
-- Draw the widget
|
||||
cr:save()
|
||||
cr:rectangle(0, 0, self:get_size())
|
||||
cr:clip()
|
||||
call(widget.draw)
|
||||
cr:restore()
|
||||
|
||||
-- Draw its children (We already clipped to the draw extents above)
|
||||
call(widget.before_draw_children)
|
||||
for i, wi in ipairs(self:get_children()) do
|
||||
call(widget.before_draw_child, i, wi:get_widget())
|
||||
wi:draw(context, cr)
|
||||
call(widget.after_draw_child, i, wi:get_widget())
|
||||
end
|
||||
call(widget.after_draw_children)
|
||||
|
||||
-- Apply opacity
|
||||
if opacity ~= 1 then
|
||||
cr:pop_group_to_source()
|
||||
cr.operator = cairo.Operator.OVER
|
||||
cr:paint_with_alpha(opacity)
|
||||
end
|
||||
end
|
||||
|
||||
cr:restore()
|
||||
end
|
||||
|
||||
return hierarchy
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
|
@ -29,6 +29,7 @@ local wibox = { mt = {} }
|
|||
wibox.layout = require("wibox.layout")
|
||||
wibox.widget = require("wibox.widget")
|
||||
wibox.drawable = require("wibox.drawable")
|
||||
wibox.hierarchy = require("wibox.hierarchy")
|
||||
|
||||
--- Set the widget that the wibox displays
|
||||
function wibox:set_widget(widget)
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
---------------------------------------------------------------------------
|
||||
-- @author Uli Schlachter
|
||||
-- @copyright 2015 Uli Schlachter
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
local hierarchy = require("wibox.hierarchy")
|
||||
|
||||
local cairo = require("lgi").cairo
|
||||
local matrix = require("gears.matrix")
|
||||
local object = require("gears.object")
|
||||
|
||||
local function make_widget(children)
|
||||
local result = object()
|
||||
result:add_signal("widget::redraw_needed")
|
||||
result:add_signal("widget::layout_changed")
|
||||
result.visible = true
|
||||
result.layout = function()
|
||||
return children
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function make_child(widget, width, height, matrix)
|
||||
return { _widget = widget, _width = width, _height = height, _matrix = matrix }
|
||||
end
|
||||
|
||||
describe("wibox.hierarchy", function()
|
||||
describe("Accessor functions", function()
|
||||
local widget, instance
|
||||
before_each(function()
|
||||
local function nop() end
|
||||
local context = {}
|
||||
widget = make_widget(nil)
|
||||
instance = hierarchy.new(context, widget, 10, 20, nop, nop)
|
||||
end)
|
||||
|
||||
it("get_parent", function()
|
||||
assert.is_nil(instance:get_parent())
|
||||
end)
|
||||
|
||||
it("get_widget", function()
|
||||
assert.is.equal(instance:get_widget(), widget)
|
||||
end)
|
||||
|
||||
it("get_matrix_to_parent", function()
|
||||
assert.is_true(matrix.equals(cairo.Matrix.create_identity(),
|
||||
instance:get_matrix_to_parent()))
|
||||
end)
|
||||
|
||||
it("get_matrix_to_device", function()
|
||||
assert.is_true(matrix.equals(cairo.Matrix.create_identity(),
|
||||
instance:get_matrix_to_device()))
|
||||
end)
|
||||
|
||||
it("get_matrix_from_parent", function()
|
||||
assert.is_true(matrix.equals(cairo.Matrix.create_identity(),
|
||||
instance:get_matrix_from_parent()))
|
||||
end)
|
||||
|
||||
it("get_matrix_from_device", function()
|
||||
assert.is_true(matrix.equals(cairo.Matrix.create_identity(),
|
||||
instance:get_matrix_from_device()))
|
||||
end)
|
||||
|
||||
it("get_draw_extents", function()
|
||||
assert.is.same({ instance:get_draw_extents() }, { 0, 0, 10, 20 })
|
||||
end)
|
||||
|
||||
it("get_size", function()
|
||||
assert.is.same({ instance:get_size() }, { 10, 20 })
|
||||
end)
|
||||
|
||||
it("get_children", function()
|
||||
assert.is.same(instance:get_children(), {})
|
||||
end)
|
||||
end)
|
||||
|
||||
it("disconnect works", function()
|
||||
local child = make_widget(nil)
|
||||
local parent = make_widget({
|
||||
make_child(child, 2, 5, cairo.Matrix.create_translate(10, 0))
|
||||
})
|
||||
|
||||
local extra_arg = {}
|
||||
local child_redraws, child_layouts = 0, 0
|
||||
local parent_redraws, parent_layouts = 0, 0
|
||||
local function redraw(arg, extra)
|
||||
assert.is.equal(extra_arg, extra)
|
||||
if arg:get_widget() == child then
|
||||
child_redraws = child_redraws + 1
|
||||
elseif arg:get_widget() == parent then
|
||||
parent_redraws = parent_redraws + 1
|
||||
else
|
||||
error("Unknown widget")
|
||||
end
|
||||
end
|
||||
local function layout(arg, extra)
|
||||
assert.is.equal(extra_arg, extra)
|
||||
if arg:get_widget() == child then
|
||||
child_layouts = child_layouts + 1
|
||||
elseif arg:get_widget() == parent then
|
||||
parent_layouts = parent_layouts + 1
|
||||
else
|
||||
error("Unknown widget")
|
||||
end
|
||||
end
|
||||
local context = {}
|
||||
local instance = hierarchy.new(context, parent, 15, 20, redraw, layout, extra_arg)
|
||||
|
||||
-- There should be a connection
|
||||
parent:emit_signal("widget::redraw_needed")
|
||||
assert.is.same({ 0, 0, 1, 0 }, { child_redraws, child_layouts, parent_redraws, parent_layouts })
|
||||
child:emit_signal("widget::redraw_needed")
|
||||
assert.is.same({ 1, 0, 1, 0 }, { child_redraws, child_layouts, parent_redraws, parent_layouts })
|
||||
child:emit_signal("widget::layout_changed")
|
||||
assert.is.same({ 1, 1, 1, 0 }, { child_redraws, child_layouts, parent_redraws, parent_layouts })
|
||||
parent:emit_signal("widget::layout_changed")
|
||||
assert.is.same({ 1, 1, 1, 1 }, { child_redraws, child_layouts, parent_redraws, parent_layouts })
|
||||
|
||||
-- Garbage-collect the hierarchy
|
||||
instance = nil
|
||||
collectgarbage("collect")
|
||||
|
||||
-- No connections should be left
|
||||
parent:emit_signal("widget::redraw_needed")
|
||||
child:emit_signal("widget::redraw_needed")
|
||||
child:emit_signal("widget::layout_changed")
|
||||
parent:emit_signal("widget::layout_changed")
|
||||
assert.is.same({ 1, 1, 1, 1 }, { child_redraws, child_layouts, parent_redraws, parent_layouts })
|
||||
end)
|
||||
|
||||
describe("children", function()
|
||||
local child, intermediate, parent
|
||||
local hierarchy_child, hierarchy_intermediate, hierarchy_parent
|
||||
before_each(function()
|
||||
child = make_widget(nil)
|
||||
intermediate = make_widget({
|
||||
make_child(child, 10, 20, cairo.Matrix.create_translate(0, 5))
|
||||
})
|
||||
parent = make_widget({
|
||||
make_child(intermediate, 5, 2, cairo.Matrix.create_translate(4, 0))
|
||||
})
|
||||
|
||||
local function nop() end
|
||||
local context = {}
|
||||
hierarchy_parent = hierarchy.new(context, parent, 15, 16, nop, nop)
|
||||
|
||||
-- This also tests get_children
|
||||
local children = hierarchy_parent:get_children()
|
||||
assert.is.equal(#children, 1)
|
||||
hierarchy_intermediate = children[1]
|
||||
|
||||
local children = hierarchy_intermediate:get_children()
|
||||
assert.is.equal(#children, 1)
|
||||
hierarchy_child = children[1]
|
||||
end)
|
||||
|
||||
it("get_parent", function()
|
||||
assert.is.equal(hierarchy_child:get_parent(), hierarchy_intermediate)
|
||||
assert.is.equal(hierarchy_intermediate:get_parent(), hierarchy_parent)
|
||||
assert.is_nil(hierarchy_parent:get_parent())
|
||||
end)
|
||||
|
||||
it("get_widget", function()
|
||||
assert.is.equal(hierarchy_child:get_widget(), child)
|
||||
assert.is.equal(hierarchy_intermediate:get_widget(), intermediate)
|
||||
assert.is.equal(hierarchy_parent:get_widget(), parent)
|
||||
end)
|
||||
|
||||
it("get_matrix_to_parent", function()
|
||||
assert.is_true(matrix.equals(hierarchy_child:get_matrix_to_parent(), cairo.Matrix.create_translate(0, 5)))
|
||||
assert.is_true(matrix.equals(hierarchy_intermediate:get_matrix_to_parent(), cairo.Matrix.create_translate(4, 0)))
|
||||
assert.is_true(matrix.equals(hierarchy_parent:get_matrix_to_parent(), cairo.Matrix.create_identity()))
|
||||
end)
|
||||
|
||||
it("get_matrix_to_device", function()
|
||||
assert.is_true(matrix.equals(hierarchy_child:get_matrix_to_device(), cairo.Matrix.create_translate(4, 5)))
|
||||
assert.is_true(matrix.equals(hierarchy_intermediate:get_matrix_to_device(), cairo.Matrix.create_translate(4, 0)))
|
||||
assert.is_true(matrix.equals(hierarchy_parent:get_matrix_to_device(), cairo.Matrix.create_identity()))
|
||||
end)
|
||||
|
||||
it("get_matrix_from_parent", function()
|
||||
assert.is_true(matrix.equals(hierarchy_child:get_matrix_from_parent(), cairo.Matrix.create_translate(0, -5)))
|
||||
assert.is_true(matrix.equals(hierarchy_intermediate:get_matrix_from_parent(), cairo.Matrix.create_translate(-4, 0)))
|
||||
assert.is_true(matrix.equals(hierarchy_parent:get_matrix_from_parent(), cairo.Matrix.create_identity()))
|
||||
end)
|
||||
|
||||
it("get_matrix_from_device", function()
|
||||
assert.is_true(matrix.equals(hierarchy_child:get_matrix_from_device(), cairo.Matrix.create_translate(-4, -5)))
|
||||
assert.is_true(matrix.equals(hierarchy_intermediate:get_matrix_from_device(), cairo.Matrix.create_translate(-4, 0)))
|
||||
assert.is_true(matrix.equals(hierarchy_parent:get_matrix_from_device(), cairo.Matrix.create_identity()))
|
||||
end)
|
||||
|
||||
it("get_draw_extents", function()
|
||||
assert.is.same({ hierarchy_child:get_draw_extents() }, { 0, 0, 10, 20 })
|
||||
assert.is.same({ hierarchy_intermediate:get_draw_extents() }, { 0, 0, 10, 25 })
|
||||
assert.is.same({ hierarchy_parent:get_draw_extents() }, { 0, 0, 15, 25 })
|
||||
end)
|
||||
|
||||
it("get_size", function()
|
||||
assert.is.same({ hierarchy_child:get_size() }, { 10, 20 })
|
||||
assert.is.same({ hierarchy_intermediate:get_size() }, { 5, 2 })
|
||||
assert.is.same({ hierarchy_parent:get_size() }, { 15, 16 })
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("find_differences", function()
|
||||
local child, intermediate, parent
|
||||
local instance
|
||||
local function nop() end
|
||||
before_each(function()
|
||||
child = make_widget(nil)
|
||||
intermediate = make_widget({
|
||||
make_child(child, 10, 20, cairo.Matrix.create_translate(0, 5))
|
||||
})
|
||||
parent = make_widget({
|
||||
make_child(intermediate, 5, 2, cairo.Matrix.create_translate(4, 0))
|
||||
})
|
||||
|
||||
local context = {}
|
||||
instance = hierarchy.new(context, parent, 15, 16, nop, nop)
|
||||
end)
|
||||
|
||||
it("No difference", function()
|
||||
local context = {}
|
||||
local instance2 = hierarchy.new(context, parent, 15, 16, nop, nop)
|
||||
local region = instance:find_differences(instance2)
|
||||
assert.is.equal(region:num_rectangles(), 0)
|
||||
end)
|
||||
|
||||
it("child moved", function()
|
||||
intermediate.layout = function()
|
||||
return { make_child(child, 10, 20, cairo.Matrix.create_translate(0, 4)) }
|
||||
end
|
||||
local context = {}
|
||||
local instance2 = hierarchy.new(context, parent, 15, 16, nop, nop)
|
||||
local region = instance:find_differences(instance2)
|
||||
assert.is.equal(region:num_rectangles(), 1)
|
||||
local rect = region:get_rectangle(0)
|
||||
-- The widget drew to 4, 5, 10, 20 before and 4, 4, 10, 20 after
|
||||
assert.is.same({ rect.x, rect.y, rect.width, rect.height }, { 4, 4, 10, 21 })
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
Loading…
Reference in New Issue