Merge pull request #486 from Elv13/new_widget_syntax

widget: Add a new container declaration syntax
This commit is contained in:
Emmanuel Lepage Vallée 2016-02-10 03:58:31 -05:00
commit cd584fda55
19 changed files with 644 additions and 164 deletions

View File

@ -34,6 +34,11 @@ set(AWE_DOC_FILES
${AWE_DOC_DIR}/02-contributing.md
${SOURCE_DIR}/LICENSE)
set(AWE_DOC_IMAGES
${AWE_DOC_DIR}/images/widgetlayout1.png
${AWE_DOC_DIR}/images/widgetlayout2.png
)
set(AWE_SRCS
${BUILD_DIR}/awesome.c
${BUILD_DIR}/banning.c
@ -259,6 +264,9 @@ if(GENERATE_DOC)
file(GLOB_RECURSE AWE_LUA_FILES ${BUILD_DIR}/lib/*.lua)
file(GLOB_RECURSE AWE_MD_FILES ${AWE_DOC_DIR}/*.md)
# Copy the images to the build directory
file(COPY ${SOURCE_DIR}/docs/images DESTINATION ${AWE_DOC_DIR})
# Run ldoc and make it fail if any warnings are generated. The
# redirection-magic swaps stdout and stderr and awk exits with a non-zero
# status if it sees at least one line of input.
@ -342,6 +350,7 @@ install(DIRECTORY ${SOURCE_DIR}/themes DESTINATION ${AWESOME_DATA_PATH}
install(DIRECTORY ${BUILD_DIR}/themes DESTINATION ${AWESOME_DATA_PATH}
PATTERN "*.lua")
install(FILES ${AWE_DOC_FILES} DESTINATION ${AWESOME_DOC_PATH})
install(FILES ${AWE_DOC_IMAGES} DESTINATION ${AWESOME_DOC_PATH}/images)
install(FILES "awesome.desktop" DESTINATION ${AWESOME_XSESSION_PATH})
if(GENERATE_DOC)
install(DIRECTORY ${BUILD_DIR}/doc DESTINATION ${AWESOME_DOC_PATH})

View File

@ -195,27 +195,24 @@ for s = 1, screen.count() do
-- Create the wibox
mywibox[s] = awful.wibox({ position = "top", screen = s })
-- Widgets that are aligned to the left
local left_layout = wibox.layout.fixed.horizontal()
left_layout:add(mylauncher)
left_layout:add(mytaglist[s])
left_layout:add(mypromptbox[s])
-- Widgets that are aligned to the right
local right_layout = wibox.layout.fixed.horizontal()
right_layout:add(mykeyboardlayout)
if s == 1 then right_layout:add(wibox.widget.systray()) end
right_layout:add(mytextclock)
right_layout:add(mylayoutbox[s])
-- Now bring it all together (with the tasklist in the middle)
local layout = wibox.layout.align.horizontal()
layout:set_left(left_layout)
layout:set_middle(mytasklist[s])
layout:set_right(right_layout)
mywibox[s]:set_widget(layout)
-- Add widgets to the wibox
mywibox[s]:setup {
layout = wibox.layout.align.horizontal,
{ -- Left widgets
layout = wibox.layout.fixed.horizontal,
mylauncher,
mytaglist[s],
mypromptbox[s],
},
mytasklist[s], -- Middle widget
{ -- Right widgets
layout = wibox.layout.fixed.horizontal,
mykeyboardlayout,
s == 1 and wibox.widget.systray(),
mytextclock,
mylayoutbox[s],
},
}
end
-- }}}
@ -494,33 +491,30 @@ client.connect_signal("manage", function (c)
end)
)
-- Widgets that are aligned to the left
local left_layout = wibox.layout.fixed.horizontal()
left_layout:add(awful.titlebar.widget.iconwidget(c))
left_layout:buttons(buttons)
-- Widgets that are aligned to the right
local right_layout = wibox.layout.fixed.horizontal()
right_layout:add(awful.titlebar.widget.floatingbutton(c))
right_layout:add(awful.titlebar.widget.maximizedbutton(c))
right_layout:add(awful.titlebar.widget.stickybutton(c))
right_layout:add(awful.titlebar.widget.ontopbutton(c))
right_layout:add(awful.titlebar.widget.closebutton(c))
-- The title goes in the middle
local middle_layout = wibox.layout.flex.horizontal()
local title = awful.titlebar.widget.titlewidget(c)
title:set_align("center")
middle_layout:add(title)
middle_layout:buttons(buttons)
-- Now bring it all together
local layout = wibox.layout.align.horizontal()
layout:set_left(left_layout)
layout:set_right(right_layout)
layout:set_middle(middle_layout)
awful.titlebar(c):set_widget(layout)
awful.titlebar(c) : setup {
{ -- Left
awful.titlebar.widget.iconwidget(c),
buttons = buttons,
layout = wibox.layout.fixed.horizontal
},
{ -- Middle
{ -- Title
align = "center",
widget = awful.titlebar.widget.titlewidget(c)
},
buttons = buttons,
layout = wibox.layout.flex.horizontal
},
{ -- Right
awful.titlebar.widget.floatingbutton (c),
awful.titlebar.widget.maximizedbutton(c),
awful.titlebar.widget.stickybutton (c),
awful.titlebar.widget.ontopbutton (c),
awful.titlebar.widget.closebutton (c),
layout = wibox.layout.fixed.horizontal()
},
layout = wibox.layout.align.horizontal
}
end
end)

View File

@ -0,0 +1,263 @@
# The declarative layout system
This system provide an alternative to the system used in Awesome 3.5 and is
inspired by the one once used by Awesome 3.2-3.4 and Qt QML system.
## A simple layout
* Display `my_first_widget` only on screen one
* Add a background color to `my_third_widget`
* Dispose in a `wibox.layout.fixed.horizontal` layout
Code:
mywibox[s] : setup {
s == 1 and my_first_widget, -- Only display on screen 1
my_second_widget,
{ -- Add a background color/pattern for my_third_widget
my_third_widget,
bg = beautiful.bg_focus,
widget = wibox.widget.background,
},
layout = wibox.layout.fixed.horizontal,
}
In this example `s == 1` is an inline expression. In the default `rc.lua`,
there is an `s` variable represent to define the current screen. Any lua
logic expression can be used as long as it return a valid widget, or a
declarative layout, or `nil`.
## Define widgets inline and place them
* Create a `wibox.widget.textbox` with various properties
* Force the textbox size using `wibox.layout.constraint`
* Add a margin around another textbox
* Add a `wibox.widget.background` (for visualization)
Code:
mywibox[s] : setup {
{
-- Force the textbox to always be 300 pixel long
{
{
markup = "<b>Hello World!</b>",
align = "center",
widget = wibox.widget.textbox
},
bg = "#ff0000",
widget = wibox.widget.background,
},
width = 300,
strategy = "min",
layout = wibox.layout.constraint
},
{
-- Add a border around the background
{
{
markup = "Foobar",
widget = wibox.widget.textbox
},
bg = "#0000ff",
widget = wibox.widget.background
},
left = 10,
right = 10,
top = 1,
bottom = 2,
layout = wibox.layout.margin
},
layout = wibox.layout.fixed.horizontal,
}
Result:
![Example2 screenshot](../../docs/images/widgetlayout1.png)
## Use an `wibox.layout.align` layout
The `wibox.layout.align` is a little different. While most layouts will
ignore any `nil` lines, the `align` layout rely on them so `left`, `middle`
and `right` can be defined
Code:
mywibox[s] : setup {
my_textbox1, -- Left
nil, -- Nothing in the middle
my_textbox2, -- Right
layout = wibox.layout.fixed.horizontal,
}
## Define new widgets
New trivial widgets can be created directly in the layout declaration. Here
is a simple circle widget:
Code:
mywibox[s] : setup {
fit = function(self, context, width, height)
return height, height -- A square taking the full height
end,
draw = function(self, context, cr, width, height)
cr:set_source_rgb(1, 0, 0) -- Red
cr:arc(height/2, height/2, height/2, 0, math.pi*2)
cr:fill()
end,
layout = wibox.widget.base.make_widget,
}
Result:
![Example4 screenshot](../../docs/images/widgetlayout2.png)
For more information about how to draw widgets, refer to the `Cairo` api:
* [Path](http://cairographics.org/manual/cairo-Paths.html)
* [Context](http://cairographics.org/manual/cairo-cairo-t.html)
* [Pattern](http://cairographics.org/manual/cairo-cairo-pattern-t.html)
* [transformation](http://cairographics.org/manual/cairo-Transformations.html)
* [Operator](http://cairographics.org/operators/)
* [Pango text](https://developer.gnome.org/pango/stable/)
## Externally defined widgets and layouts
This is useful when the widget is provided by an external module or when it
requires complex manipulations which would make the declaration unreadable.
Code:
local tb = wibox.widget.textbox()
tb:set_markup("Hello world! ")
-- Repeat "tb" 3 times
mywibox[s] : setup {
tb,
tb,
tb,
layout = wibox.layout.fixed.horizontal,
}
## Accessing widgets
For each widget or container, it is possible to add an `identifier` attribute
so the widget can be accessed later.
Widgets defined using `setup` can be access by 3 means:
* Avoid the issue by using externally created widgets
* Use `my_wibox.my_first_widget.my_second_widget` style access
* Use JavaScript like `my_wibox:get_children_by_id("my_second_widget")[1]`
The first method mixes the imperative and declarative syntax, but makes the code
less readable. The second is a little verbose and only works if every node in
the chain have a valid identifier. The last one doesn't require long paths,
but it is not easy to get a specific instance if multiple widgets have the
same identifier.
WARNING: The widget identifier must not use reseved name. This include all
method names, existing widget attributes, `layout` and `widget`. Names should
also respect the lua variable name policies (case sensitive, alphanumeric and
underscore characters and non-numeric first character)
Code:
mywibox[s] : setup {
{
id = "second",
widget = wibox.widget.textbox
},
{
id = "third",
widget = wibox.widget.textbox
},
id = "first",
layout = wibox.layout.fixed.horizontal,
}
mywibox[s].first.second:set_markup("changed!")
mywibox[s]:get_children_by_id("third")[1]:set_markup("Also changed!")
## Extending the system
This system is very flexible. Each section attribute (the entries with string
keys) is directly linked to the layout or widget API. When setting the
imaginary `myproperty`, it will first check if `set_myproperty` exist. If it
doesn't, it will check if there is a `myproperty` method. Finally, it will
just set the `mywidget.myproperty` directly in case it is used later or
catched by a lua `metatable` (operator overload).
Code:
-- "Monkeypatch" a new function to wibox.widget.textbox to add vicious
-- extension support
function wibox.widget.textbox:vicious(args)
local f = unpack or table.unpack -- Lua 5.1 compat
vicious.register(w, f(args))
end
mywibox[s] : setup {
{
vicious = {vicious.widgets.cpu, "CPU: $1%", 3},
widget = wibox.widget.textbox
},
layout = wibox.layout.fixed.horizontal,
}
In this example, the system is extended to that the popular
[Vicious](http://awesome.naquadah.org/wiki/Vicious) extension module can be
used directly in the layout declaration. This example will update the textbox
every 3 seconds to show the CPU usage.
## Handling sections
The system allows sections to be defined externally, then composed into
the final layout declaration. Here is an example re-using one of the above
example:
Code:
local circle = {
fit = function(self, context, width, height)
return height, height -- A square taking the full height
end,
draw = function(self, context, cr, width, height)
cr:set_source_rgb(1, 0, 0) -- Red
cr:arc(height/2, height/2, height/2, 0, math.pi*2)
cr:fill()
end,
layout = wibox.widget.base.make_widget,
}
-- Define a layout with the imperative syntax
local l = wibox.widget.align()
-- 3 circle
mywibox[s] : setup {
circle,
circle,
circle,
l,
layout = wibox.layout.align.horizontal
}
-- This can be done instead
local three_circle = {layout = wibox.layout.align.horizontal}
for i=1, 3 do
table.insert(three_circle, circle)
end
mywibox[s] : setup (three_circle)

113
docs/04-new-widgets.md Normal file
View File

@ -0,0 +1,113 @@
# Creating new widget
All widgets have to be generated via this function so that
the needed signals are added and mouse input handling is set up.
The returned widget will have a `:buttons` member function that can be used to
register a set of mouse button events with the widget.
To implement your own widget, you can implement some member functions on a
freshly-created widget. Note that all of these functions should be deterministic
in the sense that they will show the same behavior if they are repeatedly called
with the same arguments (same width and height). If your widget is updated and
needs to change, suitable signals have to be emitted. This will be explained
later.
The first callback is `:fit`. This function is called to select the size of your
widget. The arguments to this function is the available space and it should
return its desired size. Note that this function only provides a hint which is
not necessarily followed. The widget must also be able to draw itself at
different sizes than the one requested.
function widget:fit(context, width, height)
-- Find the maximum square available
local m = math.min(width, height)
return m, m
end
The next callback is `:draw`. As the name suggests, this function is called to
draw the widget. The arguments to this widget are the context that the widget is
drawn in, the cairo context on which it should be drawn and the widget's size.
The cairo context is set up in such a way that the widget as its top-left corner
at (0, 0) and its bottom-right corner at (width, height). In other words, no
special transformation needs to be done. Note that during this callback a
suitable clip will already be applied to the cairo context so that this callback
will not be able to draw outside of the area that was registered for the widget
by the layout that placed this widget. You should not call `cr:reset_clip()`, as
redraws will not be handled correctly in this case.
function widget:draw(context, cr, width, height)
cr:move_to(0, 0)
cr:line_to(width, height)
cr:move_to(0, height)
cr:line_to(width, 0)
cr:stroke()
end
There are two signals configured for a widget. When the result that `:fit` would
return changes, the `widget::layout_changed` signal has to be emitted. If this
actually causes layout changes, the affected areas will be redrawn. The other
signal is `widget::redraw_needed`. This signal signals that `:draw` has to be
called to redraw the widget, but it is safe to assume that `:fit` does still
return the same values as before. If in doubt, you can emit both signals to be
safe.
If your widget only needs to draw something to the screen, the above is all that
is needed. The following callbacks can be used when implementing layouts which
place other widgets on the screen.
The `:layout` callback is used to figure out which other widgets should be drawn
relative to this widget. Note that it is allowed to place widgets outside of the
extents of your own widget, for example at a negative position or at twice the
size of this widget. Use this mechanism if your widget needs to draw outside of
its own extents. If the result of this callback changes,
`widget::layout_changed` has to be emitted. You can use `:fit_widget` to call
the `:fit` callback of other widgets. Never call `:fit` directly! For example,
if you want to place another widget `child` inside of your widget, you can do it
like this:
-- For readability
local base = wibox.widget.base
function widget:layout(width, height)
local result = {}
table.insert(result, base.place_widget_at(child, width/2, 0, width/2, height)
return result
end
Finally, if you want to influence how children are drawn, there are four
callbacks available that all get similar arguments:
function widget:before_draw_children(context, cr, width, height)
function widget:after_draw_children(context, cr, width, height)
function widget:before_draw_child(context, index, child, cr, width, height)
function widget:after_draw_child(context, index, child, cr, width, height)
All of these are called with the same arguments as the `:draw()` method. Please
note that a larger clip will be active during these callbacks that also contains
the area of all children. These callbacks can be used to influence the way in
which children are drawn, but they should not cause the drawing to cover a
different area. As an example, these functions can be used to draw children
translucently:
function widget:before_draw_children(context, cr, width, height)
cr:push_group()
end
function widget:after_draw_children(context, cr, width, height)
cr:pop_group_to_source()
cr:paint_with_alpha(0.5)
end
In pseudo-code, the call sequence for the drawing callbacks during a redraw
looks like this:
widget:draw(context, cr, width, height)
widget:before_draw_children(context, cr, width, height)
for child do
widget:before_draw_child(context, cr, child_index, child, width, height)
cr:save()
-- Draw child and all of its children recursively, taking into account the
-- position and size given to base.place_widget_at() in :layout().
cr:restore()
widget:after_draw_child(context, cr, child_index, child, width, height)
end
widget:after_draw_children(context, cr, width, height)

View File

@ -19,6 +19,8 @@ topics={
'00-authors.md',
'01-readme.md',
'02-contributing.md',
'03-declarative-layout.md',
'04-new-widgets.md',
}
-- Setup @client to be an alias for "@tparam client.object"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

View File

@ -26,6 +26,12 @@ local titlebar = {
widget = {}
}
--- Set a declarative widget hierarchy description.
-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
-- @param args An array containing the widgets disposition
-- @name setup
-- @class function
--- Show tooltips when hover on titlebar buttons (defaults to 'true')
titlebar.enable_tooltip = true
@ -108,6 +114,9 @@ local function new(c, args)
-- Make sure the titlebar has the right colors applied
bars[position].update_colors()
-- Handle declarative/recursive widget container
ret.setup = base.widget.setup
return ret
end

View File

@ -21,6 +21,7 @@ local sort = require("gears.sort")
local beautiful = require("beautiful")
local surface = require("gears.surface")
local cairo = require("lgi").cairo
local base = require("wibox.widget.base")
--- This provides widget box windows. Every wibox can also be used as if it were
-- a drawin. All drawin functions and properties are also available on wiboxes!
@ -36,6 +37,13 @@ function wibox:set_widget(widget)
self._drawable:set_widget(widget)
end
--- Set a declarative widget hierarchy description.
-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
-- @param args An array containing the widgets disposition
-- @name setup
-- @class function
wibox.setup = base.widget.setup
--- Set the background of the wibox
-- @param c The background to use. This must either be a cairo pattern object,
-- nil or a string that gears.color() understands.

View File

@ -171,6 +171,17 @@ function align:get_children()
return util.from_sparse {self.first, self.second, self.third}
end
--- Replace the layout children
-- This layout only accept three children, all others will be ignored
-- @tparam table children A table composed of valid widgets
function align:set_children(children)
if not children then return self:reset() end
self.first = children[1]
self.second = children[2]
self.third = children[3]
self:emit_signal("widget::layout_changed")
end
--- Fit the align layout into the given space. The align layout will
-- ask for the sum of the sizes of its sub-widgets in its direction
-- and the largest sized sub widget in the other direction.
@ -243,6 +254,9 @@ local function get_layout(dir, first, second, third)
ret:set_second(second)
ret:set_third(third)
-- An align layout allow set_children to have empty entries
ret.allow_empty_widget = true
return ret
end

View File

@ -50,6 +50,14 @@ function constraint:get_children()
return {self.widget}
end
--- Replace the layout children
-- This layout only accept one children, all others will be ignored
-- @tparam table children A table composed of valid widgets
function constraint:set_children(children)
self.widget = children and children[1]
self:emit_signal("widget::layout_changed")
end
--- Set the strategy to use for the constraining. Valid values are 'max',
-- 'min' or 'exact'. Throws an error on invalid values.
function constraint:set_strategy(val)

View File

@ -110,6 +110,14 @@ function fixed:get_children()
return self.widgets
end
--- Replace the layout children
-- @tparam table children A table composed of valid widgets
function fixed:set_children(children)
if not children then return self:reset() end
self.widgets = children
self:emit_signal("widget::layout_changed")
end
--- Replace the first instance of `widget` in the layout with `widget2`
-- @param widget The widget to replace
-- @param widget2 The widget to replace `widget` with

View File

@ -31,6 +31,11 @@ local flex = {}
-- @name get_children
-- @class function
--- Replace the layout children
-- @tparam table children A table composed of valid widgets
-- @name set_children
-- @class function
--- Add some widgets to the given fixed layout
-- @param layout The layout you are modifying.
-- @tparam widget ... Widgets that should be added (must at least be one)

View File

@ -78,6 +78,14 @@ function margin:get_children()
return {self.widget}
end
--- Replace the layout children
-- This layout only accept one children, all others will be ignored
-- @tparam table children A table composed of valid widgets
function margin:set_children(children)
self.widget = children and children[1]
self:emit_signal("widget::layout_changed")
end
--- Set all the margins to val.
function margin:set_margins(val)
self.left = val

View File

@ -64,6 +64,14 @@ function mirror:get_children()
return {self.widget}
end
--- Replace the layout children
-- This layout only accept one children, all others will be ignored
-- @tparam table children A table composed of valid widgets
function mirror:set_children(children)
self.widget = children and children[1]
self:emit_signal("widget::layout_changed")
end
--- Reset this layout. The widget will be removed and the axes reset.
function mirror:reset()
self.horizontal = false

View File

@ -72,6 +72,14 @@ function rotate:get_children()
return {self.widget}
end
--- Replace the layout children
-- This layout only accept one children, all others will be ignored
-- @tparam table children A table composed of valid widgets
function rotate:set_children(children)
self.widget = children and children[1]
self:emit_signal("widget::layout_changed")
end
--- Reset this layout. The widget will be removed and the rotation reset.
function rotate:reset()
self.direction = nil

View File

@ -267,6 +267,14 @@ function scroll:get_children()
return {self.widget}
end
--- Replace the layout children
-- This layout only accept one children, all others will be ignored
-- @tparam table children A table composed of valid widgets
function scroll:set_children(children)
self.widget = children and children[1]
self:emit_signal("widget::layout_changed")
end
--- Specify the expand mode that is used for extra space.
-- @tparam boolean expand If true, the widget is expanded to include the extra
-- space. If false, the extra space is simply left empty.

View File

@ -101,6 +101,14 @@ function background:get_children()
return {self.widget}
end
--- Replace the layout children
-- This layout only accept one children, all others will be ignored
-- @tparam table children A table composed of valid widgets
function background:set_children(children)
self.widget = children and children[1]
self:emit_signal("widget::layout_changed")
end
--- Set the background to use
function background:set_bg(bg)
if bg then

View File

@ -8,6 +8,7 @@
local object = require("gears.object")
local cache = require("gears.cache")
local matrix = require("gears.matrix")
local util = require("awful.util")
local setmetatable = setmetatable
local pairs = pairs
local type = type
@ -80,6 +81,14 @@ function base.widget:get_children()
return {}
end
--- Replace the layout children
-- The default implementation does nothing, this must be re-implemented by
-- all layout and container widgets.
-- @tparam table children A table composed of valid widgets
function base.widget:set_children(children)
-- Nothing on purpose
end
-- It could have been merged into `get_all_children`, but it's not necessary
local function digg_children(ret, tlw)
for k, w in ipairs(tlw:get_children()) do
@ -321,126 +330,121 @@ function base.place_widget_at(widget, x, y, width, height)
return base.place_widget_via_matrix(widget, matrix.create_translate(x, y), width, height)
end
--[[--
Create a new widget. All widgets have to be generated via this function so that
the needed signals are added and mouse input handling is set up.
The returned widget will have a :buttons member function that can be used to
register a set of mouse button events with the widget.
To implement your own widget, you can implement some member functions on a
freshly-created widget. Note that all of these functions should be deterministic
in the sense that they will show the same behavior if they are repeatedly called
with the same arguments (same width and height). If your widget is updated and
needs to change, suitable signals have to be emitted. This will be explained
later.
The first callback is :fit. This function is called to select the size of your
widget. The arguments to this function is the available space and it should
return its desired size. Note that this function only provides a hint which is
not necessarily followed. The widget must also be able to draw itself at
different sizes than the one requested.
function widget:fit(context, width, height)
-- Find the maximum square available
local m = math.min(width, height)
return m, m
-- Read the table, separate attributes from widgets
local function parse_table(t, leave_empty)
local keys= {}
local max = 0
local attributes, widgets = {}, {}
for k,v in pairs(t) do
if type(k) == "number" then
-- As `ipairs` doesn't always work on sparse tables, update the
-- maximum
if k > max then
max = k
end
The next callback is :draw. As the name suggests, this function is called to
draw the widget. The arguments to this widget are the context that the widget is
drawn in, the cairo context on which it should be drawn and the widget's size.
The cairo context is set up in such a way that the widget as its top-left corner
at (0, 0) and its bottom-right corner at (width, height). In other words, no
special transformation needs to be done. Note that during this callback a
suitable clip will already be applied to the cairo context so that this callback
will not be able to draw outside of the area that was registered for the widget
by the layout that placed this widget. You should not call `cr:reset_clip()`, as
redraws will not be handled correctly in this case.
function widget:draw(context, cr, width, height)
cr:move_to(0, 0)
cr:line_to(width, height)
cr:move_to(0, height)
cr:line_to(width, 0)
cr:stroke()
widgets[k] = v
else
attributes[k] = v
end
end
There are two signals configured for a widget. When the result that :fit would
return changes, the `widget::layout_changed` signal has to be emitted. If this
actually causes layout changes, the affected areas will be redrawn. The other
signal is `widget::redraw_needed`. This signal signals that :draw has to be
called to redraw the widget, but it is safe to assume that :fit does still
return the same values as before. If in doubt, you can emit both signals to be
safe.
If your widget only needs to draw something to the screen, the above is all that
is needed. The following callbacks can be used when implementing layouts which
place other widgets on the screen.
The :layout callback is used to figure out which other widgets should be drawn
relative to this widget. Note that it is allowed to place widgets outside of the
extents of your own widget, for example at a negative position or at twice the
size of this widget. Use this mechanism if your widget needs to draw outside of
its own extents. If the result of this callback changes,
`widget::layout_changed` has to be emitted. You can use @{fit_widget} to call
the `:fit` callback of other widgets. Never call `:fit` directly! For example,
if you want to place another widget `child` inside of your widget, you can do it
like this:
-- For readability
local base = wibox.widget.base
function widget:layout(width, height)
local result = {}
table.insert(result, base.place_widget_at(child, width/2, 0, width/2, height)
return result
-- Pack the sparse table if the container doesn't support sparse tables
if not leave_empty then
widgets = util.table.from_sparse(widgets)
max = #widgets
end
Finally, if you want to influence how children are drawn, there are four
callbacks available that all get similar arguments:
function widget:before_draw_children(context, cr, width, height)
function widget:after_draw_children(context, cr, width, height)
function widget:before_draw_child(context, index, child, cr, width, height)
function widget:after_draw_child(context, index, child, cr, width, height)
All of these are called with the same arguments as the :draw() method. Please
note that a larger clip will be active during these callbacks that also contains
the area of all children. These callbacks can be used to influence the way in
which children are drawn, but they should not cause the drawing to cover a
different area. As an example, these functions can be used to draw children
translucently:
function widget:before_draw_children(context, cr, width, height)
cr:push_group()
end
function widget:after_draw_children(context, cr, width, height)
cr:pop_group_to_source()
cr:paint_with_alpha(0.5)
return max, attributes, widgets
end
In pseudo-code, the call sequence for the drawing callbacks during a redraw
looks like this:
-- Recursively build a container from a declarative table
local function drill(ids, content)
if not content then return end
widget:draw(context, cr, width, height)
widget:before_draw_children(context, cr, width, height)
for child do
widget:before_draw_child(context, cr, child_index, child, width, height)
cr:save()
-- Draw child and all of its children recursively, taking into account the
-- position and size given to base.place_widget_at() in :layout().
cr:restore()
widget:after_draw_child(context, cr, child_index, child, width, height)
-- Alias `widget` to `layout` as they are handled the same way
content.layout = content.layout or content.widget
-- Make sure the layout is not indexed on a function
local layout = type(content.layout) == "function" and content.layout() or content.layout
-- Create layouts based on metatable __call
local l = layout.is_widget and layout or layout()
-- Get the number of children widgets (including nil widgets)
local max, attributes, widgets = parse_table(content, l.allow_empty_widget)
-- Get the optional identifier to create a virtual widget tree to place
-- in an "access table" to be able to retrieve the widget
local id = attributes.id
-- Clear the internal attributes
attributes.id, attributes.layout, attributes.widget = nil, nil, nil
for k = 1, max do
-- ipairs cannot be used on sparse tables
local v, id2, e = widgets[k], id, nil
if v then
-- It is another declarative container, parse it
if not v.is_widget then
e, id2 = drill(ids, v)
widgets[k] = e
end
widget:after_draw_children(context, cr, width, height)
@param proxy If this is set, the returned widget will be a proxy for this
widget. It will be equivalent to this widget. This means it
looks the same on the screen.
@tparam[opt] string widget_name Name of the widget. If not set, it will be
set automatically via `gears.object.modulename`.
@see fit_widget
--]]--
-- Place the widget in the access table
if id2 then
l [id2] = e
ids[id2] = ids[id2] or {}
table.insert(ids[id2], e)
end
end
end
-- Replace all children (if any) with the new ones
l:set_children(widgets)
-- Set layouts attributes
for attr, val in pairs(attributes) do
if l["set_"..attr] then
l["set_"..attr](l, val)
elseif type(l[attr]) == "function" then
l[attr](l, val)
else
l[attr] = val
end
end
return l, id
end
-- Only available when the declarative system is used
local function get_children_by_id(self, name)
return self._by_id[name] or {}
end
--- Set a declarative widget hierarchy description.
-- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
-- @param args An array containing the widgets disposition
function base.widget:setup(args)
local f,ids = self.set_widget or self.add or self.set_first,{}
local w, id = drill(ids, args)
f(self,w)
if id then
-- Avoid being dropped by wibox metatable -> drawin
rawset(self, id, w)
end
rawset(self, "_by_id", ids)
rawset(self, "get_children_by_id", get_children_by_id)
end
--- Create an empty widget skeleton
-- See [Creating new widgets](../documentation/04-new-widget.md.html)
-- @param proxy If this is set, the returned widget will be a proxy for this
-- widget. It will be equivalent to this widget. This means it
-- looks the same on the screen.
-- @tparam[opt] string widget_name Name of the widget. If not set, it will be
-- set automatically via `gears.object.modulename`.
-- @see fit_widget
function base.make_widget(proxy, widget_name)
local ret = object()
@ -470,6 +474,9 @@ function base.make_widget(proxy, widget_name)
-- Widget is fully opaque
ret.opacity = 1
-- Differentiate tables from widgets
ret.is_widget = true
-- Size is not restricted/forced
ret._forced_width = nil
ret._forced_height = nil