widgets: Add a grid layout [WIP]
In a quest to get rid of all fit/draw monkeypatching, this commit add a proper grid layout. It is currently as good as the old layout/grid.lua hack, but not "correct" used outside of Radical menus (swap/insert/remove are also broken) and it fail to handle many corner case.
This commit is contained in:
parent
b850b334ba
commit
30084a84d5
|
@ -0,0 +1,338 @@
|
|||
---------------------------------------------------------------------------
|
||||
-- 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_col[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 = column, 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 = row, 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_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()
|
||||
|
||||
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
|
Loading…
Reference in New Issue