From 5d46d47ef79571daf963c4857ecfd58d061142df Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 9 Oct 2016 01:29:35 -0400 Subject: [PATCH] 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. --- lib/wibox/layout/init.lua | 1 + lib/wibox/layout/manual.lua | 217 ++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 lib/wibox/layout/manual.lua diff --git a/lib/wibox/layout/init.lua b/lib/wibox/layout/init.lua index d07e6d3b..681f2155 100644 --- a/lib/wibox/layout/init.lua +++ b/lib/wibox/layout/init.lua @@ -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"); diff --git a/lib/wibox/layout/manual.lua b/lib/wibox/layout/manual.lua new file mode 100644 index 00000000..4fae6b59 --- /dev/null +++ b/lib/wibox/layout/manual.lua @@ -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})