wibox.layout: Add a `manual` layout.

A layout with manual widget positions. It is still useful as it is
manager by the hierarchy and widget position can be functions.
This commit is contained in:
Emmanuel Lepage Vallee 2016-10-09 01:29:35 -04:00
parent 9e01c92ea2
commit 5d46d47ef7
2 changed files with 218 additions and 0 deletions

View File

@ -12,6 +12,7 @@ return setmetatable({
align = require("wibox.layout.align");
flex = require("wibox.layout.flex");
rotate = require("wibox.layout.rotate");
manual = require("wibox.layout.manual");
margin = require("wibox.layout.margin");
mirror = require("wibox.layout.mirror");
constraint = require("wibox.layout.constraint");

217
lib/wibox/layout/manual.lua Normal file
View File

@ -0,0 +1,217 @@
---------------------------------------------------------------------------
-- A layout with widgets added at specific positions.
--
-- Use cases include desktop icons, complex custom composed widgets, a floating
-- client layout and fine grained control over the output.
--
--@DOC_wibox_layout_defaults_manual_EXAMPLE@
-- @author Emmanuel Lepage Vallee
-- @copyright 2016 Emmanuel Lepage Vallee
-- @classmod wibox.layout.manual
---------------------------------------------------------------------------
local gtable = require("gears.table")
local base = require("wibox.widget.base")
local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
local manual_layout = {}
--- Add some widgets to the given stack layout
-- @param layout The layout you are modifying.
-- @tparam widget ... Widgets that should be added
-- @name add
-- @class function
--- Remove a widget from the layout
-- @tparam index The widget index to remove
-- @treturn boolean index If the operation is successful
-- @name remove
-- @class function
--- 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
-- @name insert
-- @class function
--- 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
-- @name remove_widgets
-- @class function
function manual_layout:fit(_, width, height)
return width, height
end
local function geometry(self, new)
self._new_geo = new
return self._new_geo or self
end
function manual_layout:layout(context, width, height)
local res = {}
for k, v in ipairs(self._private.widgets) do
local pt = self._private.pos[k] or {x=0,y=0}
local w, h = base.fit_widget(self, context, v, width, height)
-- Make sure the signature is compatible with `awful.placement`. `Wibox`,
-- doesn't depend on `awful`, but it is still nice not to have to code
-- geometry functions again and again.
if type(pt) == "function" or (getmetatable(pt) or {}).__call then
local geo = {
x = 0,
y = 0,
width = w,
height = h,
geometry = geometry,
}
pt = pt(geo, {
parent = {
x=0, y=0, width = width, height = height, geometry = geometry
}
})
-- Trick to ensure compatibility with `awful.placement`
gtable.crush(pt, geo._new_geo or {})
end
assert(pt.x)
assert(pt.y)
table.insert(res, base.place_widget_at(
v, pt.x, pt.y, pt.width or w, pt.height or h
))
end
return res
end
function manual_layout:add(...)
local wdgs = {...}
local old_count = #self._private.widgets
gtable.merge(self._private.widgets, {...})
-- Add the points
for k, v in ipairs(wdgs) do
if v.point then
self._private.pos[old_count+k] = v.point
end
end
self:emit_signal("widget::layout_changed")
end
--- Add a widget at a specific point.
--
-- The point can either be a function or a table. The table follow the generic
-- geometry format used elsewhere in Awesome.
--
-- * *x*: The horizontal position.
-- * *y*: The vertical position.
-- * *width*: The width.
-- * *height*: The height.
--
-- If a function is used, it follows the same prototype as `awful.placement`
-- functions.
--
-- * *geo*:
-- * *x*: The horizontal position (always 0).
-- * *y*: The vertical position (always 0).
-- * *width*: The width.
-- * *height*: The height.
-- * *geometry*: A function to get or set the geometry (for compatibility).
-- The function is compatible with the `awful.placement` prototype.
-- * *args*:
-- * *parent* The layout own geometry
-- * *x*: The horizontal position (always 0).
-- * *y*: The vertical position (always 0).
-- * *width*: The width.
-- * *height*: The height.
-- * *geometry*: A function to get or set the geometry (for compatibility)
-- The function is compatible with the `awful.placement` prototype.
--
--@DOC_wibox_layout_manual_add_at_EXAMPLE@
-- @tparam widget widget The widget.
-- @tparam table|function point Either an `{x=x,y=y}` table or a function
-- returning the new geometry.
function manual_layout:add_at(widget, point)
assert(not widget.point, "2 points are specified, only one is supported")
-- Check is the point function is valid
if type(point) == "function" or (getmetatable(point) or {}).__call then
local fake_geo = {x=0,y=0,width=1,height=1,geometry=geometry}
local pt = point(fake_geo, {
parent = {
x=0, y=0, width = 10, height = 10, geometry = geometry
}
})
assert(pt and pt.x and pt.y, "The point function doesn't seem to be valid")
end
self._private.pos[#self._private.widgets+1] = point
self:add(widget)
end
--- Move a widget (by index).
-- @tparam number index The widget index.
-- @tparam table|function point A new point value.
-- @see add_at
function manual_layout:move(index, point)
assert(self._private.pos[index])
self._private.pos[index] = point
self:emit_signal( "widget::layout_changed" )
end
--- Move a widget.
--
--@DOC_wibox_layout_manual_move_widget_EXAMPLE@
-- @tparam widget widget The widget.
-- @tparam table|function point A new point value.
-- @see add_at
function manual_layout:move_widget(widget, point)
local idx, l = self:index(widget, false)
if idx then
l:move(idx, point)
end
end
function manual_layout:get_children()
return self._private.widgets
end
function manual_layout:set_children(children)
self:reset()
self:add(unpack(children))
end
function manual_layout:reset()
self._private.widgets = {}
self._private.pos = {}
self:emit_signal( "widget::layout_changed" )
end
--- Create a manual layout.
-- @tparam table ... Widgets to add to the layout.
local function new_manual(...)
local ret = base.make_widget(nil, nil, {enable_properties = true})
gtable.crush(ret, manual_layout, true)
ret._private.widgets = {}
ret._private.pos = {}
ret:add(...)
return ret
end
--@DOC_widget_COMMON@
--@DOC_object_COMMON@
return setmetatable(manual_layout, {__call=function(_,...) return new_manual(...) end})