From c09582dca3a72f512197988fb421a6ceca78e001 Mon Sep 17 00:00:00 2001 From: Lukas Hrazky Date: Sat, 24 Oct 2009 14:50:37 +0200 Subject: [PATCH] widget layouts: rewrite Signed-off-by: Lukas Hrazky Signed-off-by: Julien Danjou --- lib/awful/widget/layout/default.lua.in | 58 ---- lib/awful/widget/layout/grid.lua.in | 203 +++++++++++++ lib/awful/widget/layout/horizontal.lua.in | 251 +++++----------- lib/awful/widget/layout/init.lua.in | 89 +++++- lib/awful/widget/layout/linear_common.lua.in | 290 +++++++++++++++++++ lib/awful/widget/layout/vertical.lua.in | 177 ++++++----- 6 files changed, 737 insertions(+), 331 deletions(-) delete mode 100644 lib/awful/widget/layout/default.lua.in create mode 100644 lib/awful/widget/layout/grid.lua.in create mode 100644 lib/awful/widget/layout/linear_common.lua.in diff --git a/lib/awful/widget/layout/default.lua.in b/lib/awful/widget/layout/default.lua.in deleted file mode 100644 index 7c73d451..00000000 --- a/lib/awful/widget/layout/default.lua.in +++ /dev/null @@ -1,58 +0,0 @@ -------------------------------------------------- --- @author Gregor Best --- @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 }) diff --git a/lib/awful/widget/layout/grid.lua.in b/lib/awful/widget/layout/grid.lua.in new file mode 100644 index 00000000..ede33760 --- /dev/null +++ b/lib/awful/widget/layout/grid.lua.in @@ -0,0 +1,203 @@ +------------------------------------------------- +-- @author Lukas Hrazky +-- @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 diff --git a/lib/awful/widget/layout/horizontal.lua.in b/lib/awful/widget/layout/horizontal.lua.in index 0914fab4..ed22806a 100644 --- a/lib/awful/widget/layout/horizontal.lua.in +++ b/lib/awful/widget/layout/horizontal.lua.in @@ -1,188 +1,95 @@ ------------------------------------------------- --- @author Gregor Best --- @copyright 2009 Gregor Best +-- @author Gregor Best , Lukas Hrazky +-- @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") - - 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) - end - local g = layout(bounds, v, screen) - if margins[v] then - x = x + (margins[v].left or 0) - 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) - 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) - else - x = x + g.free.x - 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 +--- Places the widgets in a row, aligned to the left. +-- This is the default layout. +-- It recognizes the following attributes in the encompassing table: +--

width: Maximum width of the layout. (optional)
+-- height: Height of the layout. If set, all widgets are resized to this height. (optional)

+-- @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 +--- Places the widgets in a row, aligned to the right. +-- It recognizes the following attributes in the encompassing table: +--

width: Maximum width of the layout. (optional)
+-- height: Height of the layout. If set, all widgets are resized to this height. (optional)

+-- @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 + return g +end + +--- Places the widgets in a row, aligned to the center. +-- It recognizes the following attributes in the encompassing table: +--

width: Maximum width of the layout. (optional)
+-- height: Height of the layout. If set, all widgets are resized to this height. (optional)

+-- 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.x = w.x + (bounds.width - g.total.width) / 2 + end + -- 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 + g.total.x = g.total.x + (bounds.width - g.total.width) / 2 + end + return g +end + +--- Places the widgets in a row, making them equally wide. +-- It recognizes the following attributes in the encompassing table: +--

width: Maximum width of the layout. (optional)
+-- height: Height of the layout. If set, all widgets are resized to this height. (optional)
+-- max_size: Maximum width of one item. If not set, widgets' widths are set so that they always +-- cover all available space. (optional)
+-- gap: Gap between the widgets. (optional)

+-- @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 diff --git a/lib/awful/widget/layout/init.lua.in b/lib/awful/widget/layout/init.lua.in index f4535ee4..d0b3ce91 100644 --- a/lib/awful/widget/layout/init.lua.in +++ b/lib/awful/widget/layout/init.lua.in @@ -1,23 +1,90 @@ +------------------------------------------------- +-- @author Gregor Best , Lukas Hrazky +-- @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. ---

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: --- +-- 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: +--

left, right, top, bottom

+-- For example, if you want to put a 10 pixel free space on the left of a widget, add this: +--

-- awful.widget.layout.margins[mywidget] = { left = 10 } --- ---

+--

-- @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: +--

cell_width: Either a table containing column widths or +-- a number (width for all columns). In case its a number, col_count is mandatory.
+-- cell_height: Either a table containing row heights or +-- a number (height for all rows). In case its a number, row_count is mandatory.
+-- col_count: In case cell_width is a number, sets the number of columns.
+-- row_count: In case cell_height is a number, sets the number of rows.
+-- col_gap: Gap between columns in pixels. (optional)
+-- row_gap: Gap between rows in pixels. (optional)
+-- resize: Boolean indicating whether widgets should be resized to the size of the cell. +-- (optional)

+-- @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 diff --git a/lib/awful/widget/layout/linear_common.lua.in b/lib/awful/widget/layout/linear_common.lua.in new file mode 100644 index 00000000..9c6f168a --- /dev/null +++ b/lib/awful/widget/layout/linear_common.lua.in @@ -0,0 +1,290 @@ +------------------------------------------------- +-- @author Gregor Best , Lukas Hrazky +-- @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 diff --git a/lib/awful/widget/layout/vertical.lua.in b/lib/awful/widget/layout/vertical.lua.in index 5a176848..0e51bcc6 100644 --- a/lib/awful/widget/layout/vertical.lua.in +++ b/lib/awful/widget/layout/vertical.lua.in @@ -1,101 +1,98 @@ ------------------------------------------------- --- @author Gregor Best --- @copyright 2009 Gregor Best +-- @author Gregor Best , Lukas Hrazky +-- @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 - - -- 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 - end - 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) - for _, w in ipairs(g) do - w.y = w.y + y - table.insert(geometries, w) - end - y = y + height - elseif type(v) == "widget" then - local g - if v.visible then - g = v:extents(screen) - else - g = { - ["width"] = 0, - ["height"] = 0 - } - 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 - 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 +--- Places the widgets in a column, aligned to the top. +-- It recognizes the following attributes in the encompassing table: +--

width: Maximum width of the layout. If set, all widgets are resized to this width. +-- (optional)
+-- height: Height of the layout. (optional)

+-- @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 + +--- Places the widgets in a column, aligned to the bottom. +-- It recognizes the following attributes in the encompassing table: +--

width: Maximum width of the layout. If set, all widgets are resized to this width. +-- (optional)
+-- height: Height of the layout. (optional)

+-- @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 + +--- Places the widgets in a column, aligned to the center. +-- It recognizes the following attributes in the encompassing table: +--

width: Maximum width of the layout. If set, all widgets are resized to this width. +-- (optional)
+-- height: Height of the layout. (optional)

+-- 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 + (bounds.height - g.total.height) / 2 + end + -- 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.total.y = g.total.y + (bounds.height - g.total.height) / 2 + end + return g +end + +--- Places the widgets in a column, making them equally high. +-- It recognizes the following attributes in the encompassing table: +--

width: Maximum width of the layout. If set, all widgets are resized to this width. +-- (optional)
+-- height: Height of the layout. (optional)
+-- max_size: Maximum height of one item. If not set, widgets' heights are set so that they always +-- cover all available space. (optional)
+-- gap: Gap between the widgets. (optional)

+-- @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 + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80