widget layouts: rewrite
Signed-off-by: Lukas Hrazky <lukkash@email.cz> Signed-off-by: Julien Danjou <julien@danjou.info>
This commit is contained in:
parent
eb7ae0fb20
commit
c09582dca3
|
@ -1,58 +0,0 @@
|
|||
-------------------------------------------------
|
||||
-- @author Gregor Best <farhaven@googlemail.com>
|
||||
-- @copyright 2009 Gregor Best
|
||||
-- @release @AWESOME_VERSION@
|
||||
-------------------------------------------------
|
||||
|
||||
-- Grab environment
|
||||
local ipairs = ipairs
|
||||
local type = type
|
||||
local table = table
|
||||
local math = math
|
||||
local setmetatable = setmetatable
|
||||
local util = require("awful.util")
|
||||
|
||||
--- Simple default layout, emulating the fallback C layout
|
||||
module("awful.widget.layout.default")
|
||||
|
||||
local function default(bounds, widgets, screen)
|
||||
local geometries = {
|
||||
free = { x = 0, y = 0, width = 0, height = bounds.height }
|
||||
}
|
||||
|
||||
local width = 0
|
||||
|
||||
local keys = util.table.keys_filter(widgets, "table", "widget")
|
||||
|
||||
for _, k in ipairs(keys) do
|
||||
local v = widgets[k]
|
||||
if type(v) == "table" then
|
||||
local layout = v.layout or default
|
||||
local nbounds = util.table.clone(bounds)
|
||||
local g = layout(nbounds, v, screen)
|
||||
for _, w in ipairs(g) do
|
||||
table.insert(geometries, w)
|
||||
end
|
||||
else
|
||||
if v.visible then
|
||||
local e = v:extents(screen)
|
||||
e.x = 0
|
||||
e.y = 0
|
||||
e.width = math.min(e.width, bounds.width)
|
||||
e.height = bounds.height
|
||||
width = math.max(e.width, width)
|
||||
|
||||
table.insert(geometries, e)
|
||||
else
|
||||
table.insert(geometries, { x = 0, y = 0, width = 0, height = 0 })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
geometries.free.width = bounds.width - width
|
||||
geometries.free.x = width
|
||||
|
||||
return geometries
|
||||
end
|
||||
|
||||
setmetatable(_M, { __call = function(_, ...) return default(...) end })
|
|
@ -0,0 +1,203 @@
|
|||
-------------------------------------------------
|
||||
-- @author Lukas Hrazky <lukkash@email.cz>
|
||||
-- @copyright 2009 Gregor Best, Lukas Hrazky
|
||||
-- @release @AWESOME_VERSION@
|
||||
-------------------------------------------------
|
||||
|
||||
local setmetatable = setmetatable
|
||||
local require = require
|
||||
local ipairs = ipairs
|
||||
local pairs = pairs
|
||||
local type = type
|
||||
local insert = table.insert
|
||||
local min = math.min
|
||||
local max = math.max
|
||||
local util = require("awful.util")
|
||||
local clone = util.table.clone
|
||||
|
||||
local margins = awful.widget.layout.margins
|
||||
local layout = awful.widget.layout
|
||||
|
||||
--module("awful.widget.layout.grid")
|
||||
|
||||
local M = {}
|
||||
|
||||
|
||||
-- Creates an iterator over the cell sizes from the direction string.
|
||||
-- It takes the bounds for the grid, the row/column sizes and a direction and returns an iterator
|
||||
-- over the slices of the bounds whose sizes are determined by 'widths' or 'heights'. The 'direction'
|
||||
-- determines which of these will be used and whether it will be in reverse or not.
|
||||
-- @param direction The direction in which to iterate: 'leftright', 'rightleft', 'topdown' or 'bottomup'
|
||||
-- @param bounds The bounds for the iterator, will be sliced according to direction
|
||||
-- @param widths The widths of the columns in the grid
|
||||
-- @param heights The heights of the rows in the grid
|
||||
-- @return An iterator over the slices of the bounds
|
||||
local function direction_iter(direction, bounds, widths, heights)
|
||||
local iter
|
||||
local i = 0
|
||||
local idx, sizes
|
||||
|
||||
-- preset the closure upvalues according to the direction
|
||||
if direction == "leftright" or direction == "rightleft" then
|
||||
idx = layout.regular_index
|
||||
sizes = widths
|
||||
elseif direction == "topdown" or direction == "bottomup" then
|
||||
idx = layout.switched_index
|
||||
sizes = heights
|
||||
end
|
||||
|
||||
if direction == "leftright" or direction == "topdown" then
|
||||
-- the regular direction iterator
|
||||
iter = function(bounds)
|
||||
i = i + 1
|
||||
if not sizes[i] then return end
|
||||
|
||||
local b = clone(bounds)
|
||||
local width = min(sizes[i], b[idx.width])
|
||||
b[idx.width] = width
|
||||
bounds[idx.x] = bounds[idx.x] + width + sizes.gap
|
||||
bounds[idx.width] = bounds[idx.width] - width
|
||||
return b
|
||||
end
|
||||
elseif direction == "rightleft" or direction == "bottomup" then
|
||||
-- the reverse direction iterator
|
||||
iter = function(bounds)
|
||||
i = i + 1
|
||||
if not sizes[i] then return end
|
||||
|
||||
local b = clone(bounds)
|
||||
local width = min(sizes[i], b[idx.width])
|
||||
b[idx.x] = b[idx.x] + b[idx.width] - width + sizes.gap
|
||||
b[idx.width] = width
|
||||
bounds[idx.width] = bounds[idx.width] - width
|
||||
return b
|
||||
end
|
||||
end
|
||||
|
||||
return iter, bounds
|
||||
end
|
||||
|
||||
-- Places the widgets in a grid.
|
||||
-- @see awful.widget.layout for a luadoc
|
||||
local function grid(bounds, widgets, screen)
|
||||
local widths = {}
|
||||
local heights = {}
|
||||
|
||||
-- we either have a table with the widths of the cells, or its a number and there is a col_count
|
||||
if type(widgets.cell_width) == "table" then
|
||||
widths = widgets.cell_width
|
||||
else
|
||||
for i = 1, widgets.col_count do widths[i] = widgets.cell_width end
|
||||
end
|
||||
|
||||
-- we either have a table with the heights of the cells, or its a number and there is a row_count
|
||||
if type(widgets.cell_height) == "table" then
|
||||
heights = widgets.cell_height
|
||||
else
|
||||
for i = 1, widgets.row_count do heights[i] = widgets.cell_height end
|
||||
end
|
||||
|
||||
widths.gap = widgets.col_gap or 0
|
||||
heights.gap = widgets.row_gap or 0
|
||||
|
||||
-- calculate total space required for the grid
|
||||
local w = - widths.gap
|
||||
local h = - heights.gap
|
||||
for _, v in ipairs(widths) do w = w + v + widths.gap end
|
||||
for _, v in ipairs(heights) do h = h + v + heights.gap end
|
||||
bounds.width = min(bounds.width, w)
|
||||
bounds.height = min(bounds.height, h)
|
||||
|
||||
-- the table for the geometries which will be returned
|
||||
-- we clone the bounds to the 'total' attribute. bounds are used to keep the free space
|
||||
-- throughout this function. at the end, 'total' is modified to represent the space taken
|
||||
-- by all widgets
|
||||
local geometries = {total = clone(bounds)}
|
||||
|
||||
-- i counts the widgets placed in the grid
|
||||
local i = 1
|
||||
|
||||
-- the iterators return the bounds for the cells of the table. first one in secondary direction
|
||||
-- returns rows or columns, second one returns cell bounds in that row/column
|
||||
for sb in direction_iter(widgets.secondary_dir or "topdown", bounds, widths, heights) do
|
||||
for b in direction_iter(widgets.primary_dir or "leftright", sb, widths, heights) do
|
||||
-- get the next widget, if there is none, we are done, some cells won't be filled
|
||||
local w = widgets[i]
|
||||
if not w then break end
|
||||
|
||||
if type(w) == "table" or type(w) == "widget" then
|
||||
local m = margins[w]
|
||||
-- shrink the cell bounds by the margins
|
||||
b.width = b.width - m.left - m.right
|
||||
b.height = b.height - m.top - m.bottom
|
||||
b.x = b.x + m.left
|
||||
b.y = b.y + m.top
|
||||
|
||||
if type(w) == "table" then
|
||||
-- backup the width and height of the table so we can restore it
|
||||
local t_width = w.width
|
||||
local t_height = w.height
|
||||
|
||||
-- if the 'widgets' table has height set and the table itself doesn't, we set it
|
||||
if widgets.resize then
|
||||
w.width = b.width
|
||||
w.height = b.height
|
||||
end
|
||||
|
||||
-- call the layout function recursively on this table
|
||||
local layout = w.layout or layout.default
|
||||
local g = layout(b, w, screen)
|
||||
|
||||
-- restore the table's original width and height
|
||||
w.width = t_width
|
||||
w.height = t_height
|
||||
|
||||
-- insert all geometries from the table to our geometries
|
||||
for _, w in ipairs(g) do
|
||||
insert(geometries, w)
|
||||
end
|
||||
|
||||
elseif type(widgets[i]) == "widget" then
|
||||
local g = {x = 0, y = 0, width = 0, height = 0}
|
||||
|
||||
if widgets[i].visible then
|
||||
-- get the geometry of the widget
|
||||
g = widgets[i]:extents(screen)
|
||||
|
||||
-- set the coords
|
||||
g.x = b.x
|
||||
g.y = b.y
|
||||
|
||||
-- if we are resizing or it doesn't fit inside the bounds, set width/height
|
||||
if widgets.resize or g.width > b.width then
|
||||
g.width = b.width
|
||||
end
|
||||
if widgets.resize or g.height > b.height then
|
||||
g.height = b.height
|
||||
end
|
||||
end
|
||||
|
||||
insert(geometries, g)
|
||||
end
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- set zero geometries for any leftover widgets
|
||||
-- Hack: if it has been registered as a widget in a wibox,
|
||||
-- it's w.len since __len meta does not work on table until Lua 5.2.
|
||||
-- Otherwise it's standard #w.
|
||||
local len = (widgets.len or #widgets)
|
||||
for j = i, len do
|
||||
insert(geometries, {x = 0, y = 0, width = 0, height = 0})
|
||||
end
|
||||
|
||||
return geometries
|
||||
end
|
||||
|
||||
--setmetatable(_M, { __call = function(_, ...) return grid(...) end })
|
||||
return grid
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80
|
|
@ -1,188 +1,95 @@
|
|||
-------------------------------------------------
|
||||
-- @author Gregor Best <farhaven@googlemail.com>
|
||||
-- @copyright 2009 Gregor Best
|
||||
-- @author Gregor Best <farhaven@googlemail.com>, Lukas Hrazky <lukkash@email.cz>
|
||||
-- @copyright 2009 Gregor Best, Lukas Hrazky
|
||||
-- @release @AWESOME_VERSION@
|
||||
-------------------------------------------------
|
||||
|
||||
-- Grab environment
|
||||
local require = require
|
||||
local ipairs = ipairs
|
||||
local type = type
|
||||
local table = table
|
||||
local math = math
|
||||
local pairs = pairs
|
||||
local util = require("awful.util")
|
||||
local default = require("awful.widget.layout.default")
|
||||
local margins = awful.widget.layout.margins
|
||||
local layout = require("awful.widget.layout")
|
||||
local linear = require("awful.widget.layout.linear_common")
|
||||
|
||||
--- Horizontal widget layout
|
||||
--- Horizontal widget layouts
|
||||
module("awful.widget.layout.horizontal")
|
||||
|
||||
local function horizontal(direction, bounds, widgets, screen)
|
||||
local geometries = { }
|
||||
local x = 0
|
||||
-- index used for horizontal layouts
|
||||
local idx = layout.regular_index
|
||||
|
||||
-- we are only interested in tables and widgets
|
||||
local keys = util.table.keys_filter(widgets, "table", "widget")
|
||||
--- Places the widgets in a row, aligned to the left.
|
||||
-- This is the default layout.
|
||||
-- It recognizes the following attributes in the encompassing table:
|
||||
-- <p><code>width</code>: Maximum width of the layout. (optional)<br/>
|
||||
-- <code>height</code>: Height of the layout. If set, all widgets are resized to this height. (optional)</p>
|
||||
-- @param bounds The geometry of the bounds for the layout.
|
||||
-- @param widgets The table of the widgets to layout. When layout function is placed in
|
||||
-- a table, it is the table that is passed as this argument.
|
||||
-- @param screen The screen of the wibox.
|
||||
-- @return A table of geometries of all the widgets.
|
||||
function leftright(bounds, widgets, screen)
|
||||
return linear.fixed(idx, bounds, widgets, screen)
|
||||
end
|
||||
|
||||
for _, k in ipairs(keys) do
|
||||
local v = widgets[k]
|
||||
if type(v) == "table" then
|
||||
local layout = v.layout or default
|
||||
if margins[v] then
|
||||
bounds.width = bounds.width - (margins[v].left or 0) - (margins[v].right or 0)
|
||||
bounds.height = bounds.height - (margins[v].top or 0) - (margins[v].bottom or 0)
|
||||
--- Places the widgets in a row, aligned to the right.
|
||||
-- It recognizes the following attributes in the encompassing table:
|
||||
-- <p><code>width</code>: Maximum width of the layout. (optional)<br/>
|
||||
-- <code>height</code>: Height of the layout. If set, all widgets are resized to this height. (optional)</p>
|
||||
-- @param bounds The geometry of the bounds for the layout.
|
||||
-- @param widgets The table of the widgets to layout. When layout function is placed in
|
||||
-- a table, it is the table that is passed as this argument.
|
||||
-- @param screen The screen of the wibox.
|
||||
-- @return A table of geometries of all the widgets.
|
||||
function rightleft(bounds, widgets, screen)
|
||||
local g = linear.fixed(idx, util.table.clone(bounds), widgets, screen)
|
||||
for _, w in pairs(g) do
|
||||
w.x = w.x + bounds.width - g.total.width
|
||||
end
|
||||
local g = layout(bounds, v, screen)
|
||||
if margins[v] then
|
||||
x = x + (margins[v].left or 0)
|
||||
return g
|
||||
end
|
||||
for _, v in ipairs(g) do
|
||||
v.x = v.x + x
|
||||
v.y = v.y + (margins[v] and (margins[v].top and margins[v].top or 0) or 0)
|
||||
table.insert(geometries, v)
|
||||
|
||||
--- Places the widgets in a row, aligned to the center.
|
||||
-- It recognizes the following attributes in the encompassing table:
|
||||
-- <p><code>width</code>: Maximum width of the layout. (optional)<br/>
|
||||
-- <code>height</code>: Height of the layout. If set, all widgets are resized to this height. (optional)</p>
|
||||
-- The widgets will be placed in the center of the free space that is left after all widgets
|
||||
-- preceding the table with this layout were placed. The area covered by widgets with this layout
|
||||
-- is not subtracted from the total free area (so the widgets "cover no space"). Therefore, if you
|
||||
-- put anything after a table with center layout, the widgets may overlap.<br/>
|
||||
-- @param bounds The geometry of the bounds for the layout.
|
||||
-- @param widgets The table of the widgets to layout. When layout function is placed in
|
||||
-- a table, it is the table that is passed as this argument.
|
||||
-- @param screen The screen of the wibox.
|
||||
-- @return A table of geometries of all the widgets.
|
||||
function center(bounds, widgets, screen)
|
||||
local g = linear.fixed(idx, util.table.clone(bounds), widgets, screen)
|
||||
for _, w in ipairs(g) do
|
||||
w.x = w.x + (bounds.width - g.total.width) / 2
|
||||
end
|
||||
bounds = g.free
|
||||
if margins[v] then
|
||||
x = x + g.free.x + (margins[v].right or 0)
|
||||
bounds.width = bounds.width - (margins[v].right or 0) - (margins[v].left or 0)
|
||||
-- if width is set for widgets table, make total this wide,
|
||||
-- otherwise move total.x to reflect new position
|
||||
if widgets.width then
|
||||
g.total.width = widgets.width
|
||||
else
|
||||
x = x + g.free.x
|
||||
g.total.x = g.total.x + (bounds.width - g.total.width) / 2
|
||||
end
|
||||
elseif type(v) == "widget" then
|
||||
local g
|
||||
if v.visible then
|
||||
g = v:extents(screen)
|
||||
if margins[v] then
|
||||
g.width = g.width + (margins[v].left or 0) + (margins[v].right or 0)
|
||||
g.height = g.height + (margins[v].top or 0) + (margins[v].bottom or 0)
|
||||
end
|
||||
else
|
||||
g = {
|
||||
width = 0,
|
||||
height = 0,
|
||||
}
|
||||
end
|
||||
|
||||
if v.resize and g.width > 0 and g.height > 0 then
|
||||
local ratio = g.width / g.height
|
||||
g.width = math.floor(bounds.height * ratio)
|
||||
g.height = bounds.height
|
||||
end
|
||||
|
||||
if g.width > bounds.width then
|
||||
g.width = bounds.width
|
||||
end
|
||||
g.height = bounds.height
|
||||
|
||||
if margins[v] then
|
||||
g.y = (margins[v].top or 0)
|
||||
else
|
||||
g.y = 0
|
||||
end
|
||||
|
||||
if direction == "leftright" then
|
||||
if margins[v] then
|
||||
g.x = x + (margins[v].left or 0)
|
||||
else
|
||||
g.x = x
|
||||
end
|
||||
x = x + g.width
|
||||
else
|
||||
if margins[v] then
|
||||
g.x = x + bounds.width - g.width + (margins[v].left or 0)
|
||||
else
|
||||
g.x = x + bounds.width - g.width
|
||||
end
|
||||
end
|
||||
bounds.width = bounds.width - g.width
|
||||
|
||||
table.insert(geometries, g)
|
||||
end
|
||||
end
|
||||
|
||||
geometries.free = util.table.clone(bounds)
|
||||
geometries.free.x = x
|
||||
geometries.free.y = 0
|
||||
|
||||
return geometries
|
||||
return g
|
||||
end
|
||||
|
||||
--- Places the widgets in a row, making them equally wide.
|
||||
-- It recognizes the following attributes in the encompassing table:
|
||||
-- <p><code>width</code>: Maximum width of the layout. (optional)<br/>
|
||||
-- <code>height</code>: Height of the layout. If set, all widgets are resized to this height. (optional)<br/>
|
||||
-- <code>max_size</code>: Maximum width of one item. If not set, widgets' widths are set so that they always
|
||||
-- cover all available space. (optional)<br/>
|
||||
-- <code>gap</code>: Gap between the widgets. (optional)</p>
|
||||
-- @param bounds The geometry of the bounds for the layout.
|
||||
-- @param widgets The table of the widgets to layout. When layout function is placed in
|
||||
-- a table, it is the table that is passed as this argument.
|
||||
-- @param screen The screen of the wibox.
|
||||
-- @return A table of geometries of all the widgets.
|
||||
function flex(bounds, widgets, screen)
|
||||
local geometries = {
|
||||
free = util.table.clone(bounds)
|
||||
}
|
||||
-- the flex layout always uses the complete available place, thus we return
|
||||
-- no usable free area
|
||||
geometries.free.width = 0
|
||||
|
||||
-- we are only interested in tables and widgets
|
||||
local keys = util.table.keys_filter(widgets, "table", "widget")
|
||||
local nelements = 0
|
||||
|
||||
for _, k in ipairs(keys) do
|
||||
local v = widgets[k]
|
||||
if type(v) == "table" then
|
||||
nelements = nelements + 1
|
||||
elseif type(v) == "widget" then
|
||||
local g = v:extents()
|
||||
if v.resize and g.width > 0 and g.height > 0 then
|
||||
bounds.width = bounds.width - bounds.height
|
||||
elseif g.width > 0 and g.height > 0 then
|
||||
nelements = nelements + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
nelements = (nelements == 0) and 1 or nelements
|
||||
|
||||
local x = 0
|
||||
local width = bounds.width / nelements
|
||||
|
||||
for _, k in ipairs(util.table.keys(widgets)) do
|
||||
local v = widgets[k]
|
||||
if type(v) == "table" then
|
||||
local layout = v.layout or default
|
||||
local g = layout(bounds, v, screen)
|
||||
for _, v in ipairs(g) do
|
||||
v.x = v.x + x
|
||||
table.insert(geometries, v)
|
||||
end
|
||||
bounds = g.free
|
||||
elseif type(v) == "widget" then
|
||||
local g = v:extents(screen)
|
||||
g.resize = v.resize
|
||||
|
||||
if v.resize and g.width > 0 and g.height > 0 then
|
||||
g.width = bounds.height
|
||||
g.height = bounds.height
|
||||
g.x = x
|
||||
g.y = bounds.y
|
||||
x = x + g.width
|
||||
elseif g.width > 0 and g.height > 0 then
|
||||
g.x = x
|
||||
g.y = bounds.y
|
||||
g.width = math.floor(width + 0.5)
|
||||
g.height = bounds.height
|
||||
x = x + width
|
||||
else
|
||||
g.x = 0
|
||||
g.y = 0
|
||||
g.width = 0
|
||||
g.height = 0
|
||||
end
|
||||
|
||||
table.insert(geometries, g)
|
||||
end
|
||||
end
|
||||
|
||||
return geometries
|
||||
end
|
||||
|
||||
function leftright(...)
|
||||
return horizontal("leftright", ...)
|
||||
end
|
||||
|
||||
function rightleft(...)
|
||||
return horizontal("rightleft", ...)
|
||||
return linear.flex(idx, bounds, widgets, screen)
|
||||
end
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80
|
||||
|
|
|
@ -1,23 +1,90 @@
|
|||
-------------------------------------------------
|
||||
-- @author Gregor Best <farhaven@googlemail.com>, Lukas Hrazky <lukkash@email.cz>
|
||||
-- @copyright 2009 Gregor Best, Lukas Hrazky
|
||||
-- @release @AWESOME_VERSION@
|
||||
-------------------------------------------------
|
||||
|
||||
local setmetatable = setmetatable
|
||||
local require = require
|
||||
local type = type
|
||||
|
||||
-- Widget layouts
|
||||
--- Widget layouts
|
||||
module("awful.widget.layout")
|
||||
|
||||
--- Widgets margins.
|
||||
-- <p>In this table you can set the margin you want the layout to use when
|
||||
-- positionning your widgets.
|
||||
-- For example, if you want to put 10 pixel free on left on a widget, add this:
|
||||
-- <code>
|
||||
-- the table that really contains the margins.
|
||||
local margin_table = setmetatable({}, {__mode = 'k'})
|
||||
-- empty margin table - used every time there is no margin for something
|
||||
local empty_margin = setmetatable({}, {__index = function() return 0 end})
|
||||
|
||||
--- Margins of widgets and layout tables.
|
||||
-- In this table you can set the margin you want the layout to use when positionning
|
||||
-- your widgets or tables with widgets. The key is the widget or the table to set
|
||||
-- margin for, value is either a number (setting the same margin to all four sides) or
|
||||
-- a table with one or more of the following keys:
|
||||
-- <p><code>left, right, top, bottom</code></p>
|
||||
-- For example, if you want to put a 10 pixel free space on the left of a widget, add this:
|
||||
-- <p><code>
|
||||
-- awful.widget.layout.margins[mywidget] = { left = 10 }
|
||||
-- </code>
|
||||
-- </p>
|
||||
-- </code></p>
|
||||
-- @name margins
|
||||
-- @class table
|
||||
margins = setmetatable({}, { __mode = 'k' })
|
||||
margins = setmetatable({}, {
|
||||
__index = function(t, i)
|
||||
return margin_table[i] or empty_margin
|
||||
end,
|
||||
__newindex = function(t, i, v)
|
||||
if type(v) == "number" then
|
||||
margin_table[i] = setmetatable({}, {__index = function() return v end})
|
||||
elseif type(v) == "table" then
|
||||
margin_table[i] = setmetatable(v, {__index = function() return 0 end})
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
-- A table used to index geometry properties. Returns the name with which it is indexed.
|
||||
-- It is used in horizontal layouts, where it returns "x" when indexed by "x", etc.
|
||||
regular_index = setmetatable({}, {__index = function(t, i) return i end})
|
||||
|
||||
-- A table used to index geometry properties. Returns the name opposite to the one
|
||||
-- with which it is indexed. It is used in vertical layouts, where it returns "y"
|
||||
-- when indexed by "x", etc.
|
||||
switched_index = {
|
||||
x = "y",
|
||||
y = "x",
|
||||
width = "height",
|
||||
height = "width",
|
||||
top = "left",
|
||||
bottom = "right",
|
||||
left = "top",
|
||||
right = "bottom"
|
||||
}
|
||||
|
||||
|
||||
-- set the default layout function
|
||||
local horizontal = require("awful.widget.layout.horizontal")
|
||||
default = horizontal.leftright
|
||||
|
||||
require("awful.widget.layout.horizontal")
|
||||
require("awful.widget.layout.vertical")
|
||||
require("awful.widget.layout.default")
|
||||
|
||||
--- Places the widgets in a grid.
|
||||
-- It recognizes the following attributes in the encompassing table:
|
||||
-- <p><code>cell_width</code>: Either a table containing column widths or
|
||||
-- a number (width for all columns). In case its a number, <code>col_count</code> is mandatory.<br/>
|
||||
-- <code>cell_height</code>: Either a table containing row heights or
|
||||
-- a number (height for all rows). In case its a number, <code>row_count</code> is mandatory.<br/>
|
||||
-- <code>col_count</code>: In case <code>cell_width</code> is a number, sets the number of columns.<br/>
|
||||
-- <code>row_count</code>: In case <code>cell_height</code> is a number, sets the number of rows.<br/>
|
||||
-- <code>col_gap</code>: Gap between columns in pixels. (optional)<br/>
|
||||
-- <code>row_gap</code>: Gap between rows in pixels. (optional)<br/>
|
||||
-- <code>resize</code>: Boolean indicating whether widgets should be resized to the size of the cell.
|
||||
-- (optional)</p>
|
||||
-- @param bounds The geometry of the bounds for the layout.
|
||||
-- @param widgets The table of the widgets to layout. When layout function is placed in
|
||||
-- a table, it is the table that is passed as this argument.
|
||||
-- @param screen The screen of the wibox.
|
||||
-- @return A table of geometries of all the widgets.
|
||||
-- @name grid
|
||||
-- @class function
|
||||
grid = require("awful.widget.layout.grid")
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80
|
||||
|
|
|
@ -0,0 +1,290 @@
|
|||
-------------------------------------------------
|
||||
-- @author Gregor Best <farhaven@googlemail.com>, Lukas Hrazky <lukkash@email.cz>
|
||||
-- @copyright 2009 Gregor Best, Lukas Hrazky
|
||||
-- @release @AWESOME_VERSION@
|
||||
-------------------------------------------------
|
||||
|
||||
local setmetatable = setmetatable
|
||||
local require = require
|
||||
local ipairs = ipairs
|
||||
local type = type
|
||||
local insert = table.insert
|
||||
local min = math.min
|
||||
local max = math.max
|
||||
local floor = math.floor
|
||||
local util = require("awful.util")
|
||||
local clone = util.table.clone
|
||||
|
||||
local margins = awful.widget.layout.margins
|
||||
local layout = awful.widget.layout
|
||||
|
||||
local linear_common = {}
|
||||
|
||||
-- Calculates geometries for fixed layouts.
|
||||
-- This generic function is used for all layouts exept flex.
|
||||
-- It is written for horizontal layouts, but by using a special index 'idx' to access
|
||||
-- all the geometry attributes, it is used for vertical layouts too. In that case, 'idx'
|
||||
-- returns 'y' for 'x', 'width' for 'height', etc.
|
||||
-- @param idx An index table that defines whether horzintal or vertical arrangement will be
|
||||
-- calculated. See regular_index and switched_index tables.
|
||||
-- @param bounds The geometry of the bounds for the layout.
|
||||
-- @param widgets The table of the widgets to layout, can be nested.
|
||||
-- @param screen The screen of the wibox.
|
||||
-- @return A table of geometries of all the widgets.
|
||||
function linear_common.fixed(idx, bounds, widgets, screen)
|
||||
-- set bounds width and height to what is preset in the widgets table (if there is something)
|
||||
-- we don't need to use idx here since we're treating both directions the same way
|
||||
if widgets.width and widgets.width < bounds.width then
|
||||
bounds.width = widgets.width
|
||||
end
|
||||
if widgets.height and widgets.height < bounds.height then
|
||||
bounds.height = widgets.height
|
||||
end
|
||||
|
||||
-- the table for the geometries which will be returned
|
||||
-- we clone the bounds to the 'total' attribute. bounds are used to keep the free space
|
||||
-- throughout this function. at the end, 'total' is modified to represent the space taken
|
||||
-- by all widgets
|
||||
local geometries = {total = clone(bounds)}
|
||||
-- the height of the heighest widget, will be the height of the total space taken
|
||||
local maxh = 0
|
||||
|
||||
for _, v in ipairs(widgets) do
|
||||
if type(v) == "table" or type(v) == "widget" then
|
||||
local m = margins[v]
|
||||
-- we can shrink the bounds by the horizontal margins right now, they affect our free
|
||||
-- space directly (vertical don't, they are different for every item in 'widgets')
|
||||
bounds[idx.width] = bounds[idx.width] - m[idx.left] - m[idx.right]
|
||||
bounds[idx.x] = bounds[idx.x] + m[idx.left]
|
||||
|
||||
if type(v) == "table" then
|
||||
-- create new bounds for the table and shrink it by the vertical margins
|
||||
local t_bounds = clone(bounds)
|
||||
t_bounds[idx.height] = t_bounds[idx.height] - m[idx.top] - m[idx.bottom]
|
||||
t_bounds[idx.y] = t_bounds[idx.y] + m[idx.top]
|
||||
|
||||
-- backup the width and height of the table so we can restore it
|
||||
local t_width = v.width
|
||||
local t_height = v.height
|
||||
|
||||
-- if the 'widgets' table has height set and the table itself doesn't, we set it
|
||||
v[idx.height] = v[idx.height] or widgets[idx.height]
|
||||
|
||||
-- call the layout function recursively on this table
|
||||
local layout = v.layout or layout.default
|
||||
local g = layout(t_bounds, v, screen)
|
||||
|
||||
-- restore the table's original width and height
|
||||
v.width = t_width
|
||||
v.height = t_height
|
||||
|
||||
-- subtract the space taken by the table from our bounds - only if the taken space
|
||||
-- is either on the left or on the right of our bounds (not in the middle)
|
||||
if g.total[idx.x] == bounds[idx.x] or
|
||||
g.total[idx.x] + g.total[idx.width] == bounds[idx.x] + bounds[idx.width] then
|
||||
bounds[idx.width] = bounds[idx.width] - g.total[idx.width]
|
||||
end
|
||||
-- we only move the 'x' coord if the taken space is on the left side of our bounds
|
||||
if g.total[idx.x] == bounds[idx.x] then
|
||||
bounds[idx.x] = bounds[idx.x] + g.total[idx.width] + m[idx.right]
|
||||
end
|
||||
|
||||
-- update the maximum height with this new table
|
||||
maxh = max(maxh, g.total[idx.height] + m[idx.top] + m[idx.bottom])
|
||||
|
||||
-- insert all geometries from the table to our geometries
|
||||
for _, w in ipairs(g) do
|
||||
insert(geometries, w)
|
||||
end
|
||||
|
||||
elseif type(v) == "widget" then
|
||||
local g = {x = 0, y = 0, width = 0, height = 0}
|
||||
|
||||
if v.visible then
|
||||
-- get the geometry of the widget
|
||||
g = v:extents(screen)
|
||||
|
||||
-- resize to fit the height available if requested
|
||||
if v.resize and g.width > 0 and g.height > 0 then
|
||||
local ratio = g[idx.width] / g[idx.height]
|
||||
g[idx.width] = floor(bounds[idx.height] * ratio)
|
||||
g[idx.height] = bounds[idx.height]
|
||||
end
|
||||
|
||||
-- set the coords, apply the top margin
|
||||
g[idx.y] = bounds[idx.y] + m[idx.top]
|
||||
g[idx.x] = bounds[idx.x]
|
||||
|
||||
-- limit the width of the widget to what's available
|
||||
g[idx.width] = min(g[idx.width], bounds[idx.width])
|
||||
|
||||
-- if the 'widgets' table has height set, we set it to the widget
|
||||
g[idx.height] = widgets[idx.height] and
|
||||
(widgets[idx.height] - m[idx.top] - m[idx.bottom]) or g[idx.height]
|
||||
|
||||
-- limit the height of the widget to what's available
|
||||
g[idx.height] = min(g[idx.height], bounds[idx.height] - m[idx.top] - m[idx.bottom])
|
||||
|
||||
-- subtract the space taken by the widget from our bounds
|
||||
bounds[idx.width] = bounds[idx.width] - g[idx.width]
|
||||
-- move bounds right by the widget's width
|
||||
bounds[idx.x] = bounds[idx.x] + g[idx.width] + m[idx.right]
|
||||
|
||||
-- update the maximum height with height of this widget
|
||||
maxh = max(maxh, g[idx.height] + m[idx.top] + m[idx.bottom])
|
||||
end
|
||||
|
||||
insert(geometries, g)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- calculate the total space taken by the widgets
|
||||
geometries.total[idx.width] = geometries.total[idx.width] - bounds[idx.width]
|
||||
geometries.total[idx.height] = maxh
|
||||
-- if the bounds are on the left of what was empty in the beginning of this function,
|
||||
-- we move the total to the right. this, however, most probably happened cos of rightleft layout
|
||||
-- inside 'widgets', and only if there was nothing aligned to the left
|
||||
if geometries.total[idx.x] == bounds[idx.x] then
|
||||
geometries.total[idx.x] = geometries.total[idx.x] + bounds[idx.width]
|
||||
end
|
||||
|
||||
return geometries
|
||||
end
|
||||
|
||||
|
||||
-- Calculates geometries for flex layouts.
|
||||
-- This generic function is used for all flex layouts.
|
||||
-- It is written for horizontal layouts, but by using special index 'idx' to access
|
||||
-- all the geometry attributes, it is used for vertical layouts too. In that case, 'idx'
|
||||
-- returns 'y' for 'x', 'width' for 'height', etc.
|
||||
-- @param idx An index table that defines whether horzintal or vertical arrangement will be
|
||||
-- calculated. See regular_index and switched_index tables.
|
||||
-- @param bounds The geometry of the bounds for the layout.
|
||||
-- @param widgets The table of the widgets to layout, can be nested.
|
||||
-- @param screen The screen of the wibox.
|
||||
-- @return A table of geometries of all the widgets.
|
||||
function linear_common.flex(idx, bounds, widgets, screen)
|
||||
-- set bounds width and height to what is preset in the widgets table (if there is something)
|
||||
-- we don't need to use idx here since we're treating both directions the same way
|
||||
if widgets.width and widgets.width < bounds.width then
|
||||
bounds.width = widgets.width
|
||||
end
|
||||
if widgets.height and widgets.height < bounds.height then
|
||||
bounds.height = widgets.height
|
||||
end
|
||||
|
||||
-- the table for the geometries which will be returned
|
||||
-- we clone the bounds to the 'total' attribute. bounds are used to keep the free space
|
||||
-- throughout this function. at the end, 'total' is modified to represent the space taken
|
||||
-- by all widgets
|
||||
local geometries = {total = clone(bounds)}
|
||||
-- the height of the heighest widget, will be the height of the total space taken
|
||||
local maxh = 0
|
||||
-- the number of widgets/tables in 'widgets' argument
|
||||
local n = 0
|
||||
-- get the gap or set to 0
|
||||
local gap = widgets.gap or 0
|
||||
|
||||
-- count the widgets/tables
|
||||
for _, v in ipairs(widgets) do
|
||||
if type(v) == "table" or (type(v) == "widget" and v.visible) then
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- calculate the width. these vars keep the floating numbers, while bounds keep the
|
||||
-- rounded ones and are set from these on each iteration to ensure proper rounding
|
||||
local width = (n > 0) and ((widgets[idx.width] or bounds[idx.width]) - gap * (n - 1)) / n or 0
|
||||
local x = bounds[idx.x]
|
||||
|
||||
-- if max_size is set and it is lower than the calculated width, use it
|
||||
width = (widgets.max_size and widgets.max_size < width) and widgets.max_size or width
|
||||
|
||||
for _, v in ipairs(widgets) do
|
||||
-- someone give me freaking continue
|
||||
if type(v) == "widget" and not v.visible then
|
||||
insert(geometries, {x = 0, y = 0, width = 0, height = 0})
|
||||
elseif type(v) == "widget" or type(v) == "table" then
|
||||
-- do the floating magic, calculate real_width which will be set to the geometries
|
||||
x = x + width
|
||||
local real_width = floor(x - bounds[idx.x] + 0.5)
|
||||
|
||||
local m = margins[v]
|
||||
|
||||
if type(v) == "table" then
|
||||
-- create new bounds for the table and shrink it by the margins
|
||||
local t_bounds = {}
|
||||
t_bounds[idx.x] = bounds[idx.x] + m[idx.left]
|
||||
t_bounds[idx.y] = bounds[idx.y] + m[idx.top]
|
||||
t_bounds[idx.width] = real_width - m[idx.left] - m[idx.right]
|
||||
t_bounds[idx.height] = bounds[idx.height] - m[idx.top] - m[idx.bottom]
|
||||
|
||||
-- backup the width and height of the table so we can restore it
|
||||
local t_width = v.width
|
||||
local t_height = v.height
|
||||
|
||||
-- set the table's width so it can flex what's inside it
|
||||
v[idx.width] = real_width
|
||||
|
||||
-- if the 'widgets' table has height set and the table itself doesn't, we set it
|
||||
v[idx.height] = v[idx.height] or widgets[idx.height]
|
||||
|
||||
-- call the layout function recursively on this table
|
||||
local layout = v.layout or layout.default
|
||||
local g = layout(t_bounds, v, screen)
|
||||
|
||||
-- restore the table's original width and height
|
||||
v.width = t_width
|
||||
v.height = t_height
|
||||
|
||||
-- update the maximum height with this new table
|
||||
maxh = max(maxh, g.total[idx.height] + m[idx.top] + m[idx.bottom])
|
||||
|
||||
for _, v in ipairs(g) do
|
||||
insert(geometries, v)
|
||||
end
|
||||
elseif type(v) == "widget" then
|
||||
g = v:extents(screen)
|
||||
|
||||
-- resize to fit the width available if requested
|
||||
if v.resize and g.width > 0 and g.height > 0 then
|
||||
local ratio = g[idx.height] / g[idx.width]
|
||||
g[idx.height] = real_width * ratio
|
||||
end
|
||||
|
||||
-- set the widget geometry
|
||||
g[idx.x] = bounds[idx.x] + m[idx.left]
|
||||
g[idx.width] = real_width - m[idx.left] - m[idx.right]
|
||||
g[idx.y] = bounds[idx.y] + m[idx.top]
|
||||
|
||||
-- if the 'widgets' table has height set, we set it to the widget
|
||||
g[idx.height] = widgets[idx.height] and
|
||||
(widgets[idx.height] - m[idx.top] - m[idx.bottom]) or g[idx.height]
|
||||
|
||||
-- limit the height of the widget to what's available
|
||||
g[idx.height] = min(g[idx.height], bounds[idx.height] - m[idx.top] - m[idx.bottom])
|
||||
|
||||
-- update the maximum height with height of this widget
|
||||
maxh = max(maxh, g[idx.height] + m[idx.top] + m[idx.bottom])
|
||||
|
||||
insert(geometries, g)
|
||||
end
|
||||
|
||||
-- update the bounds (move it to the right by the widget's width)
|
||||
bounds[idx.width] = bounds[idx.width] - real_width - gap
|
||||
x = x + gap
|
||||
bounds[idx.x] = floor(x + 0.5)
|
||||
end
|
||||
end
|
||||
|
||||
-- we have total already from the cloned bounds, just set the height to what we got
|
||||
geometries.total[idx.width] = geometries.total[idx.width] - bounds[idx.width]
|
||||
geometries.total[idx.height] = maxh
|
||||
|
||||
return geometries
|
||||
end
|
||||
|
||||
return linear_common
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80
|
|
@ -1,101 +1,98 @@
|
|||
-------------------------------------------------
|
||||
-- @author Gregor Best <farhaven@googlemail.com>
|
||||
-- @copyright 2009 Gregor Best
|
||||
-- @author Gregor Best <farhaven@googlemail.com>, Lukas Hrazky <lukkash@email.cz>
|
||||
-- @copyright 2009 Gregor Best, Lukas Hrazky
|
||||
-- @release @AWESOME_VERSION@
|
||||
-------------------------------------------------
|
||||
|
||||
-- Grab environment
|
||||
local require = require
|
||||
local ipairs = ipairs
|
||||
local type = type
|
||||
local table = table
|
||||
local math = math
|
||||
local pairs = pairs
|
||||
local util = require("awful.util")
|
||||
local default = require("awful.widget.layout.default")
|
||||
local layout = require("awful.widget.layout")
|
||||
local linear = require("awful.widget.layout.linear_common")
|
||||
|
||||
--- Vertical widget layout
|
||||
--- Vertical widget layouts
|
||||
module("awful.widget.layout.vertical")
|
||||
|
||||
function flex(bounds, widgets, screen)
|
||||
local geometries = {
|
||||
free = util.table.clone(bounds)
|
||||
}
|
||||
-- index used for vertical layouts
|
||||
local idx = layout.switched_index
|
||||
|
||||
local y = 0
|
||||
--- Places the widgets in a column, aligned to the top.
|
||||
-- It recognizes the following attributes in the encompassing table:
|
||||
-- <p><code>width</code>: Maximum width of the layout. If set, all widgets are resized to this width.
|
||||
-- (optional)<br/>
|
||||
-- <code>height</code>: Height of the layout. (optional)</p>
|
||||
-- @param bounds The geometry of the bounds for the layout.
|
||||
-- @param widgets The table of the widgets to layout. When layout function is placed in
|
||||
-- a table, it is the table that is passed as this argument.
|
||||
-- @param screen The screen of the wibox.
|
||||
-- @return A table of geometries of all the widgets.
|
||||
function topdown(bounds, widgets, screen)
|
||||
return linear.fixed(idx, bounds, widgets, screen)
|
||||
end
|
||||
|
||||
-- we are only interested in tables and widgets
|
||||
local keys = util.table.keys_filter(widgets, "table", "widget")
|
||||
local nelements = 0
|
||||
for _, k in ipairs(keys) do
|
||||
local v = widgets[k]
|
||||
if type(v) == "table" then
|
||||
nelements = nelements + 1
|
||||
else
|
||||
local e = v:extents()
|
||||
if v.visible and e.width > 0 and e.height > 0 then
|
||||
nelements = nelements + 1
|
||||
--- Places the widgets in a column, aligned to the bottom.
|
||||
-- It recognizes the following attributes in the encompassing table:
|
||||
-- <p><code>width</code>: Maximum width of the layout. If set, all widgets are resized to this width.
|
||||
-- (optional)<br/>
|
||||
-- <code>height</code>: Height of the layout. (optional)</p>
|
||||
-- @param bounds The geometry of the bounds for the layout.
|
||||
-- @param widgets The table of the widgets to layout. When layout function is placed in
|
||||
-- a table, it is the table that is passed as this argument.
|
||||
-- @param screen The screen of the wibox.
|
||||
-- @return A table of geometries of all the widgets.
|
||||
function bottomup(bounds, widgets, screen)
|
||||
local g = linear.fixed(idx, util.table.clone(bounds), widgets, screen)
|
||||
for _, w in pairs(g) do
|
||||
w.y = w.y + bounds.height - g.total.height
|
||||
end
|
||||
return g
|
||||
end
|
||||
end
|
||||
if nelements == 0 then return geometries end
|
||||
local height = math.floor(bounds.height / nelements)
|
||||
|
||||
for _, k in ipairs(keys) do
|
||||
local v = widgets[k]
|
||||
if type(v) == "table" then
|
||||
local layout = v.layout or default
|
||||
-- we need to modify the height a bit because vertical layouts always span the
|
||||
-- whole height
|
||||
nbounds = util.table.clone(bounds)
|
||||
nbounds.height = height
|
||||
local g = layout(nbounds, v, screen)
|
||||
--- Places the widgets in a column, aligned to the center.
|
||||
-- It recognizes the following attributes in the encompassing table:
|
||||
-- <p><code>width</code>: Maximum width of the layout. If set, all widgets are resized to this width.
|
||||
-- (optional)<br/>
|
||||
-- <code>height</code>: Height of the layout. (optional)</p>
|
||||
-- The widgets will be placed in the center of the free space that is left after all widgets
|
||||
-- preceding the table with this layout were placed. The area covered by widgets with this layout
|
||||
-- is not subtracted from the total free area (so the widgets "cover no space"). Therefore, if you
|
||||
-- put anything after a table with center layout, the widgets may overlap.
|
||||
-- @param bounds The geometry of the bounds for the layout.
|
||||
-- @param widgets The table of the widgets to layout. When layout function is placed in
|
||||
-- a table, it is the table that is passed as this argument.
|
||||
-- @param screen The screen of the wibox.
|
||||
-- @return A table of geometries of all the widgets.
|
||||
function center(bounds, widgets, screen)
|
||||
local g = linear.fixed(idx, util.table.clone(bounds), widgets, screen)
|
||||
for _, w in ipairs(g) do
|
||||
w.y = w.y + y
|
||||
table.insert(geometries, w)
|
||||
w.y = w.y + (bounds.height - g.total.height) / 2
|
||||
end
|
||||
y = y + height
|
||||
elseif type(v) == "widget" then
|
||||
local g
|
||||
if v.visible then
|
||||
g = v:extents(screen)
|
||||
-- if height is set for widgets table, make total this wide,
|
||||
-- otherwise move total.y to reflect new position
|
||||
if widgets.height then
|
||||
g.total.height = widgets.height
|
||||
else
|
||||
g = {
|
||||
["width"] = 0,
|
||||
["height"] = 0
|
||||
}
|
||||
g.total.y = g.total.y + (bounds.height - g.total.height) / 2
|
||||
end
|
||||
return g
|
||||
end
|
||||
|
||||
g.ratio = 1
|
||||
if g.height > 0 and g.width > 0 then
|
||||
g.ratio = g.width / g.height
|
||||
end
|
||||
g.height = height
|
||||
if v.resize then
|
||||
g.width = g.height * g.ratio
|
||||
end
|
||||
g.width = math.min(g.width, bounds.width)
|
||||
geometries.free.x = math.max(geometries.free.x, g.width)
|
||||
|
||||
g.x = 0
|
||||
g.y = y
|
||||
y = y + g.height
|
||||
bounds.height = bounds.height - g.height
|
||||
|
||||
table.insert(geometries, g)
|
||||
end
|
||||
--- Places the widgets in a column, making them equally high.
|
||||
-- It recognizes the following attributes in the encompassing table:
|
||||
-- <p><code>width</code>: Maximum width of the layout. If set, all widgets are resized to this width.
|
||||
-- (optional)<br/>
|
||||
-- <code>height</code>: Height of the layout. (optional)<br/>
|
||||
-- <code>max_size</code>: Maximum height of one item. If not set, widgets' heights are set so that they always
|
||||
-- cover all available space. (optional)<br/>
|
||||
-- <code>gap</code>: Gap between the widgets. (optional)</p>
|
||||
-- @param bounds The geometry of the bounds for the layout.
|
||||
-- @param widgets The table of the widgets to layout. When layout function is placed in
|
||||
-- a table, it is the table that is passed as this argument.
|
||||
-- @param screen The screen of the wibox.
|
||||
-- @return A table of geometries of all the widgets.
|
||||
function flex(bounds, widgets, screen)
|
||||
return linear.flex(idx, bounds, widgets, screen)
|
||||
end
|
||||
|
||||
local maxw = 0
|
||||
local maxx = 0
|
||||
for _, v in ipairs(geometries) do
|
||||
if v.width > maxw then maxw = v.width end
|
||||
if v.x > maxx then maxx = v.x end
|
||||
end
|
||||
|
||||
geometries.free.width = geometries.free.width - maxw
|
||||
geometries.free.x = geometries.free.x + maxw
|
||||
|
||||
geometries.free.height = nelements * height
|
||||
geometries.free.y = 0
|
||||
|
||||
return geometries
|
||||
end
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80
|
||||
|
|
Loading…
Reference in New Issue