From 3ef5003acedf57a3b2fe5a40587e3351da4e3860 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Thu, 4 Feb 2016 22:42:54 -0500 Subject: [PATCH 1/7] doc: Add support for images --- CMakeLists.txt | 11 ++++++++++- docs/images/widgetlayout1.png | Bin 0 -> 1038 bytes docs/images/widgetlayout2.png | Bin 0 -> 587 bytes 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 docs/images/widgetlayout1.png create mode 100644 docs/images/widgetlayout2.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 762084298..e0372467f 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/docs/images/widgetlayout1.png b/docs/images/widgetlayout1.png new file mode 100644 index 0000000000000000000000000000000000000000..309407859b4bebb88539c25a72d0ffa8350bf95b GIT binary patch literal 1038 zcmV+p1o8WcP)m<0Ogu2zGOSGtiT@nkrq~)bR5&wpy>7h%vmC?aqD@ST5 z&OIHT2cNm-rZ}Hxy6=l2+jqbHe!uN``0Uy5d9*m4&bja35ki=!(&aLH3K_&|_5_3w zNzBg97(b}XRcZX7#yDj51cVU6P?CB=h|D6Xmj)CCMTv9Y+?i~rF7^Z2bTK6LQY|JR z_Bt>8eiVg@3V_Z|6oor?04gd_6#V`@8B{{haOy|CaTB{CskhG!3;?*@0QL3yU$rtg zXx6+$%2_P>P3(rGo`Lf7>FQ#Bo=}MXeo9M|w0Ah@=^+px5a7iN9F90`)Le7BvDv7u zW_6XSDspqFtkj=P&{cxthq5w0ek2l+LyPp}bTT^1;vyp>oH~`L2t0Ve(h}3tl$Yy8 zMIjR5>sQX7*DGExKYkF281**HAgQ-E;zr4%N8GqUYb$kiG&IoNoum_=kE>T{YoooL zD_8LO;}+alak~kJ86MtNSJ$s|=)XO|=fmUS@@1NuVxRi; zi(oLxJadM#XXES+ z2Z=?lQcf{-m)!Uf68$g`ptx9Xxw=YDPHda#`@tYZMdapUv9P>MFi1&BoDnA`aJhK) zj7yg&EoE+wzCNy9%ZayawE%8;# zv$Hdb68pn;VS(~;LLnRu{C)}xw=60O4hNwS1qFQjrXA?l*Yofpr%#ie&H6f4>!y~- z^?D*T)J!>cOk0~#BX9Qx2C&&^Y2oc#!eQRLp}Cp7yd5m+sgbvR9*;!ce)s%2ckg1i zbMz>O59@U`@(wmhJp=Xk19Wz7H}BrP188XA!Ucf$@8dU9)6H(@^=p9MUhH$=;$CnpIf&W8%tCKt$6#kwp9U}jX!_L%HsZgqi1e9`HwUM-Mq=) zzg)bC*Q+g@Bt9G0-VX5eDZtB@+Qa|N%<%g+>+9NX{^0{Zf0C2K^z@FqiW&X*abCTW zQ-gS^xk507*qo IM6N<$f?r0 z=4PALZfh1rQK!?vKN`mDD|MjM?+ za88e>>-YCAcOvDdZr46`-0OXz15}H}+xA(jDCcgi6clY!5HnpcVH&4NbN)4476h(J71CTbG--H-TIi@Li{h}zk?-Iyu Z{s3n`P5Ad$-dX?v002ovPDHLkV1lQx3?2Xg literal 0 HcmV?d00001 From e36f23171bfc7147020d93bbc8c111c1aa9fc25d Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Thu, 4 Feb 2016 01:11:10 -0500 Subject: [PATCH 2/7] wibox.widget: Add `set_children` method Provide a generic method to set the widget content --- lib/wibox/layout/align.lua | 14 ++++++++++++++ lib/wibox/layout/constraint.lua | 8 ++++++++ lib/wibox/layout/fixed.lua | 8 ++++++++ lib/wibox/layout/flex.lua | 5 +++++ lib/wibox/layout/margin.lua | 8 ++++++++ lib/wibox/layout/mirror.lua | 8 ++++++++ lib/wibox/layout/rotate.lua | 8 ++++++++ lib/wibox/layout/scroll.lua | 8 ++++++++ lib/wibox/widget/background.lua | 8 ++++++++ lib/wibox/widget/base.lua | 8 ++++++++ 10 files changed, 83 insertions(+) diff --git a/lib/wibox/layout/align.lua b/lib/wibox/layout/align.lua index bc7635cd2..95337aa80 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 32aa06a57..9fd4c1d2a 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 291c96e3e..0611effd8 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 a6312e71d..9c0a4b544 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 1666f85fa..e9b97e752 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 c98e407fc..a0975459e 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 5d5d3b668..616d2564e 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 44165ea51..d443a4a4d 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 8ea11954a..a31616a8d 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 5e324207d..c7ea5acd7 100644 --- a/lib/wibox/widget/base.lua +++ b/lib/wibox/widget/base.lua @@ -80,6 +80,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 From 6a68173ccb96a8b81603ddd44137bb355a2c3d71 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 27 Sep 2015 17:00:01 +0200 Subject: [PATCH 3/7] widget: Add a new container declaration syntax This new syntax is inspired by the Awesome widget 3.2-3.4 API. It allow cleaner widgets declaration. The produced code is usually much shorted and easier to read than wibox.widget imperative syntax. --- lib/wibox/widget/base.lua | 111 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/lib/wibox/widget/base.lua b/lib/wibox/widget/base.lua index c7ea5acd7..0a6913819 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 @@ -329,6 +330,113 @@ 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 +-- 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 + + widgets[k] = v + else + attributes[k] = v + end + end + + -- 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 + + return max, attributes, widgets +end + +-- Recursively build a container from a declarative table +local function drill(ids, content) + if not content then return end + + -- 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 + + -- 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 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. @@ -478,6 +586,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 From 7842f92f089b238dba63f5d95841326c96bc7a83 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 27 Sep 2015 17:00:01 +0200 Subject: [PATCH 4/7] wibox: Support the declarative container syntax Also support it for titlebars --- lib/awful/titlebar.lua | 9 +++++++++ lib/wibox/init.lua | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/lib/awful/titlebar.lua b/lib/awful/titlebar.lua index 41ec779b5..f259453f2 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 5952571ae..be61fadd1 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. From 0e833fd5f4719d8a9663b51c5727b2a4be1c0770 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 27 Sep 2015 17:00:01 +0200 Subject: [PATCH 5/7] awesomerc: Use the new declarative syntax --- awesomerc.lua | 112 ++++++++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 59 deletions(-) diff --git a/awesomerc.lua b/awesomerc.lua index 5fb42d30a..233118880 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) From 8be110461532d002f81dc27fa17d2064a0499980 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 10 Feb 2016 01:07:47 -0500 Subject: [PATCH 6/7] doc: Add layout system documentation --- docs/03-declarative-layout.md | 263 ++++++++++++++++++++++++++++++++++ docs/config.ld | 1 + 2 files changed, 264 insertions(+) create mode 100644 docs/03-declarative-layout.md diff --git a/docs/03-declarative-layout.md b/docs/03-declarative-layout.md new file mode 100644 index 000000000..3b7524394 --- /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/config.ld b/docs/config.ld index 94403fe1e..0b2f37508 100644 --- a/docs/config.ld +++ b/docs/config.ld @@ -19,6 +19,7 @@ topics={ '00-authors.md', '01-readme.md', '02-contributing.md', + '03-declarative-layout.md', } -- Setup @client to be an alias for "@tparam client.object" From b1e69dba8c606cb9a962aff8d9d5b39668f7f96d Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 10 Feb 2016 01:21:29 -0500 Subject: [PATCH 7/7] doc: Move the new widget documentation to a new file This will help with discoverability --- docs/04-new-widgets.md | 113 +++++++++++++++++++++++++++++++++ docs/config.ld | 1 + lib/wibox/widget/base.lua | 128 +++----------------------------------- 3 files changed, 122 insertions(+), 120 deletions(-) create mode 100644 docs/04-new-widgets.md diff --git a/docs/04-new-widgets.md b/docs/04-new-widgets.md new file mode 100644 index 000000000..b519d8552 --- /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 0b2f37508..ce738c8ec 100644 --- a/docs/config.ld +++ b/docs/config.ld @@ -20,6 +20,7 @@ topics={ '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/lib/wibox/widget/base.lua b/lib/wibox/widget/base.lua index 0a6913819..72393144b 100644 --- a/lib/wibox/widget/base.lua +++ b/lib/wibox/widget/base.lua @@ -437,126 +437,14 @@ function base.widget:setup(args) rawset(self, "get_children_by_id", get_children_by_id) 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 - 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) - -@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()