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 ${AWE_DOC_DIR}/02-contributing.md
${SOURCE_DIR}/LICENSE) ${SOURCE_DIR}/LICENSE)
set(AWE_DOC_IMAGES
${AWE_DOC_DIR}/images/widgetlayout1.png
${AWE_DOC_DIR}/images/widgetlayout2.png
)
set(AWE_SRCS set(AWE_SRCS
${BUILD_DIR}/awesome.c ${BUILD_DIR}/awesome.c
${BUILD_DIR}/banning.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_LUA_FILES ${BUILD_DIR}/lib/*.lua)
file(GLOB_RECURSE AWE_MD_FILES ${AWE_DOC_DIR}/*.md) 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 # 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 # redirection-magic swaps stdout and stderr and awk exits with a non-zero
# status if it sees at least one line of input. # status if it sees at least one line of input.
@ -341,7 +349,8 @@ install(DIRECTORY ${SOURCE_DIR}/themes DESTINATION ${AWESOME_DATA_PATH}
PATTERN "*.lua" EXCLUDE) PATTERN "*.lua" EXCLUDE)
install(DIRECTORY ${BUILD_DIR}/themes DESTINATION ${AWESOME_DATA_PATH} install(DIRECTORY ${BUILD_DIR}/themes DESTINATION ${AWESOME_DATA_PATH}
PATTERN "*.lua") PATTERN "*.lua")
install(FILES ${AWE_DOC_FILES} DESTINATION ${AWESOME_DOC_PATH}) 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}) install(FILES "awesome.desktop" DESTINATION ${AWESOME_XSESSION_PATH})
if(GENERATE_DOC) if(GENERATE_DOC)
install(DIRECTORY ${BUILD_DIR}/doc DESTINATION ${AWESOME_DOC_PATH}) install(DIRECTORY ${BUILD_DIR}/doc DESTINATION ${AWESOME_DOC_PATH})

View File

@ -195,27 +195,24 @@ for s = 1, screen.count() do
-- Create the wibox -- Create the wibox
mywibox[s] = awful.wibox({ position = "top", screen = s }) mywibox[s] = awful.wibox({ position = "top", screen = s })
-- Widgets that are aligned to the left -- Add widgets to the wibox
local left_layout = wibox.layout.fixed.horizontal() mywibox[s]:setup {
left_layout:add(mylauncher) layout = wibox.layout.align.horizontal,
left_layout:add(mytaglist[s]) { -- Left widgets
left_layout:add(mypromptbox[s]) layout = wibox.layout.fixed.horizontal,
mylauncher,
-- Widgets that are aligned to the right mytaglist[s],
local right_layout = wibox.layout.fixed.horizontal() mypromptbox[s],
right_layout:add(mykeyboardlayout) },
mytasklist[s], -- Middle widget
if s == 1 then right_layout:add(wibox.widget.systray()) end { -- Right widgets
right_layout:add(mytextclock) layout = wibox.layout.fixed.horizontal,
right_layout:add(mylayoutbox[s]) mykeyboardlayout,
s == 1 and wibox.widget.systray(),
-- Now bring it all together (with the tasklist in the middle) mytextclock,
local layout = wibox.layout.align.horizontal() mylayoutbox[s],
layout:set_left(left_layout) },
layout:set_middle(mytasklist[s]) }
layout:set_right(right_layout)
mywibox[s]:set_widget(layout)
end end
-- }}} -- }}}
@ -482,45 +479,42 @@ client.connect_signal("manage", function (c)
if titlebars_enabled and (c.type == "normal" or c.type == "dialog") then if titlebars_enabled and (c.type == "normal" or c.type == "dialog") then
-- buttons for the titlebar -- buttons for the titlebar
local buttons = awful.util.table.join( local buttons = awful.util.table.join(
awful.button({ }, 1, function() awful.button({ }, 1, function()
client.focus = c client.focus = c
c:raise() c:raise()
awful.mouse.client.move(c) awful.mouse.client.move(c)
end), end),
awful.button({ }, 3, function() awful.button({ }, 3, function()
client.focus = c client.focus = c
c:raise() c:raise()
awful.mouse.client.resize(c) awful.mouse.client.resize(c)
end) end)
) )
-- Widgets that are aligned to the left awful.titlebar(c) : setup {
local left_layout = wibox.layout.fixed.horizontal() { -- Left
left_layout:add(awful.titlebar.widget.iconwidget(c)) awful.titlebar.widget.iconwidget(c),
left_layout:buttons(buttons) buttons = buttons,
layout = wibox.layout.fixed.horizontal
-- Widgets that are aligned to the right },
local right_layout = wibox.layout.fixed.horizontal() { -- Middle
right_layout:add(awful.titlebar.widget.floatingbutton(c)) { -- Title
right_layout:add(awful.titlebar.widget.maximizedbutton(c)) align = "center",
right_layout:add(awful.titlebar.widget.stickybutton(c)) widget = awful.titlebar.widget.titlewidget(c)
right_layout:add(awful.titlebar.widget.ontopbutton(c)) },
right_layout:add(awful.titlebar.widget.closebutton(c)) buttons = buttons,
layout = wibox.layout.flex.horizontal
-- The title goes in the middle },
local middle_layout = wibox.layout.flex.horizontal() { -- Right
local title = awful.titlebar.widget.titlewidget(c) awful.titlebar.widget.floatingbutton (c),
title:set_align("center") awful.titlebar.widget.maximizedbutton(c),
middle_layout:add(title) awful.titlebar.widget.stickybutton (c),
middle_layout:buttons(buttons) awful.titlebar.widget.ontopbutton (c),
awful.titlebar.widget.closebutton (c),
-- Now bring it all together layout = wibox.layout.fixed.horizontal()
local layout = wibox.layout.align.horizontal() },
layout:set_left(left_layout) layout = wibox.layout.align.horizontal
layout:set_right(right_layout) }
layout:set_middle(middle_layout)
awful.titlebar(c):set_widget(layout)
end end
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', '00-authors.md',
'01-readme.md', '01-readme.md',
'02-contributing.md', '02-contributing.md',
'03-declarative-layout.md',
'04-new-widgets.md',
} }
-- Setup @client to be an alias for "@tparam client.object" -- 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 = {} 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') --- Show tooltips when hover on titlebar buttons (defaults to 'true')
titlebar.enable_tooltip = true titlebar.enable_tooltip = true
@ -108,6 +114,9 @@ local function new(c, args)
-- Make sure the titlebar has the right colors applied -- Make sure the titlebar has the right colors applied
bars[position].update_colors() bars[position].update_colors()
-- Handle declarative/recursive widget container
ret.setup = base.widget.setup
return ret return ret
end end

View File

@ -21,6 +21,7 @@ local sort = require("gears.sort")
local beautiful = require("beautiful") local beautiful = require("beautiful")
local surface = require("gears.surface") local surface = require("gears.surface")
local cairo = require("lgi").cairo 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 --- 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! -- 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) self._drawable:set_widget(widget)
end 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 --- Set the background of the wibox
-- @param c The background to use. This must either be a cairo pattern object, -- @param c The background to use. This must either be a cairo pattern object,
-- nil or a string that gears.color() understands. -- 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} return util.from_sparse {self.first, self.second, self.third}
end 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 --- 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 -- ask for the sum of the sizes of its sub-widgets in its direction
-- and the largest sized sub widget in the other 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_second(second)
ret:set_third(third) ret:set_third(third)
-- An align layout allow set_children to have empty entries
ret.allow_empty_widget = true
return ret return ret
end end

View File

@ -50,6 +50,14 @@ function constraint:get_children()
return {self.widget} return {self.widget}
end 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', --- Set the strategy to use for the constraining. Valid values are 'max',
-- 'min' or 'exact'. Throws an error on invalid values. -- 'min' or 'exact'. Throws an error on invalid values.
function constraint:set_strategy(val) function constraint:set_strategy(val)

View File

@ -110,6 +110,14 @@ function fixed:get_children()
return self.widgets return self.widgets
end 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` --- Replace the first instance of `widget` in the layout with `widget2`
-- @param widget The widget to replace -- @param widget The widget to replace
-- @param widget2 The widget to replace `widget` with -- @param widget2 The widget to replace `widget` with

View File

@ -31,6 +31,11 @@ local flex = {}
-- @name get_children -- @name get_children
-- @class function -- @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 --- Add some widgets to the given fixed layout
-- @param layout The layout you are modifying. -- @param layout The layout you are modifying.
-- @tparam widget ... Widgets that should be added (must at least be one) -- @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} return {self.widget}
end 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. --- Set all the margins to val.
function margin:set_margins(val) function margin:set_margins(val)
self.left = val self.left = val

View File

@ -64,6 +64,14 @@ function mirror:get_children()
return {self.widget} return {self.widget}
end 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. --- Reset this layout. The widget will be removed and the axes reset.
function mirror:reset() function mirror:reset()
self.horizontal = false self.horizontal = false

View File

@ -72,6 +72,14 @@ function rotate:get_children()
return {self.widget} return {self.widget}
end 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. --- Reset this layout. The widget will be removed and the rotation reset.
function rotate:reset() function rotate:reset()
self.direction = nil self.direction = nil

View File

@ -267,6 +267,14 @@ function scroll:get_children()
return {self.widget} return {self.widget}
end 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. --- Specify the expand mode that is used for extra space.
-- @tparam boolean expand If true, the widget is expanded to include the extra -- @tparam boolean expand If true, the widget is expanded to include the extra
-- space. If false, the extra space is simply left empty. -- space. If false, the extra space is simply left empty.

View File

@ -101,6 +101,14 @@ function background:get_children()
return {self.widget} return {self.widget}
end 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 --- Set the background to use
function background:set_bg(bg) function background:set_bg(bg)
if bg then if bg then

View File

@ -8,6 +8,7 @@
local object = require("gears.object") local object = require("gears.object")
local cache = require("gears.cache") local cache = require("gears.cache")
local matrix = require("gears.matrix") local matrix = require("gears.matrix")
local util = require("awful.util")
local setmetatable = setmetatable local setmetatable = setmetatable
local pairs = pairs local pairs = pairs
local type = type local type = type
@ -80,6 +81,14 @@ function base.widget:get_children()
return {} return {}
end 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 -- It could have been merged into `get_all_children`, but it's not necessary
local function digg_children(ret, tlw) local function digg_children(ret, tlw)
for k, w in ipairs(tlw:get_children()) do 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) return base.place_widget_via_matrix(widget, matrix.create_translate(x, y), width, height)
end end
--[[-- -- Read the table, separate attributes from widgets
Create a new widget. All widgets have to be generated via this function so that local function parse_table(t, leave_empty)
the needed signals are added and mouse input handling is set up. 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 returned widget will have a :buttons member function that can be used to widgets[k] = v
register a set of mouse button events with the widget. else
attributes[k] = v
To implement your own widget, you can implement some member functions on a end
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 end
The next callback is :draw. As the name suggests, this function is called to -- Pack the sparse table if the container doesn't support sparse tables
draw the widget. The arguments to this widget are the context that the widget is if not leave_empty then
drawn in, the cairo context on which it should be drawn and the widget's size. widgets = util.table.from_sparse(widgets)
The cairo context is set up in such a way that the widget as its top-left corner max = #widgets
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 end
There are two signals configured for a widget. When the result that :fit would return max, attributes, widgets
return changes, the `widget::layout_changed` signal has to be emitted. If this end
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 -- Recursively build a container from a declarative table
is needed. The following callbacks can be used when implementing layouts which local function drill(ids, content)
place other widgets on the screen. if not content then return end
The :layout callback is used to figure out which other widgets should be drawn -- Alias `widget` to `layout` as they are handled the same way
relative to this widget. Note that it is allowed to place widgets outside of the content.layout = content.layout or content.widget
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 -- Make sure the layout is not indexed on a function
local base = wibox.widget.base local layout = type(content.layout) == "function" and content.layout() or content.layout
function widget:layout(width, height)
local result = {} -- Create layouts based on metatable __call
table.insert(result, base.place_widget_at(child, width/2, 0, width/2, height) local l = layout.is_widget and layout or layout()
return result
-- 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
-- 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 end
Finally, if you want to influence how children are drawn, there are four -- Replace all children (if any) with the new ones
callbacks available that all get similar arguments: l:set_children(widgets)
function widget:before_draw_children(context, cr, width, height) -- Set layouts attributes
function widget:after_draw_children(context, cr, width, height) for attr, val in pairs(attributes) do
function widget:before_draw_child(context, index, child, cr, width, height) if l["set_"..attr] then
function widget:after_draw_child(context, index, child, cr, width, height) l["set_"..attr](l, val)
elseif type(l[attr]) == "function" then
All of these are called with the same arguments as the :draw() method. Please l[attr](l, val)
note that a larger clip will be active during these callbacks that also contains else
the area of all children. These callbacks can be used to influence the way in l[attr] = val
which children are drawn, but they should not cause the drawing to cover a end
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 end
In pseudo-code, the call sequence for the drawing callbacks during a redraw return l, id
looks like this: end
widget:draw(context, cr, width, height) -- Only available when the declarative system is used
widget:before_draw_children(context, cr, width, height) local function get_children_by_id(self, name)
for child do return self._by_id[name] or {}
widget:before_draw_child(context, cr, child_index, child, width, height) end
cr:save()
-- Draw child and all of its children recursively, taking into account the --- Set a declarative widget hierarchy description.
-- position and size given to base.place_widget_at() in :layout(). -- See [The declarative layout system](../documentation/03-declarative-layout.md.html)
cr:restore() -- @param args An array containing the widgets disposition
widget:after_draw_child(context, cr, child_index, child, width, height) 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 end
widget:after_draw_children(context, cr, width, height) rawset(self, "_by_id", ids)
rawset(self, "get_children_by_id", get_children_by_id)
end
@param proxy If this is set, the returned widget will be a proxy for this --- Create an empty widget skeleton
widget. It will be equivalent to this widget. This means it -- See [Creating new widgets](../documentation/04-new-widget.md.html)
looks the same on the screen. -- @param proxy If this is set, the returned widget will be a proxy for this
@tparam[opt] string widget_name Name of the widget. If not set, it will be -- widget. It will be equivalent to this widget. This means it
set automatically via `gears.object.modulename`. -- looks the same on the screen.
@see fit_widget -- @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) function base.make_widget(proxy, widget_name)
local ret = object() local ret = object()
@ -470,6 +474,9 @@ function base.make_widget(proxy, widget_name)
-- Widget is fully opaque -- Widget is fully opaque
ret.opacity = 1 ret.opacity = 1
-- Differentiate tables from widgets
ret.is_widget = true
-- Size is not restricted/forced -- Size is not restricted/forced
ret._forced_width = nil ret._forced_width = nil
ret._forced_height = nil ret._forced_height = nil