diff --git a/CMakeLists.txt b/CMakeLists.txt index 76208429..e0372467 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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. @@ -341,7 +349,8 @@ install(DIRECTORY ${SOURCE_DIR}/themes DESTINATION ${AWESOME_DATA_PATH} PATTERN "*.lua" EXCLUDE) install(DIRECTORY ${BUILD_DIR}/themes DESTINATION ${AWESOME_DATA_PATH} 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}) if(GENERATE_DOC) install(DIRECTORY ${BUILD_DIR}/doc DESTINATION ${AWESOME_DOC_PATH}) diff --git a/awesomerc.lua b/awesomerc.lua index 5fb42d30..23311888 100755 --- a/awesomerc.lua +++ b/awesomerc.lua @@ -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 -- }}} @@ -482,45 +479,42 @@ client.connect_signal("manage", function (c) if titlebars_enabled and (c.type == "normal" or c.type == "dialog") then -- buttons for the titlebar local buttons = awful.util.table.join( - awful.button({ }, 1, function() - client.focus = c - c:raise() - awful.mouse.client.move(c) - end), - awful.button({ }, 3, function() - client.focus = c - c:raise() - awful.mouse.client.resize(c) - end) - ) + awful.button({ }, 1, function() + client.focus = c + c:raise() + awful.mouse.client.move(c) + end), + awful.button({ }, 3, function() + client.focus = c + c:raise() + awful.mouse.client.resize(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) diff --git a/docs/03-declarative-layout.md b/docs/03-declarative-layout.md new file mode 100644 index 00000000..3b752439 --- /dev/null +++ b/docs/03-declarative-layout.md @@ -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 = "Hello World!", + 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) + diff --git a/docs/04-new-widgets.md b/docs/04-new-widgets.md new file mode 100644 index 00000000..b519d855 --- /dev/null +++ b/docs/04-new-widgets.md @@ -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) diff --git a/docs/config.ld b/docs/config.ld index 94403fe1..ce738c8e 100644 --- a/docs/config.ld +++ b/docs/config.ld @@ -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" diff --git a/docs/images/widgetlayout1.png b/docs/images/widgetlayout1.png new file mode 100644 index 00000000..30940785 Binary files /dev/null and b/docs/images/widgetlayout1.png differ diff --git a/docs/images/widgetlayout2.png b/docs/images/widgetlayout2.png new file mode 100644 index 00000000..1ff1b9b5 Binary files /dev/null and b/docs/images/widgetlayout2.png differ diff --git a/lib/awful/titlebar.lua b/lib/awful/titlebar.lua index 41ec779b..f259453f 100644 --- a/lib/awful/titlebar.lua +++ b/lib/awful/titlebar.lua @@ -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 diff --git a/lib/wibox/init.lua b/lib/wibox/init.lua index 5952571a..be61fadd 100644 --- a/lib/wibox/init.lua +++ b/lib/wibox/init.lua @@ -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. diff --git a/lib/wibox/layout/align.lua b/lib/wibox/layout/align.lua index bc7635cd..95337aa8 100644 --- a/lib/wibox/layout/align.lua +++ b/lib/wibox/layout/align.lua @@ -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 diff --git a/lib/wibox/layout/constraint.lua b/lib/wibox/layout/constraint.lua index 32aa06a5..9fd4c1d2 100644 --- a/lib/wibox/layout/constraint.lua +++ b/lib/wibox/layout/constraint.lua @@ -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) diff --git a/lib/wibox/layout/fixed.lua b/lib/wibox/layout/fixed.lua index 291c96e3..0611effd 100644 --- a/lib/wibox/layout/fixed.lua +++ b/lib/wibox/layout/fixed.lua @@ -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 diff --git a/lib/wibox/layout/flex.lua b/lib/wibox/layout/flex.lua index a6312e71..9c0a4b54 100644 --- a/lib/wibox/layout/flex.lua +++ b/lib/wibox/layout/flex.lua @@ -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) diff --git a/lib/wibox/layout/margin.lua b/lib/wibox/layout/margin.lua index 1666f85f..e9b97e75 100644 --- a/lib/wibox/layout/margin.lua +++ b/lib/wibox/layout/margin.lua @@ -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 diff --git a/lib/wibox/layout/mirror.lua b/lib/wibox/layout/mirror.lua index c98e407f..a0975459 100644 --- a/lib/wibox/layout/mirror.lua +++ b/lib/wibox/layout/mirror.lua @@ -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 diff --git a/lib/wibox/layout/rotate.lua b/lib/wibox/layout/rotate.lua index 5d5d3b66..616d2564 100644 --- a/lib/wibox/layout/rotate.lua +++ b/lib/wibox/layout/rotate.lua @@ -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 diff --git a/lib/wibox/layout/scroll.lua b/lib/wibox/layout/scroll.lua index 44165ea5..d443a4a4 100644 --- a/lib/wibox/layout/scroll.lua +++ b/lib/wibox/layout/scroll.lua @@ -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. diff --git a/lib/wibox/widget/background.lua b/lib/wibox/widget/background.lua index 8ea11954..a31616a8 100644 --- a/lib/wibox/widget/background.lua +++ b/lib/wibox/widget/background.lua @@ -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 diff --git a/lib/wibox/widget/base.lua b/lib/wibox/widget/base.lua index 5e324207..72393144 100644 --- a/lib/wibox/widget/base.lua +++ b/lib/wibox/widget/base.lua @@ -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. +-- 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 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 + widgets[k] = v + else + attributes[k] = v + end 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() + -- 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 -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. + return max, attributes, widgets +end -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. +-- Recursively build a container from a declarative table +local function drill(ids, content) + if not content then return end -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: + -- Alias `widget` to `layout` as they are handled the same way + content.layout = content.layout or content.widget - -- 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 + -- 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 + + -- 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 -Finally, if you want to influence how children are drawn, there are four -callbacks available that all get similar arguments: + -- Replace all children (if any) with the new ones + l:set_children(widgets) - 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) + -- 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 -In pseudo-code, the call sequence for the drawing callbacks during a redraw -looks like this: + return l, id +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) +-- 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 - 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 - 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 ---]]-- +--- 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