radical/widgets/grid.lua

353 lines
9.4 KiB
Lua

---------------------------------------------------------------------------
-- A two dimension layout disposing the widgets in a grid pattern.
--
--@DOC_wibox_layout_defaults_grid_EXAMPLE@
-- @author Emmanuel Lepage Valle
-- @copyright 2016 Emmanuel Lepage Vallee
-- @release @AWESOME_VERSION@
-- @classmod wibox.layout.grid
---------------------------------------------------------------------------
local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
local base = require("wibox.widget.base")
local table = table
local pairs = pairs
local util = require("awful.util")
local grid = {}
local function get_cell_sizes(self, context, orig_width, orig_height)
local width, height = orig_width, orig_height
local max_col, max_row = {}, {}
--TODO include spacing
--TODO compute the current sum for each column to have a "less wrong"
-- widget width.
local max_h = 0
for row_idx, row in ipairs(self._private.rows) do
for col_idx, v in ipairs(row) do
local w, h = base.fit_widget(self, context, v, width, height)
max_col[col_idx] = math.max(max_col[col_idx] or 0, w)
max_row[row_idx] = math.max(max_row[row_idx] or 0, h)
max_h = math.max(max_h, h)
end
height = height - max_h
end
return max_col, max_row
end
-- TODO
-- @param context The context in which we are drawn.
-- @param width The available width.
-- @param height The available height.
function grid:layout(context, width, height)
local max_col, max_row = get_cell_sizes(self, context, width, height)
local result = {}
local spacing = self._private.spacing --TODO support spacing
local y = 0
for row_idx = 1, #max_row do -- Vertical
local x = 0
for col_idx = 1, #max_col do -- Horizontal
local v = self._private.rows[row_idx][col_idx]
if v then
table.insert(result, base.place_widget_at(v, x, y, max_col[col_idx], max_row[row_idx]))
x = x + max_col[col_idx]
else
break
end
end
y = y + max_row[row_idx]
end
return result
end
local function get_next_empty(self, row, column)
row, column = row or 1, column or 1
local cc = self._private.column_count
for i = row, math.huge do
local r = self._private.rows[i]
if not r then
r = {}
self._private.rows[i] = r
self._private.row_count = i
end
for j = column, cc do
if not r[j] then
return i, j
end
end
end
end
local function index_to_point(data, index)
return math.floor(index / data._private.row_count) + 1,
index % data._private.column_count + 1
end
--- Add some widgets to the given grid layout
-- @param ... Widgets that should be added (must at least be one)
function grid:add(...)
-- No table.pack in Lua 5.1 :-(
local args = { n=select('#', ...), ... }
assert(args.n > 0, "need at least one widget to add")
local row, column
for i=1, args.n do
row, column = get_next_empty(self, row, column)
base.check_widget(args[i])
self._private.rows[row][column] = args[i]
end
self:emit_signal("widget::layout_changed")
end
function grid:set_cell_widget(row, column, widget)
if row < self._private.row_count then
grid:set_row_count(row)
end
if column < self._private.column_count then
grid:set_column_count(column)
end
self._private.rows[row] = self._private.rows[row] or {}
self:emit_signal("widget::layout_changed")
end
function grid:set_row_count(count)
self._private.row_count = count
end
function grid:set_column_count(count)
self._private.column_count = count
end
--- Re-pack all the widget according to the current `row_count` and `column_count`.
function grid:reflow()
--TODO
self:emit_signal("widget::layout_changed")
end
--- Remove a widget from the layout
-- @tparam number index The widget index to remove
-- @treturn boolean index If the operation is successful
function grid:remove(index, reclaim)
local TODO_TOTAL = math.huge --FIXME
if not index or index < 1 or index > TODO_TOTAL then return false end
local r, c = index_to_point(data, index)
self._private.rows[r][c] = nil
self:emit_signal("widget::layout_changed")
return true
end
--- Remove one or more widgets from the layout
-- The last parameter can be a boolean, forcing a recursive seach of the
-- widget(s) to remove.
-- @param widget ... Widgets that should be removed (must at least be one)
-- @treturn boolean If the operation is successful
function grid:remove_widgets(...)
local args = { ... }
local recursive = type(args[#args]) == "boolean" and args[#args]
local ret = true
for k, rem_widget in ipairs(args) do
if recursive and k == #args then break end
local idx, l = self:index(rem_widget, recursive)
if idx and l and l.remove then
l:remove(idx, false)
else
ret = false
end
end
return #args > (recursive and 1 or 0) and ret
end
function grid:get_children()
--TODO
return self._private.rows
end
function grid:set_children(children)
self:reset()
if #children > 0 then
self:add(unpack(children))
end
end
--- Replace the first instance of `widget` in the layout with `widget2`
-- @param widget The widget to replace
-- @param widget2 The widget to replace `widget` with
-- @tparam[opt=false] boolean recursive Digg in all compatible layouts to find the widget.
-- @treturn boolean If the operation is successful
function grid:replace_widget(widget, widget2, recursive)
local idx, l = self:index(widget, recursive)
if idx and l then
l:set(idx, widget2)
return true
end
return false
end
function grid:swap_widgets(widget1, widget2, recursive)
base.check_widget(widget1)
base.check_widget(widget2)
local idx1, l1 = self:index(widget1, recursive)
local idx2, l2 = self:index(widget2, recursive)
if idx1 and l1 and idx2 and l2 and (l1.set or l1.set_widget) and (l2.set or l2.set_widget) then
if l1.set then
l1:set(idx1, widget2)
elseif l1.set_widget then
l1:set_widget(widget2)
end
if l2.set then
l2:set(idx2, widget1)
elseif l2.set_widget then
l2:set_widget(widget1)
end
return true
end
return false
end
function grid:set(index, widget2)
if (not widget2) or (not self._private.rows[index]) then return false end
base.check_widget(widget2)
self._private.rows[index] = widget2
self:emit_signal("widget::layout_changed")
return true
end
--- Insert a new widget in the layout at position `index`
-- @tparam number index The position
-- @param widget The widget
-- @treturn boolean If the operation is successful
function grid:insert(index, widget)
local TODO_TOTAL = math.huge --FIXME
if not index or index < 1 or index > TODO_TOTAL then return false end
base.check_widget(widget)
table.insert(self._private.rows, index, widget) --FIXME
self:emit_signal("widget::layout_changed")
return true
end
-- Fit the grid layout into the given space
-- @param context The context in which we are fit.
-- @param orig_width The available width.
-- @param orig_height The available height.
function grid:fit(context, orig_width, orig_height)
local max_col, max_row = get_cell_sizes(self, context, orig_width, orig_height)
-- Now that all fit is done, get the maximum
local used_max_h, used_max_w = 0, 0
for row_idx = 1, #max_row do
-- The other widgets will be discarded
if used_max_h + max_row[row_idx] > orig_height then
break
end
used_max_h = used_max_h + max_row[row_idx]
end
for col_idx = 1, #max_col do
-- The other widgets will be discarded
if used_max_w + max_col[col_idx] > orig_width then
break
end
used_max_w = used_max_w + max_col[col_idx]
end
return used_max_w, used_max_h
end
function grid:reset()
self._private.rows = {}
self:emit_signal("widget::layout_changed")
end
--- Set the layout's fill_space property. If this property is true, the last
-- widget will get all the space that is left. If this is false, the last widget
-- won't be handled specially and there can be space left unused.
-- @property fill_space
function grid:set_fill_space(val)
if self._private.fill_space ~= val then
self._private.fill_space = not not val
self:emit_signal("widget::layout_changed")
end
end
local function get_layout(dir, widget1, ...)
local ret = base.make_widget(nil, nil, {enable_properties = true})
util.table.crush(ret, grid, true)
ret._private.widgets = {}
ret:set_spacing(0)
ret:set_fill_space(false)
if widget1 then
ret:add(widget1, ...)
end
return ret
end
--- Add spacing between each layout widgets
-- @property spacing
-- @tparam number spacing Spacing between widgets.
function grid:set_spacing(spacing)
if self._private.spacing ~= spacing then
self._private.spacing = spacing
self:emit_signal("widget::layout_changed")
end
end
function grid:get_spacing()
return self._private.spacing or 0
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(grid, {__call=function(_, ...) return get_layout(...) end})
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80