diff --git a/lib/awful/widget/graph.lua b/lib/awful/widget/graph.lua index b5b4309c..fdd83198 100644 --- a/lib/awful/widget/graph.lua +++ b/lib/awful/widget/graph.lua @@ -195,7 +195,7 @@ local function add_value(_graph, value, group) table.remove(values, 1) end - _graph:emit_signal("widget::updated") + _graph:emit_signal("widget::redraw_needed") return _graph end @@ -205,7 +205,7 @@ end function graph:set_height(height) if height >= 5 then data[self].height = height - self:emit_signal("widget::updated") + self:emit_signal("widget::layout_changed") end return self end @@ -215,7 +215,7 @@ end function graph:set_width(width) if width >= 5 then data[self].width = width - self:emit_signal("widget::updated") + self:emit_signal("widget::layout_changed") end return self end @@ -226,7 +226,7 @@ for _, prop in ipairs(properties) do graph["set_" .. prop] = function(_graph, value) if data[_graph][prop] ~= value then data[_graph][prop] = value - _graph:emit_signal("widget::updated") + _graph:emit_signal("widget::redraw_needed") end return _graph end diff --git a/lib/awful/widget/progressbar.lua b/lib/awful/widget/progressbar.lua index 08d67923..4932207b 100644 --- a/lib/awful/widget/progressbar.lua +++ b/lib/awful/widget/progressbar.lua @@ -164,7 +164,7 @@ function progressbar:set_value(value) local value = value or 0 local max_value = data[self].max_value data[self].value = math.min(max_value, math.max(0, value)) - self:emit_signal("widget::updated") + self:emit_signal("widget::redraw_needed") return self end @@ -172,7 +172,7 @@ end -- @param height The height to set. function progressbar:set_height(height) data[self].height = height - self:emit_signal("widget::updated") + self:emit_signal("widget::layout_changed") return self end @@ -180,7 +180,7 @@ end -- @param width The width to set. function progressbar:set_width(width) data[self].width = width - self:emit_signal("widget::updated") + self:emit_signal("widget::layout_changed") return self end @@ -189,7 +189,7 @@ for _, prop in ipairs(properties) do if not progressbar["set_" .. prop] then progressbar["set_" .. prop] = function(pbar, value) data[pbar][prop] = value - pbar:emit_signal("widget::updated") + pbar:emit_signal("widget::redraw_needed") return pbar end end diff --git a/lib/wibox/drawable.lua b/lib/wibox/drawable.lua index ed0a7939..82f7914f 100644 --- a/lib/wibox/drawable.lua +++ b/lib/wibox/drawable.lua @@ -20,6 +20,9 @@ local object = require("gears.object") local sort = require("gears.sort") local surface = require("gears.surface") local timer = require("gears.timer") +local matrix = require("gears.matrix") +local hierarchy = require("wibox.hierarchy") +local base = require("wibox.widget.base") local drawables = setmetatable({}, { __mode = 'k' }) local wallpaper = nil @@ -69,6 +72,36 @@ local function do_redraw(self) local geom = self.drawable:geometry(); local x, y, width, height = geom.x, geom.y, geom.width, geom.height + -- Relayout + if self._need_relayout then + self._need_relayout = false + local old_hierarchy = self._widget_hierarchy + self._widget_hierarchy_callback_arg = {} + self._widget_hierarchy = self.widget and + hierarchy.new(get_widget_context(self), self.widget, width, height, + self._redraw_callback, self._layout_callback, self._widget_hierarchy_callback_arg) + + if old_hierarchy == nil or self._widget_hierarchy == nil or self._need_complete_repaint then + self._need_complete_repaint = false + self._dirty_area:union_rectangle(cairo.RectangleInt{ + x = 0, y = 0, width = width, height = height + }) + else + self._dirty_area:union(self._widget_hierarchy:find_differences(old_hierarchy)) + end + end + + -- Clip to the dirty area + if self._dirty_area:is_empty() then + return + end + for i = 0, self._dirty_area:num_rectangles() - 1 do + local rect = self._dirty_area:get_rectangle(i) + cr:rectangle(rect.x, rect.y, rect.width, rect.height) + end + self._dirty_area = cairo.Region.create() + cr:clip() + -- Draw the background cr:save() @@ -93,21 +126,9 @@ local function do_redraw(self) cr:restore() -- Draw the widget - self._widget_geometries = {} - if self.widget and self.widget.visible then + if self._widget_hierarchy then cr:set_source(self.foreground_color) - - if self.widget.opacity ~= 1 then - cr:push_group() - end - self.widget:draw(get_widget_context(self), cr, width, height) - if self.widget.opacity ~= 1 then - cr:pop_group_to_source() - cr.operator = cairo.Operator.OVER - cr:paint_with_alpha(self.widget.opacity) - end - - self:widget_at(self.widget, 0, 0, width, height) + self._widget_hierarchy:draw(get_widget_context(self), cr) end self.drawable:refresh() @@ -115,16 +136,33 @@ local function do_redraw(self) debug.assert(cr.status == "SUCCESS", "Cairo context entered error state: " .. cr.status) end ---- Register a widget's position. --- This is internal, don't call it yourself! Only wibox.layout.base.draw_widget --- is allowed to call this. -function drawable:widget_at(widget, x, y, width, height) - local t = { - widget = widget, - x = x, y = y,drawable = self, - width = width, height = height - } - table.insert(self._widget_geometries, t) +local function find_widgets(drawable, result, hierarchy, x, y) + local m = hierarchy:get_matrix_from_device() + + -- Is (x,y) inside of this hierarchy or any child (aka the draw extents) + local x1, y1 = m:transform_point(x, y) + local x2, y2, width, height = hierarchy:get_draw_extents() + if x1 < x2 or x1 >= x2 + width then + return + end + if y1 < y2 or y1 >= y2 + height then + return + end + + -- Is (x,y) inside of this widget? + local width, height = hierarchy:get_size() + if x1 >= 0 and y1 >= 0 and x1 <= width and y1 <= height then + -- Get the extents of this widget in the device space + local x2, y2, w2, h2 = matrix.transform_rectangle(hierarchy:get_matrix_to_device(), + 0, 0, width, height) + table.insert(result, { + x = x2, y = y2, width = w2, height = h2, + drawable = drawable, widget = hierarchy:get_widget() + }) + end + for _, child in ipairs(hierarchy:get_children()) do + find_widgets(drawable, result, child, x, y) + end end --- Find a widget by a point. @@ -134,43 +172,20 @@ end -- @return A sorted table with all widgets that contain the given point. The -- widgets are sorted by relevance. function drawable:find_widgets(x, y) - local matches = {} - -- Find all widgets that contain the point - for k, v in pairs(self._widget_geometries) do - local match = true - if v.x > x or v.x + v.width <= x then match = false end - if v.y > y or v.y + v.height <= y then match = false end - if match then - table.insert(matches, v) - end + local result = {} + if self._widget_hierarchy then + find_widgets(self, result, self._widget_hierarchy, x, y) end - - -- Sort the matches by area, the assumption here is that widgets don't - -- overlap and so smaller widgets are "more specific". - local function cmp(a, b) - local area_a = a.width * a.height - local area_b = b.width * b.height - return area_a < area_b - end - sort(matches, cmp) - - return matches + return result end --- Set the widget that the drawable displays function drawable:set_widget(widget) - if self.widget then - -- Disconnect from the old widget so that we aren't updated due to it - self.widget:disconnect_signal("widget::updated", self.draw) - end - self.widget = widget - if widget then - widget:weak_connect_signal("widget::updated", self.draw) - end -- Make sure the widget gets drawn + self._need_relayout = true self.draw() end @@ -192,16 +207,16 @@ function drawable:set_bg(c) if self._redraw_on_move ~= redraw_on_move then self._redraw_on_move = redraw_on_move if redraw_on_move then - self.drawable:connect_signal("property::x", self.draw) - self.drawable:connect_signal("property::y", self.draw) + self.drawable:connect_signal("property::x", self._do_complete_repaint) + self.drawable:connect_signal("property::y", self._do_complete_repaint) else - self.drawable:disconnect_signal("property::x", self.draw) - self.drawable:disconnect_signal("property::y", self.draw) + self.drawable:disconnect_signal("property::x", self._do_complete_repaint) + self.drawable:disconnect_signal("property::y", self._do_complete_repaint) end end self.background_color = c - self.draw() + self._do_complete_repaint() end --- Set the foreground of the drawable @@ -213,7 +228,7 @@ function drawable:set_fg(c) c = color(c) end self.foreground_color = c - self.draw() + self._do_complete_repaint() end local function emit_difference(name, list, skip) @@ -280,6 +295,9 @@ function drawable.new(d, widget_context_skeleton, drawable_name) local ret = object() ret.drawable = d ret._widget_context_skeleton = widget_context_skeleton + ret._need_complete_repaint = true + ret._need_relayout = true + ret._dirty_area = cairo.Region.create() setup_signals(ret) for k, v in pairs(drawable) do @@ -302,8 +320,12 @@ function drawable.new(d, widget_context_skeleton, drawable_name) ret._redraw_pending = true end end - drawables[ret.draw] = true - d:connect_signal("property::surface", ret.draw) + ret._do_complete_repaint = function() + ret._need_complete_repaint = true + ret:draw() + end + drawables[ret._do_complete_repaint] = true + d:connect_signal("property::surface", ret._do_complete_repaint) -- Currently we aren't redrawing on move (signals not connected). -- :set_bg() will later recompute this. @@ -314,7 +336,6 @@ function drawable.new(d, widget_context_skeleton, drawable_name) ret:set_fg(beautiful.fg_normal) -- Initialize internals - ret._widget_geometries = {} ret._widgets_under_mouse = {} local function button_signal(name) @@ -334,6 +355,28 @@ function drawable.new(d, widget_context_skeleton, drawable_name) d:connect_signal("mouse::move", function(_, x, y) handle_motion(ret, x, y) end) d:connect_signal("mouse::leave", function() handle_leave(ret) end) + -- Set up our callbacks for repaints + ret._redraw_callback = function(hierarchy, arg) + if ret._widget_hierarchy_callback_arg ~= arg then + return + end + local m = hierarchy:get_matrix_to_device() + local x, y, width, height = matrix.transform_rectangle(m, hierarchy:get_draw_extents()) + local x1, y1 = math.floor(x), math.floor(y) + local x2, y2 = math.ceil(x + width), math.ceil(y + height) + ret._dirty_area:union_rectangle(cairo.RectangleInt{ + x = x1, y = y1, width = x2 - x1, height = y2 - y1 + }) + ret:draw() + end + ret._layout_callback = function(hierarchy, arg) + if ret._widget_hierarchy_callback_arg ~= arg then + return + end + ret._need_relayout = true + ret:draw() + end + -- Add __tostring method to metatable. ret.drawable_name = drawable_name or object.modulename(3) local mt = {} @@ -344,7 +387,7 @@ function drawable.new(d, widget_context_skeleton, drawable_name) ret = setmetatable(ret, mt) -- Make sure the drawable is drawn at least once - ret.draw() + ret._do_complete_repaint() return ret end diff --git a/lib/wibox/hierarchy.lua b/lib/wibox/hierarchy.lua new file mode 100644 index 00000000..7ad7f4fd --- /dev/null +++ b/lib/wibox/hierarchy.lua @@ -0,0 +1,260 @@ +--------------------------------------------------------------------------- +-- Management of widget hierarchies. Each widget hierarchy object has a widget +-- for which it saves e.g. size and transformation in its parent. Also, each +-- widget has a number of children. +-- +-- @author Uli Schlachter +-- @copyright 2015 Uli Schlachter +-- @release @AWESOME_VERSION@ +-- @module wibox.hierarchy +--------------------------------------------------------------------------- + +local matrix = require("gears.matrix") +local cairo = require("lgi").cairo +local base = require("wibox.widget.base") + +local hierarchy = {} + +local function hierarchy_new(context, widget, width, height, redraw_callback, layout_callback, callback_arg, + matrix_to_parent, matrix_to_device) + local children = base.layout_widget(context, widget, width, height) + local draws_x1, draws_y1, draws_x2, draws_y2 = 0, 0, width, height + local result = { + _matrix = matrix_to_parent, + _matrix_to_device = matrix_to_device, + _widget = widget, + _size = { + width = width, + height = height + }, + _draw_extents = nil, + _children = {} + } + + result._redraw = function() redraw_callback(result, callback_arg) end + result._layout = function() layout_callback(result, callback_arg) end + widget:weak_connect_signal("widget::redraw_needed", result._redraw) + widget:weak_connect_signal("widget::layout_changed", result._layout) + + for _, w in ipairs(children or {}) do + local to_dev = cairo.Matrix.create_identity() + to_dev:multiply(matrix_to_device, w._matrix) + + local r = hierarchy_new(context, w._widget, w._width, w._height, redraw_callback, layout_callback, + callback_arg, matrix.copy(w._matrix), to_dev) + table.insert(result._children, r) + + -- Update our drawing extents + local s = r._draw_extents + local px, py, pwidth, pheight = matrix.transform_rectangle(r._matrix, + s.x, s.y, s.width, s.height) + local px2, py2 = px + pwidth, py + pheight + draws_x1 = math.min(draws_x1, px) + draws_y1 = math.min(draws_y1, py) + draws_x2 = math.max(draws_x2, px2) + draws_y2 = math.max(draws_y2, py2) + end + result._draw_extents = { + x = draws_x1, + y = draws_y1, + width = draws_x2 - draws_x1, + height = draws_y2 - draws_y1 + } + + for k, f in pairs(hierarchy) do + if type(f) == "function" then + result[k] = f + end + end + return result +end + +--- Create a new widget hierarchy that has no parent. +-- @param context The context in which we are laid out. +-- @param widget The widget that is at the base of the hierarchy. +-- @param width The available width for this hierarchy. +-- @param height The available height for this hierarchy. +-- @param redraw_callback Callback that is called with the corresponding widget +-- hierarchy on widget::redraw_needed on some widget. +-- @param layout_callback Callback that is called with the corresponding widget +-- hierarchy on widget::layout_changed on some widget. +-- @param callback_arg A second argument that is given to the above callbacks. +-- @return A new widget hierarchy +function hierarchy.new(context, widget, width, height, redraw_callback, layout_callback, callback_arg) + return hierarchy_new(context, widget, width, height, redraw_callback, layout_callback, callback_arg, + cairo.Matrix.create_identity(), cairo.Matrix.create_identity()) +end + +--- Get the widget that this hierarchy manages. +function hierarchy:get_widget() + return self._widget +end + +--- Get a cairo matrix that transforms to the parent's coordinate space from +-- this hierarchy's coordinate system. +-- @return A cairo matrix describing the transformation. +function hierarchy:get_matrix_to_parent() + return matrix.copy(self._matrix) +end + +--- Get a cairo matrix that transforms to the base of this hierarchy's +-- coordinate system (aka the coordinate system of the device that this +-- hierarchy is applied upon) from this hierarchy's coordinate system. +-- @return A cairo matrix describing the transformation. +function hierarchy:get_matrix_to_device() + return matrix.copy(self._matrix_to_device) +end + +--- Get a cairo matrix that transforms from the parent's coordinate space into +-- this hierarchy's coordinate system. +-- @return A cairo matrix describing the transformation. +function hierarchy:get_matrix_from_parent() + local m = self:get_matrix_to_parent() + m:invert() + return m +end + +--- Get a cairo matrix that transforms from the base of this hierarchy's +-- coordinate system (aka the coordinate system of the device that this +-- hierarchy is applied upon) into this hierarchy's coordinate system. +-- @return A cairo matrix describing the transformation. +function hierarchy:get_matrix_from_device() + local m = self:get_matrix_to_device() + m:invert() + return m +end + +--- Get the extents that this hierarchy possibly draws to (in the current coordinate space). +-- This includes the size of this element plus the size of all children +-- (after applying the corresponding transformation). +-- @return x, y, width, height +function hierarchy:get_draw_extents() + local ext = self._draw_extents + return ext.x, ext.y, ext.width, ext.height +end + +--- Get the size that this hierarchy logically covers (in the current coordinate space). +-- @return width, height +function hierarchy:get_size() + local ext = self._size + return ext.width, ext.height +end + +--- Get a list of all children. +-- @return List of all children hierarchies. +function hierarchy:get_children() + return self._children +end + +--- Compare two widget hierarchies and compute a cairo Region that contains all +-- rectangles that aren't the same between both hierarchies. +-- @param other The hierarchy to compare with +-- @return A cairo Region containing the differences. +function hierarchy:find_differences(other) + local region = cairo.Region.create() + local function needs_redraw(h) + local m = h:get_matrix_to_device() + local p = h._draw_extents + local x, y, width, height = matrix.transform_rectangle(m, p.x, p.y, p.width, p.height) + local x1, y1 = math.floor(x), math.floor(y) + local x2, y2 = math.ceil(x + width), math.ceil(y + height) + region:union_rectangle(cairo.RectangleInt({ + x = x1, y = y1, width = x2 - x1, height = y2 - y1 + })) + end + local compare + compare = function(self, other) + local s_size, o_size = self._size, other._size + if s_size.width ~= o_size.width or s_size.height ~= o_size.height or + #self._children ~= #other._children or self._widget ~= other._widget or + not matrix.equals(self._matrix, other._matrix) then + needs_redraw(self) + needs_redraw(other) + else + for i = 1, #self._children do + compare(self._children[i], other._children[i]) + end + end + end + compare(self, other) + return region +end + +--- Does the given cairo context have an empty clip (aka "no drawing possible")? +local function empty_clip(cr) + local x, y, width, height = cr:clip_extents() + return width == 0 or height == 0 +end + +--- Draw a hierarchy to some cairo context. +-- This function draws the widgets in this widget hierarchy to the given cairo +-- context. The context's clip is used to skip parts that aren't visible. +-- @param context The context in which widgets are drawn. +-- @param cr The cairo context that is used for drawing. +function hierarchy:draw(context, cr) + local widget = self:get_widget() + if not widget.visible then + return + end + + cr:save() + cr:transform(self:get_matrix_to_parent()) + + -- Clip to the draw extents + cr:rectangle(self:get_draw_extents()) + cr:clip() + + -- Draw if needed + if not empty_clip(cr) then + local opacity = widget.opacity + local function call(func, extra_arg1, extra_arg2) + if not func then return end + local function error_function(err) + print(debug.traceback("Error while drawing widget: " .. tostring(err), 2)) + end + if not extra_arg2 then + xpcall(function() + func(widget, context, cr, self:get_size()) + end, error_function) + else + xpcall(function() + func(widget, context, extra_arg1, extra_arg2, cr, self:get_size()) + end, error_function) + end + end + + -- Prepare opacity handling + if opacity ~= 1 then + cr:push_group() + end + + -- Draw the widget + cr:save() + cr:rectangle(0, 0, self:get_size()) + cr:clip() + call(widget.draw) + cr:restore() + + -- Draw its children (We already clipped to the draw extents above) + call(widget.before_draw_children) + for i, wi in ipairs(self:get_children()) do + call(widget.before_draw_child, i, wi:get_widget()) + wi:draw(context, cr) + call(widget.after_draw_child, i, wi:get_widget()) + end + call(widget.after_draw_children) + + -- Apply opacity + if opacity ~= 1 then + cr:pop_group_to_source() + cr.operator = cairo.Operator.OVER + cr:paint_with_alpha(opacity) + end + end + + cr:restore() +end + +return hierarchy + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/wibox/init.lua b/lib/wibox/init.lua index 5ea72c74..7d865c4c 100644 --- a/lib/wibox/init.lua +++ b/lib/wibox/init.lua @@ -29,6 +29,7 @@ local wibox = { mt = {} } wibox.layout = require("wibox.layout") wibox.widget = require("wibox.widget") wibox.drawable = require("wibox.drawable") +wibox.hierarchy = require("wibox.hierarchy") --- Set the widget that the wibox displays function wibox:set_widget(widget) diff --git a/lib/wibox/layout/align.lua b/lib/wibox/layout/align.lua index 0d33ed64..e31fb724 100644 --- a/lib/wibox/layout/align.lua +++ b/lib/wibox/layout/align.lua @@ -10,17 +10,17 @@ local table = table local pairs = pairs local type = type local floor = math.floor -local base = require("wibox.layout.base") -local widget_base = require("wibox.widget.base") +local base = require("wibox.widget.base") local align = {} ---- Draw an align layout. +--- Calculate the layout of an align layout. -- @param context The context in which we are drawn. --- @param cr The cairo context to use. -- @param width The available width. -- @param height The available height. -function align:draw(context, cr, width, height) +function align:layout(context, width, height) + local result = {} + -- Draw will have to deal with all three align modes and should work in a -- way that makes sense if one or two of the widgets are missing (if they -- are all missing, it won't draw anything.) It should also handle the case @@ -45,8 +45,7 @@ function align:draw(context, cr, width, height) -- if all the space is taken, skip the rest, and draw just the middle -- widget if size_second >= size_remains then - base.draw_widget(context, cr, self.second, 0, 0, width, height) - return + return { base.place_widget_at(self.second, 0, 0, width, height) } else -- the middle widget is sized first, the outside widgets are given -- the remaining space if available we will draw later @@ -84,7 +83,7 @@ function align:draw(context, cr, width, height) w = size_remains end end - base.draw_widget(context, cr, self.first, 0, 0, w, h) + table.insert(result, base.place_widget_at(self.first, 0, 0, w, h)) end -- size_remains will be <= 0 if first used all the space if self.third and size_remains > 0 then @@ -110,7 +109,7 @@ function align:draw(context, cr, width, height) end end local x, y = width - w, height - h - base.draw_widget(context, cr, self.third, x, y, w, h) + table.insert(result, base.place_widget_at(self.third, x, y, w, h)) end -- here we either draw the second widget in the space set aside for it -- in the beginning, or in the remaining space, if it is "inside" @@ -133,37 +132,36 @@ function align:draw(context, cr, width, height) x = floor( (width -w)/2 ) end end - base.draw_widget(context, cr, self.second, x, y, w, h) + table.insert(result, base.place_widget_at(self.second, x, y, w, h)) end -end - -local function widget_changed(layout, old_w, new_w) - if old_w then - old_w:disconnect_signal("widget::updated", layout._emit_updated) - end - if new_w then - widget_base.check_widget(new_w) - new_w:weak_connect_signal("widget::updated", layout._emit_updated) - end - layout._emit_updated() + return result end --- Set the layout's first widget. This is the widget that is at the left/top function align:set_first(widget) - widget_changed(self, self.first, widget) + if self.first == widget then + return + end self.first = widget + self:emit_signal("widget::layout_changed") end --- Set the layout's second widget. This is the centered one. function align:set_second(widget) - widget_changed(self, self.second, widget) + if self.second == widget then + return + end self.second = widget + self:emit_signal("widget::layout_changed") end --- Set the layout's third widget. This is the widget that is at the right/bottom function align:set_third(widget) - widget_changed(self, self.third, widget) + if self.third == widget then + return + end self.third = widget + self:emit_signal("widget::layout_changed") end --- Fit the align layout into the given space. The align layout will @@ -192,6 +190,7 @@ function align:fit(context, orig_width, orig_height) end return used_in_dir, used_in_other end + --- Set the expand mode which determines how sub widgets expand to take up -- unused space. Options are: -- "inside" - Default option. Size of outside widgets is determined using their @@ -210,22 +209,19 @@ function align:set_expand(mode) else self._expand = "inside" end - self:emit_signal("widget::updated") + self:emit_signal("widget::layout_changed") end function align:reset() for k, v in pairs({ "first", "second", "third" }) do self[v] = nil end - self:emit_signal("widget::updated") + self:emit_signal("widget::layout_changed") end local function get_layout(dir) - local ret = widget_base.make_widget() + local ret = base.make_widget() ret.dir = dir - ret._emit_updated = function() - ret:emit_signal("widget::updated") - end for k, v in pairs(align) do if type(v) == "function" then diff --git a/lib/wibox/layout/base.lua b/lib/wibox/layout/base.lua deleted file mode 100644 index a9205276..00000000 --- a/lib/wibox/layout/base.lua +++ /dev/null @@ -1,83 +0,0 @@ ---------------------------------------------------------------------------- --- @author Uli Schlachter --- @copyright 2010 Uli Schlachter --- @release @AWESOME_VERSION@ --- @classmod wibox.layout.base ---------------------------------------------------------------------------- - -local xpcall = xpcall -local print = print -local cairo = require("lgi").cairo -local wbase = require("wibox.widget.base") - -local base = {} - ---- Fit a widget for the given available width and height --- @param context The context in which we are fit. --- @param widget The widget to fit (this uses widget:fit(width, height)). --- @param width The available width for the widget --- @param height The available height for the widget --- @return The width and height that the widget wants to use -function base.fit_widget(context, widget, width, height) - if not widget.visible then - return 0, 0 - end - -- Sanitize the input. This also filters out e.g. NaN. - local width = math.max(0, width) - local height = math.max(0, height) - - local w, h = widget._fit_geometry_cache:get(context, width, height) - - -- Also sanitize the output. - w = math.max(0, math.min(w, width)) - h = math.max(0, math.min(h, height)) - return w, h -end - ---- Draw a widget via a cairo context --- @param context The context in which we are drawn. --- @param cr The cairo context used --- @param widget The widget to draw (this uses widget:draw(cr, width, height)). --- @param x The position that the widget should get --- @param y The position that the widget should get --- @param width The widget's width --- @param height The widget's height -function base.draw_widget(context, cr, widget, x, y, width, height) - if not widget.visible then - return - end - - -- Use save() / restore() so that our modifications aren't permanent - cr:save() - - -- Move (0, 0) to the place where the widget should show up - cr:translate(x, y) - - -- Make sure the widget cannot draw outside of the allowed area - cr:rectangle(0, 0, width, height) - cr:clip() - - if widget.opacity ~= 1 then - cr:push_group() - end - -- Let the widget draw itself - xpcall(function() - widget:draw(context, cr, width, height) - end, function(err) - print(debug.traceback("Error while drawing widget: "..tostring(err), 2)) - end) - if widget.opacity ~= 1 then - cr:pop_group_to_source() - cr.operator = cairo.Operator.OVER - cr:paint_with_alpha(widget.opacity) - end - - -- Register the widget for input handling - context:widget_at(widget, wbase.rect_to_device_geometry(cr, 0, 0, width, height)) - - cr:restore() -end - -return base - --- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/wibox/layout/constraint.lua b/lib/wibox/layout/constraint.lua index 5a0c975c..3d653928 100644 --- a/lib/wibox/layout/constraint.lua +++ b/lib/wibox/layout/constraint.lua @@ -8,19 +8,16 @@ local pairs = pairs local type = type local setmetatable = setmetatable -local base = require("wibox.layout.base") -local widget_base = require("wibox.widget.base") +local base = require("wibox.widget.base") local math = math local constraint = { mt = {} } ---- Draw a constraint layout -function constraint:draw(context, cr, width, height) - if not self.widget then - return +--- Layout a constraint layout +function constraint:layout(context, width, height) + if self.widget then + return { base.place_widget_at(self.widget, 0, 0, width, height) } end - - base.draw_widget(context, cr, self.widget, 0, 0, width, height) end --- Fit a constraint layout into the given space @@ -43,15 +40,8 @@ end --- Set the widget that this layout adds a constraint on. function constraint:set_widget(widget) - if self.widget then - self.widget:disconnect_signal("widget::updated", self._emit_updated) - end - if widget then - widget_base.check_widget(widget) - widget:weak_connect_signal("widget::updated", self._emit_updated) - end self.widget = widget - self:emit_signal("widget::updated") + self:emit_signal("widget::layout_changed") end --- Set the strategy to use for the constraining. Valid values are 'max', @@ -74,19 +64,19 @@ function constraint:set_strategy(val) end self._strategy = func[val] - self:emit_signal("widget::updated") + self:emit_signal("widget::layout_changed") end --- Set the maximum width to val. nil for no width limit. function constraint:set_width(val) self._width = val - self:emit_signal("widget::updated") + self:emit_signal("widget::layout_changed") end --- Set the maximum height to val. nil for no height limit. function constraint:set_height(val) self._height = val - self:emit_signal("widget::updated") + self:emit_signal("widget::layout_changed") end --- Reset this layout. The widget will be unreferenced, strategy set to "max" @@ -117,10 +107,6 @@ local function new(widget, strategy, width, height) end end - ret._emit_updated = function() - ret:emit_signal("widget::updated") - end - ret:set_strategy(strategy or "max") ret:set_width(width) ret:set_height(height) diff --git a/lib/wibox/layout/fixed.lua b/lib/wibox/layout/fixed.lua index 1a2ea1db..fc251993 100644 --- a/lib/wibox/layout/fixed.lua +++ b/lib/wibox/layout/fixed.lua @@ -5,21 +5,19 @@ -- @classmod wibox.layout.fixed --------------------------------------------------------------------------- -local base = require("wibox.layout.base") -local widget_base = require("wibox.widget.base") +local base = require("wibox.widget.base") local table = table local pairs = pairs local fixed = {} ---- Draw a fixed layout. Each widget gets just the space it asks for. +--- Layout a fixed layout. Each widget gets just the space it asks for. -- @param context The context in which we are drawn. --- @param cr The cairo context to use. -- @param width The available width. -- @param height The available height. --- @return The total space needed by the layout. -function fixed:draw(context, cr, width, height) - local pos,spacing = 0,self._spacing or 0 +function fixed:layout(context, width, height) + local result = {} + local pos,spacing = 0, self._spacing for k, v in pairs(self.widgets) do local x, y, w, h, _ @@ -46,16 +44,16 @@ function fixed:draw(context, cr, width, height) (self.dir ~= "y" and pos-spacing > width) then break end - base.draw_widget(context, cr, v, x, y, w, h) + table.insert(result, base.place_widget_at(v, x, y, w, h)) end + return result end --- Add a widget to the given fixed layout function fixed:add(widget) - widget_base.check_widget(widget) + base.check_widget(widget) table.insert(self.widgets, widget) - widget:weak_connect_signal("widget::updated", self._emit_updated) - self._emit_updated() + self:emit_signal("widget::layout_changed") end --- Fit the fixed layout into the given space @@ -91,7 +89,7 @@ function fixed:fit(context, orig_width, orig_height) end end - local spacing = ((self._spacing or 0)*(#self.widgets-1)) + local spacing = self._spacing * (#self.widgets-1) if self.dir == "y" then return used_max, used_in_dir + spacing @@ -101,23 +99,22 @@ end --- Reset a fixed layout. This removes all widgets from the layout. function fixed:reset() - for k, v in pairs(self.widgets) do - v:disconnect_signal("widget::updated", self._emit_updated) - end self.widgets = {} - self:emit_signal("widget::updated") + self:emit_signal("widget::layout_changed") end --- Set the layout's fill_space property. If this property is true, the last -- widget will get all the space that is left. If this is false, the last widget -- won't be handled specially and there can be space left unused. function fixed:fill_space(val) - self._fill_space = val - self:emit_signal("widget::updated") + if self._fill_space ~= val then + self._fill_space = not not val + self:emit_signal("widget::layout_changed") + end end local function get_layout(dir) - local ret = widget_base.make_widget() + local ret = base.make_widget() for k, v in pairs(fixed) do if type(v) == "function" then @@ -127,9 +124,8 @@ local function get_layout(dir) ret.dir = dir ret.widgets = {} - ret._emit_updated = function() - ret:emit_signal("widget::updated") - end + ret:set_spacing(0) + ret:fill_space(false) return ret end @@ -151,8 +147,10 @@ end --- Add spacing between each layout widgets -- @param spacing Spacing between widgets. function fixed:set_spacing(spacing) - self._spacing = spacing - self:emit_signal("widget::updated") + if self._spacing ~= spacing then + self._spacing = spacing + self:emit_signal("widget::layout_changed") + end end return fixed diff --git a/lib/wibox/layout/flex.lua b/lib/wibox/layout/flex.lua index 021867cd..47d983c4 100644 --- a/lib/wibox/layout/flex.lua +++ b/lib/wibox/layout/flex.lua @@ -5,8 +5,7 @@ -- @classmod wibox.layout.flex --------------------------------------------------------------------------- -local base = require("wibox.layout.base") -local widget_base = require("wibox.widget.base") +local base = require("wibox.widget.base") local table = table local pairs = pairs local floor = math.floor @@ -14,14 +13,17 @@ local round = require("awful.util").round local flex = {} ---- Draw a flex layout. Each widget gets an equal share of the available space. +local function round(x) + return floor(x + 0.5) +end + +--- Layout a flex layout. Each widget gets an equal share of the available space. -- @param context The context in which we are drawn. --- @param cr The cairo context to use. -- @param width The available width. -- @param height The available height. --- @return The total space needed by the layout. -function flex:draw(context, cr, width, height) - local pos,spacing = 0,self._spacing or 0 +function flex:layout(context, width, height) + local result = {} + local pos,spacing = 0, self._spacing local num = #self.widgets local total_spacing = (spacing*(num-1)) @@ -45,7 +47,7 @@ function flex:draw(context, cr, width, height) x, y = round(pos), 0 w, h = floor(space_per_item), height end - base.draw_widget(context, cr, v, x, y, w, h) + table.insert(result, base.place_widget_at(v, x, y, w, h)) pos = pos + space_per_item + spacing @@ -54,21 +56,8 @@ function flex:draw(context, cr, width, height) break end end -end -function flex:add(widget) - widget_base.check_widget(widget) - table.insert(self.widgets, widget) - widget:weak_connect_signal("widget::updated", self._emit_updated) - self._emit_updated() -end - ---- Set the maximum size the widgets in this layout will take (that is, --- maximum width for horizontal and maximum height for vertical). --- @param val The maximum size of the widget. -function flex:set_max_widget_size(val) - self._max_widget_size = val - self:emit_signal("widget::updated") + return result end --- Fit the flex layout into the given space. @@ -99,7 +88,7 @@ function flex:fit(context, orig_width, orig_height) #self.widgets * self._max_widget_size) end - local spacing = ((self._spacing or 0)*(#self.widgets-1)) + local spacing = self._spacing * (#self.widgets-1) if self.dir == "y" then return used_in_other, used_in_dir + spacing @@ -107,17 +96,39 @@ function flex:fit(context, orig_width, orig_height) return used_in_dir + spacing, used_in_other end -function flex:reset() - for k, v in pairs(self.widgets) do - v:disconnect_signal("widget::updated", self._emit_updated) +function flex:add(widget) + base.check_widget(widget) + table.insert(self.widgets, widget) + self:emit_signal("widget::layout_changed") +end + +--- Set the maximum size the widgets in this layout will take (that is, +-- maximum width for horizontal and maximum height for vertical). +-- @param val The maximum size of the widget. +function flex:set_max_widget_size(val) + if self._max_widget_size ~= val then + self._max_widget_size = val + self:emit_signal("widget::layout_changed") end +end + +--- Add spacing between each layout widgets +-- @param spacing Spacing between widgets. +function flex:set_spacing(spacing) + if self._spacing ~= spacing then + self._spacing = spacing + self:emit_signal("widget::layout_changed") + end +end + +function flex:reset() self.widgets = {} self._max_widget_size = nil - self:emit_signal("widget::updated") + self:emit_signal("widget::layout_changed") end local function get_layout(dir) - local ret = widget_base.make_widget() + local ret = base.make_widget() for k, v in pairs(flex) do if type(v) == "function" then @@ -127,9 +138,7 @@ local function get_layout(dir) ret.dir = dir ret.widgets = {} - ret._emit_updated = function() - ret:emit_signal("widget::updated") - end + ret:set_spacing(0) return ret end @@ -146,13 +155,6 @@ function flex.vertical() return get_layout("y") end ---- Add spacing between each layout widgets --- @param spacing Spacing between widgets. -function flex:set_spacing(spacing) - self._spacing = spacing - self:emit_signal("widget::updated") -end - return flex -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/wibox/layout/init.lua b/lib/wibox/layout/init.lua index 000b5cfb..98289e08 100644 --- a/lib/wibox/layout/init.lua +++ b/lib/wibox/layout/init.lua @@ -9,7 +9,6 @@ return { - base = require("wibox.layout.base"); fixed = require("wibox.layout.fixed"); align = require("wibox.layout.align"); flex = require("wibox.layout.flex"); diff --git a/lib/wibox/layout/margin.lua b/lib/wibox/layout/margin.lua index 0fc2117e..edb2c8f5 100644 --- a/lib/wibox/layout/margin.lua +++ b/lib/wibox/layout/margin.lua @@ -8,8 +8,7 @@ local pairs = pairs local type = type local setmetatable = setmetatable -local base = require("wibox.layout.base") -local widget_base = require("wibox.widget.base") +local base = require("wibox.widget.base") local gcolor = require("gears.color") local cairo = require("lgi").cairo @@ -28,16 +27,24 @@ function margin:draw(context, cr, width, height) end if color then - cr:save() cr:set_source(color) cr:rectangle(0, 0, width, height) cr:rectangle(x, y, width - x - w, height - y - h) cr:set_fill_rule(cairo.FillRule.EVEN_ODD) cr:fill() - cr:restore() end +end - base.draw_widget(context, cr, self.widget, x, y, width - x - w, height - y - h) +--- Layout a margin layout +function margin:layout(context, width, height) + if self.widget then + local x = self.left + local y = self.top + local w = self.right + local h = self.bottom + + return { base.place_widget_at(self.widget, x, y, width - x - w, height - y - h) } + end end --- Fit a margin layout into the given space @@ -53,15 +60,11 @@ end --- Set the widget that this layout adds a margin on. function margin:set_widget(widget) - if self.widget then - self.widget:disconnect_signal("widget::updated", self._emit_updated) - end if widget then - widget_base.check_widget(widget) - widget:weak_connect_signal("widget::updated", self._emit_updated) + base.check_widget(widget) end self.widget = widget - self._emit_updated() + self:emit_signal("widget::layout_changed") end --- Set all the margins to val. @@ -70,13 +73,13 @@ function margin:set_margins(val) self.right = val self.top = val self.bottom = val - self:emit_signal("widget::updated") + self:emit_signal("widget::layout_changed") end --- Set the margins color to color function margin:set_color(color) self.color = color and gcolor(color) - self._emit_updated() + self:emit_signal("widget::redraw_needed") end --- Reset this layout. The widget will be unreferenced, the margins set to 0 @@ -115,7 +118,7 @@ end for k, v in pairs({ "left", "right", "top", "bottom" }) do margin["set_" .. v] = function(layout, val) layout[v] = val - layout:emit_signal("widget::updated") + layout:emit_signal("widget::layout_changed") end end @@ -127,7 +130,7 @@ end -- @param[opt] bottom A margin to use on the bottom side of the widget. -- @param[opt] color A color for the margins. local function new(widget, left, right, top, bottom, color) - local ret = widget_base.make_widget() + local ret = base.make_widget() for k, v in pairs(margin) do if type(v) == "function" then @@ -135,10 +138,6 @@ local function new(widget, left, right, top, bottom, color) end end - ret._emit_updated = function() - ret:emit_signal("widget::updated") - end - ret:set_left(left or 0) ret:set_right(right or 0) ret:set_top(top or 0) diff --git a/lib/wibox/layout/mirror.lua b/lib/wibox/layout/mirror.lua index b0392409..fcd94230 100644 --- a/lib/wibox/layout/mirror.lua +++ b/lib/wibox/layout/mirror.lua @@ -10,21 +10,20 @@ local error = error local pairs = pairs local ipairs = ipairs local setmetatable = setmetatable -local base = require("wibox.layout.base") -local widget_base = require("wibox.widget.base") +local base = require("wibox.widget.base") +local Matrix = require("lgi").cairo.Matrix local mirror = { mt = {} } ---- Draw this layout -function mirror:draw(context, cr, width, height) +--- Layout this layout +function mirror:layout(context, cr, width, height) if not self.widget then return end if not self.horizontal and not self.vertical then base.draw_widget(wibox, cr, self.widget, 0, 0, width, height) - return -- nothing changed + return end - cr:save() - + local m = Matrix.create_identity() local t = { x = 0, y = 0 } -- translation local s = { x = 1, y = 1 } -- scale if self.horizontal then @@ -35,13 +34,10 @@ function mirror:draw(context, cr, width, height) t.x = width s.x = -1 end - cr:translate(t.x, t.y) - cr:scale(s.x, s.y) + m:translate(t.x, t.y) + m:scale(s.x, s.y) - self.widget:draw(context, cr, width, height) - - -- Undo the scale and translation from above. - cr:restore() + return base.place_widget_via_matrix(widget, m, width, height) end --- Fit this layout into the given area @@ -55,15 +51,11 @@ end --- Set the widget that this layout mirrors. -- @param widget The widget to mirror function mirror:set_widget(widget) - if self.widget then - self.widget:disconnect_signal("widget::updated", self._emit_updated) - end if widget then - widget_base.check_widget(widget) - widget:weak_connect_signal("widget::updated", self._emit_updated) + base.check_widget(widget) end self.widget = widget - self._emit_updated() + self:emit_signal("widget::layout_changed") end --- Reset this layout. The widget will be removed and the axes reset. @@ -85,7 +77,7 @@ function mirror:set_reflection(reflection) self[ref] = reflection[ref] end end - self._emit_updated() + self:emit_signal("widget::layout_changed") end --- Get the reflection of this mirror layout. @@ -101,7 +93,7 @@ end -- @param[opt] widget The widget to display. -- @param[opt] reflection A table describing the reflection to apply. local function new(widget, reflection) - local ret = widget_base.make_widget() + local ret = base.make_widget() ret.horizontal = false ret.vertical = false @@ -111,10 +103,6 @@ local function new(widget, reflection) end end - ret._emit_updated = function() - ret:emit_signal("widget::updated") - end - ret:set_widget(widget) ret:set_reflection(reflection or {}) diff --git a/lib/wibox/layout/rotate.lua b/lib/wibox/layout/rotate.lua index cf929c09..b4772963 100644 --- a/lib/wibox/layout/rotate.lua +++ b/lib/wibox/layout/rotate.lua @@ -11,8 +11,8 @@ local pi = math.pi local type = type local setmetatable = setmetatable local tostring = tostring -local base = require("wibox.layout.base") -local widget_base = require("wibox.widget.base") +local base = require("wibox.widget.base") +local Matrix = require("lgi").cairo.Matrix local rotate = { mt = {} } @@ -24,28 +24,29 @@ local function transform(layout, width, height) return width, height end ---- Draw this layout -function rotate:draw(context, cr, width, height) +--- Layout this layout +function rotate:layout(context, width, height) if not self.widget or not self.widget.visible then return end local dir = self:get_direction() + local m = Matrix.create_identity() if dir == "west" then - cr:rotate(pi / 2) - cr:translate(0, -width) + m:rotate(pi / 2) + m:translate(0, -width) elseif dir == "south" then - cr:rotate(pi) - cr:translate(-width, -height) + m:rotate(pi) + m:translate(-width, -height) elseif dir == "east" then - cr:rotate(3 * pi / 2) - cr:translate(-height, 0) + m:rotate(3 * pi / 2) + m:translate(-height, 0) end -- Since we rotated, we might have to swap width and height. -- transform() does that for us. - base.draw_widget(context, cr, self.widget, 0, 0, transform(self, width, height)) + return { base.place_widget_via_matrix(self.widget, m, transform(self, width, height)) } end --- Fit this layout into the given area @@ -58,15 +59,11 @@ end --- Set the widget that this layout rotates. function rotate:set_widget(widget) - if self.widget then - self.widget:disconnect_signal("widget::updated", self._emit_updated) - end if widget then - widget_base.check_widget(widget) - widget:weak_connect_signal("widget::updated", self._emit_updated) + base.check_widget(widget) end self.widget = widget - self._emit_updated() + self:emit_signal("widget::layout_changed") end --- Reset this layout. The widget will be removed and the rotation reset. @@ -90,7 +87,7 @@ function rotate:set_direction(dir) end self.direction = dir - self._emit_updated() + self:emit_signal("widget::layout_changed") end --- Get the direction of this rotating layout @@ -104,7 +101,7 @@ end -- @param[opt] widget The widget to display. -- @param[opt] dir The direction to rotate to. local function new(widget, dir) - local ret = widget_base.make_widget() + local ret = base.make_widget() for k, v in pairs(rotate) do if type(v) == "function" then @@ -112,10 +109,6 @@ local function new(widget, dir) end end - ret._emit_updated = function() - ret:emit_signal("widget::updated") - end - ret:set_widget(widget) ret:set_direction(dir or "north") diff --git a/lib/wibox/widget/background.lua b/lib/wibox/widget/background.lua index 5847cc3e..cca1c1d0 100644 --- a/lib/wibox/widget/background.lua +++ b/lib/wibox/widget/background.lua @@ -7,7 +7,6 @@ local base = require("wibox.widget.base") local color = require("gears.color") -local layout_base = require("wibox.layout.base") local surface = require("gears.surface") local cairo = require("lgi").cairo local setmetatable = setmetatable @@ -22,8 +21,6 @@ function background:draw(context, cr, width, height) return end - cr:save() - if self.background then cr:set_source(self.background) cr:paint() @@ -33,16 +30,19 @@ function background:draw(context, cr, width, height) cr:set_source(pattern) cr:paint() end +end - cr:restore() - +--- Prepare drawing the children of this widget +function background:before_draw_children(wibox, cr, width, height) if self.foreground then - cr:save() cr:set_source(self.foreground) end - layout_base.draw_widget(context, cr, self.widget, 0, 0, width, height) - if self.foreground then - cr:restore() +end + +--- Layout this widget +function background:layout(context, width, height) + if self.widget then + return { base.place_widget_at(self.widget, 0, 0, width, height) } end end @@ -52,20 +52,16 @@ function background:fit(context, width, height) return 0, 0 end - return layout_base.fit_widget(context, self.widget, width, height) + return base.fit_widget(context, self.widget, width, height) end --- Set the widget that is drawn on top of the background function background:set_widget(widget) - if self.widget then - self.widget:disconnect_signal("widget::updated", self._emit_updated) - end if widget then base.check_widget(widget) - widget:weak_connect_signal("widget::updated", self._emit_updated) end self.widget = widget - self._emit_updated() + self:emit_signal("widget::layout_changed") end --- Set the background to use @@ -75,7 +71,7 @@ function background:set_bg(bg) else self.background = nil end - self._emit_updated() + self:emit_signal("widget::redraw_needed") end --- Set the foreground to use @@ -85,13 +81,13 @@ function background:set_fg(fg) else self.foreground = nil end - self._emit_updated() + self:emit_signal("widget::redraw_needed") end --- Set the background image to use function background:set_bgimage(image) self.bgimage = surface.load(image) - self._emit_updated() + self:emit_signal("widget::redraw_needed") end --- Returns a new background layout. A background layout applies a background @@ -107,10 +103,6 @@ local function new(widget, bg) end end - ret._emit_updated = function() - ret:emit_signal("widget::updated") - end - ret:set_widget(widget) ret:set_bg(bg) diff --git a/lib/wibox/widget/base.lua b/lib/wibox/widget/base.lua index 16f2b8ee..56267066 100644 --- a/lib/wibox/widget/base.lua +++ b/lib/wibox/widget/base.lua @@ -2,13 +2,14 @@ -- @author Uli Schlachter -- @copyright 2010 Uli Schlachter -- @release @AWESOME_VERSION@ --- @classmod wibox.widget.base +-- @module wibox.widget.base --------------------------------------------------------------------------- local debug = require("gears.debug") local object = require("gears.object") local cache = require("gears.cache") local matrix = require("gears.matrix") +local Matrix = require("lgi").cairo.Matrix local setmetatable = setmetatable local pairs = pairs local type = type @@ -16,13 +17,144 @@ local table = table local base = {} +-- {{{ Caches + +local call_stack = {} +-- Indexes are widgets, allow them to be garbage-collected +local widget_dependencies = setmetatable({}, { __mode = "k" }) + +-- Don't do this in unit tests +if awesome and awesome.connect_signal then + -- Reset the call stack at each refresh. This fixes things up in case there was + -- an error in some callback and thus put_cache() wasn't called (if this + -- happens, we possibly recorded too many deps, but so what?) + awesome.connect_signal("refresh", function() + call_stack = {} + end) +end + +-- When you call get_cache_and_record_deps(), the widget is recorded in a stack +-- until the following put_cache(). All other calls to +-- get_cache_and_record_deps() that happen during this will cause a dependency +-- between the widgets that are involved to be recorded. This information is +-- used by clear_caches() to also clear all caches of dependent widgets. + +-- Get the caches for a widget and record its dependencies. All following +-- cache-uses will record this widgets as a dependency. This returns a function +-- that calls the callback of kind `kind` on the widget. +local function get_cache_and_record_deps(widget, kind) + -- Record dependencies (each entry in the call stack depends on `widget`) + local deps = widget_dependencies[widget] or {} + for _, w in pairs(call_stack) do + deps[w] = true + end + widget_dependencies[widget] = deps + + -- Add widget to call stack + table.insert(call_stack, widget) + + -- Create cache if needed + if not widget._widget_caches[kind] then + widget._widget_caches[kind] = cache.new(function(...) + return widget[kind](widget, ...) + end) + end + return widget._widget_caches[kind] +end + +-- Each call to the above function should be followed by a call to this +-- function. Everything in-between is recorded as a dependency (it's +-- complicated...). +local function put_cache(widget) + assert(#call_stack ~= 0) + if table.remove(call_stack) ~= widget then + put_cache(widget) + end +end + +-- Clear the caches for `widget` and all widgets that depend on it. +local function clear_caches(widget) + for w in pairs(widget_dependencies[widget] or {}) do + widget_dependencies[w] = {} + w._widget_caches = {} + end + widget_dependencies[widget] = {} + widget._widget_caches = {} +end +-- }}} + --- Figure out the geometry in device coordinate space. This gives only tight -- bounds if no rotations by non-multiples of 90° are used. function base.rect_to_device_geometry(cr, x, y, width, height) return matrix.transform_rectangle(cr.matrix, x, y, width, height) end ---- Set/get a widget's buttons +--- Fit a widget for the given available width and height. This calls the +-- widget's `:fit` callback and caches the result for later use. Never call +-- `:fit` directly, but always through this function! +-- @param context The context in which we are fit. +-- @param widget The widget to fit (this uses widget:fit(width, height)). +-- @param width The available width for the widget +-- @param height The available height for the widget +-- @return The width and height that the widget wants to use +function base.fit_widget(context, widget, width, height) + if not widget.visible then + return 0, 0 + end + + -- Sanitize the input. This also filters out e.g. NaN. + local width = math.max(0, width) + local height = math.max(0, height) + + local w, h = 0, 0 + if widget.fit then + local cache = get_cache_and_record_deps(widget, "fit") + w, h = cache:get(context, width, height) + put_cache(widget) + else + -- If it has no fit method, calculate based on the size of children + local children = base.layout_widget(context, widget, width, height) + for _, info in ipairs(children or {}) do + local x, y, w2, h2 = matrix.transform_rectangle(info._matrix, + 0, 0, info._width, info._height) + w, h = math.max(w, x + w2), math.max(h, y + h2) + end + end + + -- Also sanitize the output. + w = math.max(0, math.min(w, width)) + h = math.max(0, math.min(h, height)) + return w, h +end + +--- Lay out a widget for the given available width and height. This calls the +-- widget's `:layout` callback and caches the result for later use. Never call +-- `:layout` directly, but always through this function! However, normally there +-- shouldn't be any reason why you need to use this function. +-- @param context The context in which we are laid out. +-- @param widget The widget to layout (this uses widget:layout(context, width, height)). +-- @param width The available width for the widget +-- @param height The available height for the widget +-- @return The result from the widget's `:layout` callback. +function base.layout_widget(context, widget, width, height) + if not widget.visible then + return + end + + -- Sanitize the input. This also filters out e.g. NaN. + local width = math.max(0, width) + local height = math.max(0, height) + + if widget.layout then + local cache = get_cache_and_record_deps(widget, "layout") + local result = cache:get(context, width, height) + put_cache(widget) + return result + end +end + +--- Set/get a widget's buttons. +-- This function is available on widgets created by @{make_widget}. function base:buttons(_buttons) if _buttons then self.widget_buttons = _buttons @@ -31,7 +163,8 @@ function base:buttons(_buttons) return self.widget_buttons end ---- Handle a button event on a widget. This is used internally. +-- Handle a button event on a widget. This is used internally and should not be +-- called directly. function base.handle_button(event, widget, x, y, button, modifiers, geometry) local function is_any(mod) return #mod == 1 and mod[1] == "Any" @@ -68,23 +201,174 @@ function base.handle_button(event, widget, x, y, button, modifiers, geometry) end 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. --- @param proxy If this is set, the returned widget will be a proxy for this --- widget. It will be equivalent to this widget. --- @tparam[opt] string widget_name Name of the widget. If not set, it will be --- set automatically via `gears.object.modulename`. +--- Create widget placement information. This should be used for a widget's +-- `:layout()` callback. +-- @param widget The widget that should be placed. +-- @param mat A cairo matrix transforming from the parent widget's coordinate +-- system. For example, use cairo.Matrix.create_translate(1, 2) to draw a +-- widget at position (1, 2) relative to the parent widget. +-- @param width The width of the widget in its own coordinate system. That is, +-- after applying the transformation matrix. +-- @param height The height of the widget in its own coordinate system. That is, +-- after applying the transformation matrix. +-- @return An opaque object that can be returned from :layout() +function base.place_widget_via_matrix(widget, mat, width, height) + return { + _widget = widget, + _width = width, + _height = height, + _matrix = matrix.copy(mat) + } +end + +--- Create widget placement information. This should be used for a widget's +-- `:layout()` callback. +-- @param widget The widget that should be placed. +-- @param x The x coordinate for the widget. +-- @param y The y coordinate for the widget. +-- @param width The width of the widget in its own coordinate system. That is, +-- after applying the transformation matrix. +-- @param height The height of the widget in its own coordinate system. That is, +-- after applying the transformation matrix. +-- @return An opaque object that can be returned from :layout() +function base.place_widget_at(widget, x, y, width, height) + return base.place_widget_via_matrix(widget, Matrix.create_translate(x, y), width, height) +end + +--[[-- +Create a new widget. All widgets have to be generated via this function so that +the needed signals are added and mouse input handling is set up. + +The returned widget will have a :buttons member function that can be used to +register a set of mouse button events with the widget. + +To implement your own widget, you can implement some member functions on a +freshly-created widget. Note that all of these functions should be deterministic +in the sense that they will show the same behavior if they are repeatedly called +with the same arguments (same width and height). If your widget is updated and +needs to change, suitable signals have to be emitted. This will be explained +later. + +The first callback is :fit. This function is called to select the size of your +widget. The arguments to this function is the available space and it should +return its desired size. Note that this function only provides a hint which is +not necessarily followed. The widget must also be able to draw itself at +different sizes than the one requested. +
function widget:fit(context, width, height)
+  -- Find the maximum square available
+  local m = math.min(width, height)
+  return m, m
+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(wibox, 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(wibox, cr, width, height)
+    cr:push_group()
+end
+function widget:after_draw_children(wibox, 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(wibox, cr, width, height)
+widget:before_draw_children(wibox, cr, width, height)
+for child do
+    widget:before_draw_child(wibox, 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(wibox, cr, child_index, child, width, height)
+end
+widget:after_draw_children(wibox, 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 +--]]-- function base.make_widget(proxy, widget_name) local ret = object() -- This signal is used by layouts to find out when they have to update. - ret:add_signal("widget::updated") + ret:add_signal("widget::layout_changed") + ret:add_signal("widget::redraw_needed") -- Mouse input, oh noes! ret:add_signal("button::press") ret:add_signal("button::release") ret:add_signal("mouse::enter") ret:add_signal("mouse::leave") + -- Backwards compatibility + -- TODO: Remove this + ret:add_signal("widget::updated") + ret:connect_signal("widget::updated", function() + ret:emit_signal("widget::layout_changed") + ret:emit_signal("widget::redraw_needed") + end) + -- No buttons yet ret.widget_buttons = {} ret.buttons = base.buttons @@ -98,20 +382,24 @@ function base.make_widget(proxy, widget_name) end) if proxy then - ret.draw = function(_, ...) return proxy:draw(...) end - ret.fit = function(_, ...) return proxy:fit(...) end - proxy:connect_signal("widget::updated", function() - ret:emit_signal("widget::updated") + ret.fit = function(_, context, width, height) + return base.fit_widget(context, proxy, width, height) + end + ret.layout = function(_, context, width, height) + return { base.place_widget_at(proxy, 0, 0, width, height) } + end + proxy:connect_signal("widget::layout_changed", function() + ret:emit_signal("widget::layout_changed") + end) + proxy:connect_signal("widget::redraw_needed", function() + ret:emit_signal("widget::redraw_needed") end) end - -- Add a geometry for base.fit_widget() that is cleared when necessary - local function cb(...) - return ret:fit(...) - end - ret._fit_geometry_cache = cache.new(cb) - ret:connect_signal("widget::updated", function() - ret._fit_geometry_cache = cache.new(cb) + -- Set up caches + clear_caches(ret) + ret:connect_signal("widget::layout_changed", function() + clear_caches(ret) end) -- Add visible property and setter. @@ -119,7 +407,9 @@ function base.make_widget(proxy, widget_name) function ret:set_visible(b) if b ~= self.visible then self.visible = b - self:emit_signal("widget::updated") + self:emit_signal("widget::layout_changed") + -- In case something ignored fit and drew the widget anyway + self:emit_signal("widget::redraw_needed") end end @@ -128,7 +418,7 @@ function base.make_widget(proxy, widget_name) function ret:set_opacity(b) if b ~= self.opacity then self.opacity = b - self:emit_signal("widget::updated") + self:emit_signal("widget::redraw") end end @@ -144,23 +434,16 @@ end --- Generate an empty widget which takes no space and displays nothing function base.empty_widget() - local widget = base.make_widget() - widget.draw = function() end - widget.fit = function() return 0, 0 end - return widget + return base.make_widget() end --- Do some sanity checking on widget. This function raises a lua error if -- widget is not a valid widget. function base.check_widget(widget) debug.assert(type(widget) == "table") - for k, func in pairs({ "draw", "fit", "add_signal", "connect_signal", "disconnect_signal" }) do + for k, func in pairs({ "add_signal", "connect_signal", "disconnect_signal" }) do debug.assert(type(widget[func]) == "function", func .. " is not a function") end - - local width, height = widget:fit({}, 0, 0) - debug.assert(type(width) == "number") - debug.assert(type(height) == "number") end return base diff --git a/lib/wibox/widget/imagebox.lua b/lib/wibox/widget/imagebox.lua index e623525d..34d46335 100644 --- a/lib/wibox/widget/imagebox.lua +++ b/lib/wibox/widget/imagebox.lua @@ -20,8 +20,6 @@ function imagebox:draw(context, cr, width, height) if not self._image then return end if width == 0 or height == 0 then return end - cr:save() - if not self.resize_forbidden then -- Let's scale the image so that it fits into (width, height) local w = self._image:get_width() @@ -34,8 +32,6 @@ function imagebox:draw(context, cr, width, height) end cr:set_source_surface(self._image, 0, 0) cr:paint() - - cr:restore() end --- Fit the imagebox into the given geometry @@ -99,9 +95,14 @@ function imagebox:set_image(image) end end + if self._image == image then + return + end + self._image = image - self:emit_signal("widget::updated") + self:emit_signal("widget::redraw_needed") + self:emit_signal("widget::layout_changed") return true end @@ -110,7 +111,8 @@ end -- to fit into the available space. function imagebox:set_resize(allowed) self.resize_forbidden = not allowed - self:emit_signal("widget::updated") + self:emit_signal("widget::redraw_needed") + self:emit_signal("widget::layout_changed") end --- Returns a new imagebox diff --git a/lib/wibox/widget/systray.lua b/lib/wibox/widget/systray.lua index a7e9d3b8..011edd10 100644 --- a/lib/wibox/widget/systray.lua +++ b/lib/wibox/widget/systray.lua @@ -79,7 +79,7 @@ local function new(revers) end capi.awesome.connect_signal("systray::update", function() - ret:emit_signal("widget::updated") + ret:emit_signal("widget::layout_changed") end) return ret diff --git a/lib/wibox/widget/textbox.lua b/lib/wibox/widget/textbox.lua index a26e9304..dfa39e52 100644 --- a/lib/wibox/widget/textbox.lua +++ b/lib/wibox/widget/textbox.lua @@ -70,7 +70,8 @@ function textbox:set_markup(text) self._markup = text self._layout.text = parsed self._layout.attributes = attr - self:emit_signal("widget::updated") + self:emit_signal("widget::redraw_needed") + self:emit_signal("widget::layout_changed") end --- Set a textbox' text. @@ -82,7 +83,8 @@ function textbox:set_text(text) self._markup = nil self._layout.text = text self._layout.attributes = nil - self:emit_signal("widget::updated") + self:emit_signal("widget::redraw_needed") + self:emit_signal("widget::layout_changed") end --- Set a textbox' ellipsize mode. @@ -94,7 +96,8 @@ function textbox:set_ellipsize(mode) return end self._layout:set_ellipsize(allowed[mode]) - self:emit_signal("widget::updated") + self:emit_signal("widget::redraw_needed") + self:emit_signal("widget::layout_changed") end end @@ -107,7 +110,8 @@ function textbox:set_wrap(mode) return end self._layout:set_wrap(allowed[mode]) - self:emit_signal("widget::updated") + self:emit_signal("widget::redraw_needed") + self:emit_signal("widget::layout_changed") end end @@ -120,7 +124,8 @@ function textbox:set_valign(mode) return end self._valign = mode - self:emit_signal("widget::updated") + self:emit_signal("widget::redraw_needed") + self:emit_signal("widget::layout_changed") end end @@ -133,7 +138,8 @@ function textbox:set_align(mode) return end self._layout:set_alignment(allowed[mode]) - self:emit_signal("widget::updated") + self:emit_signal("widget::redraw_needed") + self:emit_signal("widget::layout_changed") end end @@ -141,6 +147,8 @@ end -- @param font The font description as string function textbox:set_font(font) self._layout:set_font_description(beautiful.get_font(font)) + self:emit_signal("widget::redraw_needed") + self:emit_signal("widget::layout_changed") end -- Returns a new textbox diff --git a/spec/wibox/hierarchy_spec.lua b/spec/wibox/hierarchy_spec.lua new file mode 100644 index 00000000..98a1abad --- /dev/null +++ b/spec/wibox/hierarchy_spec.lua @@ -0,0 +1,234 @@ +--------------------------------------------------------------------------- +-- @author Uli Schlachter +-- @copyright 2015 Uli Schlachter +--------------------------------------------------------------------------- + +local hierarchy = require("wibox.hierarchy") + +local cairo = require("lgi").cairo +local matrix = require("gears.matrix") +local object = require("gears.object") +local utils = require("wibox.test_utils") + +local function make_widget(children) + local result = utils.widget_stub() + result.layout = function() + return children + end + return result +end + +local function make_child(widget, width, height, matrix) + return { _widget = widget, _width = width, _height = height, _matrix = matrix } +end + +describe("wibox.hierarchy", function() + describe("Accessor functions", function() + local widget, instance + before_each(function() + local function nop() end + local context = {} + widget = make_widget(nil) + instance = hierarchy.new(context, widget, 10, 20, nop, nop) + end) + + it("get_widget", function() + assert.is.equal(instance:get_widget(), widget) + end) + + it("get_matrix_to_parent", function() + assert.is_true(matrix.equals(cairo.Matrix.create_identity(), + instance:get_matrix_to_parent())) + end) + + it("get_matrix_to_device", function() + assert.is_true(matrix.equals(cairo.Matrix.create_identity(), + instance:get_matrix_to_device())) + end) + + it("get_matrix_from_parent", function() + assert.is_true(matrix.equals(cairo.Matrix.create_identity(), + instance:get_matrix_from_parent())) + end) + + it("get_matrix_from_device", function() + assert.is_true(matrix.equals(cairo.Matrix.create_identity(), + instance:get_matrix_from_device())) + end) + + it("get_draw_extents", function() + assert.is.same({ instance:get_draw_extents() }, { 0, 0, 10, 20 }) + end) + + it("get_size", function() + assert.is.same({ instance:get_size() }, { 10, 20 }) + end) + + it("get_children", function() + assert.is.same(instance:get_children(), {}) + end) + end) + + it("disconnect works", function() + local child = make_widget(nil) + local parent = make_widget({ + make_child(child, 2, 5, cairo.Matrix.create_translate(10, 0)) + }) + + local extra_arg = {} + local child_redraws, child_layouts = 0, 0 + local parent_redraws, parent_layouts = 0, 0 + local function redraw(arg, extra) + assert.is.equal(extra_arg, extra) + if arg:get_widget() == child then + child_redraws = child_redraws + 1 + elseif arg:get_widget() == parent then + parent_redraws = parent_redraws + 1 + else + error("Unknown widget") + end + end + local function layout(arg, extra) + assert.is.equal(extra_arg, extra) + if arg:get_widget() == child then + child_layouts = child_layouts + 1 + elseif arg:get_widget() == parent then + parent_layouts = parent_layouts + 1 + else + error("Unknown widget") + end + end + local context = {} + local instance = hierarchy.new(context, parent, 15, 20, redraw, layout, extra_arg) + + -- There should be a connection + parent:emit_signal("widget::redraw_needed") + assert.is.same({ 0, 0, 1, 0 }, { child_redraws, child_layouts, parent_redraws, parent_layouts }) + child:emit_signal("widget::redraw_needed") + assert.is.same({ 1, 0, 1, 0 }, { child_redraws, child_layouts, parent_redraws, parent_layouts }) + child:emit_signal("widget::layout_changed") + assert.is.same({ 1, 1, 1, 0 }, { child_redraws, child_layouts, parent_redraws, parent_layouts }) + parent:emit_signal("widget::layout_changed") + assert.is.same({ 1, 1, 1, 1 }, { child_redraws, child_layouts, parent_redraws, parent_layouts }) + + -- Garbage-collect the hierarchy + instance = nil + collectgarbage("collect") + + -- No connections should be left + parent:emit_signal("widget::redraw_needed") + child:emit_signal("widget::redraw_needed") + child:emit_signal("widget::layout_changed") + parent:emit_signal("widget::layout_changed") + assert.is.same({ 1, 1, 1, 1 }, { child_redraws, child_layouts, parent_redraws, parent_layouts }) + end) + + describe("children", function() + local child, intermediate, parent + local hierarchy_child, hierarchy_intermediate, hierarchy_parent + before_each(function() + child = make_widget(nil) + intermediate = make_widget({ + make_child(child, 10, 20, cairo.Matrix.create_translate(0, 5)) + }) + parent = make_widget({ + make_child(intermediate, 5, 2, cairo.Matrix.create_translate(4, 0)) + }) + + local function nop() end + local context = {} + hierarchy_parent = hierarchy.new(context, parent, 15, 16, nop, nop) + + -- This also tests get_children + local children = hierarchy_parent:get_children() + assert.is.equal(#children, 1) + hierarchy_intermediate = children[1] + + local children = hierarchy_intermediate:get_children() + assert.is.equal(#children, 1) + hierarchy_child = children[1] + end) + + it("get_widget", function() + assert.is.equal(hierarchy_child:get_widget(), child) + assert.is.equal(hierarchy_intermediate:get_widget(), intermediate) + assert.is.equal(hierarchy_parent:get_widget(), parent) + end) + + it("get_matrix_to_parent", function() + assert.is_true(matrix.equals(hierarchy_child:get_matrix_to_parent(), cairo.Matrix.create_translate(0, 5))) + assert.is_true(matrix.equals(hierarchy_intermediate:get_matrix_to_parent(), cairo.Matrix.create_translate(4, 0))) + assert.is_true(matrix.equals(hierarchy_parent:get_matrix_to_parent(), cairo.Matrix.create_identity())) + end) + + it("get_matrix_to_device", function() + assert.is_true(matrix.equals(hierarchy_child:get_matrix_to_device(), cairo.Matrix.create_translate(4, 5))) + assert.is_true(matrix.equals(hierarchy_intermediate:get_matrix_to_device(), cairo.Matrix.create_translate(4, 0))) + assert.is_true(matrix.equals(hierarchy_parent:get_matrix_to_device(), cairo.Matrix.create_identity())) + end) + + it("get_matrix_from_parent", function() + assert.is_true(matrix.equals(hierarchy_child:get_matrix_from_parent(), cairo.Matrix.create_translate(0, -5))) + assert.is_true(matrix.equals(hierarchy_intermediate:get_matrix_from_parent(), cairo.Matrix.create_translate(-4, 0))) + assert.is_true(matrix.equals(hierarchy_parent:get_matrix_from_parent(), cairo.Matrix.create_identity())) + end) + + it("get_matrix_from_device", function() + assert.is_true(matrix.equals(hierarchy_child:get_matrix_from_device(), cairo.Matrix.create_translate(-4, -5))) + assert.is_true(matrix.equals(hierarchy_intermediate:get_matrix_from_device(), cairo.Matrix.create_translate(-4, 0))) + assert.is_true(matrix.equals(hierarchy_parent:get_matrix_from_device(), cairo.Matrix.create_identity())) + end) + + it("get_draw_extents", function() + assert.is.same({ hierarchy_child:get_draw_extents() }, { 0, 0, 10, 20 }) + assert.is.same({ hierarchy_intermediate:get_draw_extents() }, { 0, 0, 10, 25 }) + assert.is.same({ hierarchy_parent:get_draw_extents() }, { 0, 0, 15, 25 }) + end) + + it("get_size", function() + assert.is.same({ hierarchy_child:get_size() }, { 10, 20 }) + assert.is.same({ hierarchy_intermediate:get_size() }, { 5, 2 }) + assert.is.same({ hierarchy_parent:get_size() }, { 15, 16 }) + end) + end) + + describe("find_differences", function() + local child, intermediate, parent + local instance + local function nop() end + before_each(function() + child = make_widget(nil) + intermediate = make_widget({ + make_child(child, 10, 20, cairo.Matrix.create_translate(0, 5)) + }) + parent = make_widget({ + make_child(intermediate, 5, 2, cairo.Matrix.create_translate(4, 0)) + }) + + local context = {} + instance = hierarchy.new(context, parent, 15, 16, nop, nop) + end) + + it("No difference", function() + local context = {} + local instance2 = hierarchy.new(context, parent, 15, 16, nop, nop) + local region = instance:find_differences(instance2) + assert.is.equal(region:num_rectangles(), 0) + end) + + it("child moved", function() + intermediate.layout = function() + return { make_child(child, 10, 20, cairo.Matrix.create_translate(0, 4)) } + end + local context = {} + local instance2 = hierarchy.new(context, parent, 15, 16, nop, nop) + local region = instance:find_differences(instance2) + assert.is.equal(region:num_rectangles(), 1) + local rect = region:get_rectangle(0) + -- The widget drew to 4, 5, 10, 20 before and 4, 4, 10, 20 after + assert.is.same({ rect.x, rect.y, rect.width, rect.height }, { 4, 4, 10, 21 }) + end) + end) +end) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/spec/wibox/layout/align_spec.lua b/spec/wibox/layout/align_spec.lua index 4ea7772d..81ad7a40 100644 --- a/spec/wibox/layout/align_spec.lua +++ b/spec/wibox/layout/align_spec.lua @@ -5,11 +5,9 @@ local align = require("wibox.layout.align") local utils = require("wibox.test_utils") +local p = require("wibox.widget.base").place_widget_at -describe("wibox.layout.flex", function() - before_each(utils.stub_draw_widget) - after_each(utils.revert_draw_widget) - +describe("wibox.layout.align", function() describe("expand=none", function() local layout before_each(function() @@ -21,9 +19,8 @@ describe("wibox.layout.flex", function() assert.widget_fit(layout, { 10, 10 }, { 0, 0 }) end) - it("empty layout draw", function() - layout:draw(nil, nil, 0, 0) - utils.check_widgets_drawn({}) + it("empty layout layout", function() + assert.is.same({}, layout:layout(0, 0)) end) describe("with widgets", function() @@ -44,12 +41,11 @@ describe("wibox.layout.flex", function() assert.widget_fit(layout, { 100, 100 }, { 15, 35 }) end) - it("draw", function() - layout:draw("wibox", "cr", 100, 100) - utils.check_widgets_drawn({ - { first, 0, 0, 100, 10 }, - { third, 0, 90, 100, 10 }, - { second, 0, 42, 100, 15 }, + it("layout", function() + assert.widget_layout(layout, { 100, 100 }, { + p(first, 0, 0, 100, 10), + p(third, 0, 90, 100, 10), + p(second, 0, 42, 100, 15), }) end) end) @@ -59,12 +55,11 @@ describe("wibox.layout.flex", function() assert.widget_fit(layout, { 5, 100 }, { 5, 35 }) end) - it("draw", function() - layout:draw("wibox", "cr", 5, 100) - utils.check_widgets_drawn({ - { first, 0, 0, 5, 10 }, - { third, 0, 90, 5, 10 }, - { second, 0, 42, 5, 15 }, + it("layout", function() + assert.widget_layout(layout, { 5, 100 }, { + p(first, 0, 0, 5, 10), + p(third, 0, 90, 5, 10), + p(second, 0, 42, 5, 15), }) end) end) @@ -74,12 +69,11 @@ describe("wibox.layout.flex", function() assert.widget_fit(layout, { 100, 20 }, { 15, 20 }) end) - it("draw", function() - layout:draw("wibox", "cr", 100, 20) - utils.check_widgets_drawn({ - { first, 0, 0, 100, 2 }, - { third, 0, 18, 100, 2 }, - { second, 0, 2, 100, 15 }, + it("layout", function() + assert.widget_layout(layout, { 100, 20 }, { + p(first, 0, 0, 100, 2), + p(third, 0, 18, 100, 2), + p(second, 0, 2, 100, 15), }) end) end) @@ -97,9 +91,8 @@ describe("wibox.layout.flex", function() assert.widget_fit(layout, { 10, 10 }, { 0, 0 }) end) - it("empty layout draw", function() - layout:draw(nil, nil, 0, 0) - utils.check_widgets_drawn({}) + it("empty layout layout", function() + assert.widget_layout(layout, { 0, 0 }, {}) end) describe("with widgets", function() @@ -120,12 +113,11 @@ describe("wibox.layout.flex", function() assert.widget_fit(layout, { 100, 100 }, { 15, 35 }) end) - it("draw", function() - layout:draw("wibox", "cr", 100, 100) - utils.check_widgets_drawn({ - { first, 0, 0, 100, 42 }, - { third, 0, 58, 100, 42 }, - { second, 0, 42, 100, 15 }, + it("layout", function() + assert.widget_layout(layout, { 100, 100 }, { + p(first, 0, 0, 100, 42), + p(third, 0, 58, 100, 42), + p(second, 0, 42, 100, 15), }) end) end) @@ -135,12 +127,11 @@ describe("wibox.layout.flex", function() assert.widget_fit(layout, { 5, 100 }, { 5, 35 }) end) - it("draw", function() - layout:draw("wibox", "cr", 5, 100) - utils.check_widgets_drawn({ - { first, 0, 0, 5, 42 }, - { third, 0, 58, 5, 42 }, - { second, 0, 42, 5, 15 }, + it("layout", function() + assert.widget_layout(layout, { 5, 100 }, { + p(first, 0, 0, 5, 42), + p(third, 0, 58, 5, 42), + p(second, 0, 42, 5, 15), }) end) end) @@ -150,12 +141,11 @@ describe("wibox.layout.flex", function() assert.widget_fit(layout, { 100, 20 }, { 15, 20 }) end) - it("draw", function() - layout:draw("wibox", "cr", 100, 20) - utils.check_widgets_drawn({ - { first, 0, 0, 100, 2 }, - { third, 0, 18, 100, 2 }, - { second, 0, 2, 100, 15 }, + it("layout", function() + assert.widget_layout(layout, { 100, 20 }, { + p(first, 0, 0, 100, 2), + p(third, 0, 18, 100, 2), + p(second, 0, 2, 100, 15), }) end) end) @@ -173,9 +163,8 @@ describe("wibox.layout.flex", function() assert.widget_fit(layout, { 10, 10 }, { 0, 0 }) end) - it("empty layout draw", function() - layout:draw(nil, nil, 0, 0) - utils.check_widgets_drawn({}) + it("empty layout layout", function() + assert.widget_layout(layout, { 0, 0 }, {}) end) describe("with widgets", function() @@ -196,12 +185,11 @@ describe("wibox.layout.flex", function() assert.widget_fit(layout, { 100, 100 }, { 15, 35 }) end) - it("draw", function() - layout:draw("wibox", "cr", 100, 100) - utils.check_widgets_drawn({ - { first, 0, 0, 100, 10 }, - { third, 0, 90, 100, 10 }, - { second, 0, 10, 100, 80 }, + it("layout", function() + assert.widget_layout(layout, { 100, 100 }, { + p(first, 0, 0, 100, 10), + p(third, 0, 90, 100, 10), + p(second, 0, 10, 100, 80), }) end) end) @@ -211,12 +199,11 @@ describe("wibox.layout.flex", function() assert.widget_fit(layout, { 5, 100 }, { 5, 35 }) end) - it("draw", function() - layout:draw("wibox", "cr", 5, 100) - utils.check_widgets_drawn({ - { first, 0, 0, 5, 10 }, - { third, 0, 90, 5, 10 }, - { second, 0, 10, 5, 80 }, + it("layout", function() + assert.widget_layout(layout, { 5, 100 }, { + p(first, 0, 0, 5, 10), + p(third, 0, 90, 5, 10), + p(second, 0, 10, 5, 80), }) end) end) @@ -226,17 +213,59 @@ describe("wibox.layout.flex", function() assert.widget_fit(layout, { 100, 20 }, { 15, 20 }) end) - it("draw", function() - layout:draw("wibox", "cr", 100, 20) - --- XXX: Shouldn't this also draw part of the second widget? - utils.check_widgets_drawn({ - { first, 0, 0, 100, 10 }, - { third, 0, 10, 100, 10 }, + it("layout", function() + assert.widget_layout(layout, { 100, 20 }, { + p(first, 0, 0, 100, 10), + p(third, 0, 10, 100, 10), }) end) end) end) end) + + describe("emitting signals", function() + local layout, layout_changed + before_each(function() + layout = align.vertical() + layout:connect_signal("widget::layout_changed", function() + layout_changed = layout_changed + 1 + end) + layout_changed = 0 + end) + + it("set first", function() + local w1, w2 = {}, {} + assert.is.equal(layout_changed, 0) + layout:set_first(w1) + assert.is.equal(layout_changed, 1) + layout:set_first(w2) + assert.is.equal(layout_changed, 2) + layout:set_first(w2) + assert.is.equal(layout_changed, 2) + end) + + it("set second", function() + local w1, w2 = {}, {} + assert.is.equal(layout_changed, 0) + layout:set_second(w1) + assert.is.equal(layout_changed, 1) + layout:set_second(w2) + assert.is.equal(layout_changed, 2) + layout:set_second(w2) + assert.is.equal(layout_changed, 2) + end) + + it("set third", function() + local w1, w2 = {}, {} + assert.is.equal(layout_changed, 0) + layout:set_third(w1) + assert.is.equal(layout_changed, 1) + layout:set_third(w2) + assert.is.equal(layout_changed, 2) + layout:set_third(w2) + assert.is.equal(layout_changed, 2) + end) + end) end) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/spec/wibox/layout/fixed_spec.lua b/spec/wibox/layout/fixed_spec.lua index 744ffb8c..0e5c1a85 100644 --- a/spec/wibox/layout/fixed_spec.lua +++ b/spec/wibox/layout/fixed_spec.lua @@ -4,7 +4,9 @@ --------------------------------------------------------------------------- local fixed = require("wibox.layout.fixed") +local base = require("wibox.widget.base") local utils = require("wibox.test_utils") +local p = require("wibox.widget.base").place_widget_at describe("wibox.layout.fixed", function() local layout @@ -12,16 +14,12 @@ describe("wibox.layout.fixed", function() layout = fixed.vertical() end) - before_each(utils.stub_draw_widget) - after_each(utils.revert_draw_widget) - it("empty layout fit", function() assert.widget_fit(layout, { 10, 10 }, { 0, 0 }) end) - it("empty layout draw", function() - layout:draw(nil, nil, 0, 0) - utils.check_widgets_drawn({}) + it("empty layout layout", function() + assert.widget_layout(layout, { 0, 0 }, {}) end) describe("with widgets", function() @@ -42,12 +40,11 @@ describe("wibox.layout.fixed", function() assert.widget_fit(layout, { 100, 100 }, { 15, 35 }) end) - it("draw", function() - layout:draw("wibox", "cr", 100, 100) - utils.check_widgets_drawn({ - { first, 0, 0, 100, 10 }, - { second, 0, 10, 100, 15 }, - { third, 0, 25, 100, 10 }, + it("layout", function() + assert.widget_layout(layout, { 100, 100 }, { + p(first, 0, 0, 100, 10), + p(second, 0, 10, 100, 15), + p(third, 0, 25, 100, 10), }) end) end) @@ -57,32 +54,82 @@ describe("wibox.layout.fixed", function() assert.widget_fit(layout, { 5, 100 }, { 5, 35 }) end) - it("draw", function() - layout:draw("wibox", "cr", 5, 100) - utils.check_widgets_drawn({ - { first, 0, 0, 5, 10 }, - { second, 0, 10, 5, 15 }, - { third, 0, 25, 5, 10 }, + it("layout", function() + assert.widget_layout(layout, { 5, 100 }, { + p(first, 0, 0, 5, 10), + p(second, 0, 10, 5, 15), + p(third, 0, 25, 5, 10), }) end) end) describe("without enough width", function() it("fit", function() - -- XXX: Is this really what should happen? assert.widget_fit(layout, { 100, 20 }, { 15, 20 }) end) - it("draw", function() - layout:draw("wibox", "cr", 100, 20) - utils.check_widgets_drawn({ - { first, 0, 0, 100, 10 }, - { second, 0, 10, 100, 10 }, - { third, 0, 20, 100, 0 }, + it("layout", function() + assert.widget_layout(layout, { 100, 20 }, { + p(first, 0, 0, 100, 10), + p(second, 0, 10, 100, 10), + p(third, 0, 20, 100, 0), }) end) end) end) + + describe("emitting signals", function() + local layout_changed + before_each(function() + layout:connect_signal("widget::layout_changed", function() + layout_changed = layout_changed + 1 + end) + layout_changed = 0 + end) + + it("add", function() + local w1, w2 = base.empty_widget(), base.empty_widget() + assert.is.equal(layout_changed, 0) + layout:add(w1) + assert.is.equal(layout_changed, 1) + layout:add(w2) + assert.is.equal(layout_changed, 2) + layout:add(w2) + assert.is.equal(layout_changed, 3) + end) + + it("set_spacing", function() + assert.is.equal(layout_changed, 0) + layout:set_spacing(0) + assert.is.equal(layout_changed, 0) + layout:set_spacing(5) + assert.is.equal(layout_changed, 1) + layout:set_spacing(2) + assert.is.equal(layout_changed, 2) + layout:set_spacing(2) + assert.is.equal(layout_changed, 2) + end) + + it("reset", function() + assert.is.equal(layout_changed, 0) + layout:add(base.make_widget()) + assert.is.equal(layout_changed, 1) + layout:reset() + assert.is.equal(layout_changed, 2) + end) + + it("fill_space", function() + assert.is.equal(layout_changed, 0) + layout:fill_space(false) + assert.is.equal(layout_changed, 0) + layout:fill_space(true) + assert.is.equal(layout_changed, 1) + layout:fill_space(true) + assert.is.equal(layout_changed, 1) + layout:fill_space(false) + assert.is.equal(layout_changed, 2) + end) + end) end) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/spec/wibox/layout/flex_spec.lua b/spec/wibox/layout/flex_spec.lua index a324861c..ac83500a 100644 --- a/spec/wibox/layout/flex_spec.lua +++ b/spec/wibox/layout/flex_spec.lua @@ -4,7 +4,9 @@ --------------------------------------------------------------------------- local flex = require("wibox.layout.flex") +local base = require("wibox.widget.base") local utils = require("wibox.test_utils") +local p = require("wibox.widget.base").place_widget_at describe("wibox.layout.flex", function() local layout @@ -12,16 +14,12 @@ describe("wibox.layout.flex", function() layout = flex.vertical() end) - before_each(utils.stub_draw_widget) - after_each(utils.revert_draw_widget) - it("empty layout fit", function() assert.widget_fit(layout, { 10, 10 }, { 0, 0 }) - utils.check_widgets_drawn({}) end) - it("empty layout draw", function() - layout:draw(nil, nil, 0, 0) + it("empty layout layout", function() + assert.widget_layout(layout, { 0, 0 }, {}) end) describe("with widgets", function() @@ -42,12 +40,11 @@ describe("wibox.layout.flex", function() assert.widget_fit(layout, { 100, 100 }, { 15, 35 }) end) - it("draw", function() - layout:draw("wibox", "cr", 100, 100) - utils.check_widgets_drawn({ - { first, 0, 0, 100, 33 }, - { second, 0, 33, 100, 33 }, - { third, 0, 67, 100, 33 }, + it("layout", function() + assert.widget_layout(layout, { 100, 100 }, { + p(first, 0, 0, 100, 33), + p(second, 0, 33, 100, 33), + p(third, 0, 67, 100, 33), }) end) end) @@ -57,12 +54,11 @@ describe("wibox.layout.flex", function() assert.widget_fit(layout, { 5, 100 }, { 5, 35 }) end) - it("draw", function() - layout:draw("wibox", "cr", 5, 100) - utils.check_widgets_drawn({ - { first, 0, 0, 5, 33 }, - { second, 0, 33, 5, 33 }, - { third, 0, 67, 5, 33 }, + it("layout", function() + assert.widget_layout(layout, { 5, 100 }, { + p(first, 0, 0, 5, 33), + p(second, 0, 33, 5, 33), + p(third, 0, 67, 5, 33), }) end) end) @@ -72,16 +68,68 @@ describe("wibox.layout.flex", function() assert.widget_fit(layout, { 100, 20 }, { 15, 20 }) end) - it("draw", function() - layout:draw("wibox", "cr", 100, 20) - utils.check_widgets_drawn({ - { first, 0, 0, 100, 6 }, - { second, 0, 7, 100, 6 }, - { third, 0, 13, 100, 6 }, + it("layout", function() + assert.widget_layout(layout, { 100, 20 }, { + p(first, 0, 0, 100, 6), + p(second, 0, 7, 100, 6), + p(third, 0, 13, 100, 6), }) end) end) end) + + describe("emitting signals", function() + local layout_changed + before_each(function() + layout:connect_signal("widget::layout_changed", function() + layout_changed = layout_changed + 1 + end) + layout_changed = 0 + end) + + it("add", function() + local w1, w2 = base.empty_widget(), base.empty_widget() + assert.is.equal(layout_changed, 0) + layout:add(w1) + assert.is.equal(layout_changed, 1) + layout:add(w2) + assert.is.equal(layout_changed, 2) + layout:add(w2) + assert.is.equal(layout_changed, 3) + end) + + it("reset", function() + assert.is.equal(layout_changed, 0) + layout:add(base.make_widget()) + assert.is.equal(layout_changed, 1) + layout:reset() + assert.is.equal(layout_changed, 2) + end) + + it("set_spacing", function() + assert.is.equal(layout_changed, 0) + layout:set_spacing(0) + assert.is.equal(layout_changed, 0) + layout:set_spacing(5) + assert.is.equal(layout_changed, 1) + layout:set_spacing(2) + assert.is.equal(layout_changed, 2) + layout:set_spacing(2) + assert.is.equal(layout_changed, 2) + end) + + it("set_max_widget_size", function() + assert.is.equal(layout_changed, 0) + layout:set_max_widget_size(nil) + assert.is.equal(layout_changed, 0) + layout:set_max_widget_size(20) + assert.is.equal(layout_changed, 1) + layout:set_max_widget_size(20) + assert.is.equal(layout_changed, 1) + layout:set_max_widget_size(nil) + assert.is.equal(layout_changed, 2) + end) + end) end) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/spec/wibox/test_utils.lua b/spec/wibox/test_utils.lua index f6f859a9..14f51d3f 100644 --- a/spec/wibox/test_utils.lua +++ b/spec/wibox/test_utils.lua @@ -5,37 +5,21 @@ local object = require("gears.object") local cache = require("gears.cache") -local wbase = require("wibox.widget.base") -local lbase = require("wibox.layout.base") +local matrix_equals = require("gears.matrix").equals +local base = require("wibox.widget.base") local say = require("say") local assert = require("luassert") -local spy = require("luassert.spy") -local stub = require("luassert.stub") - -local real_draw_widget = lbase.draw_widget -local widgets_drawn = nil - --- This function would reject stubbed widgets -local real_check_widget = wbase.check_widget -wbase.check_widget = function() -end - -local function stub_draw_widget(wibox, cr, widget, x, y, width, height) - assert.is.equal("wibox", wibox) - assert.is.equal("cr", cr) - table.insert(widgets_drawn, { widget, x, y, width, height }) -end -- {{{ Own widget-based assertions local function widget_fit(state, arguments) if #arguments ~= 3 then - return false + error("Have " .. #arguments .. " arguments, but need 3") end local widget = arguments[1] local given = arguments[2] local expected = arguments[3] - local w, h = lbase.fit_widget({ "fake context" }, widget, given[1], given[2]) + local w, h = base.fit_widget({ "fake context" }, widget, given[1], given[2]) local fits = expected[1] == w and expected[2] == h if state.mod == fits then @@ -52,51 +36,65 @@ local function widget_fit(state, arguments) end say:set("assertion.widget_fit.positive", "Offering (%s, %s) to widget and expected (%s, %s), but got (%s, %s)") assert:register("assertion", "widget_fit", widget_fit, "assertion.widget_fit.positive", "assertion.widget_fit.positive") + +local function widget_layout(state, arguments) + if #arguments ~= 3 then + error("Have " .. #arguments .. " arguments, but need 3") + end + + local widget = arguments[1] + local given = arguments[2] + local expected = arguments[3] + local children = widget.layout and widget:layout({ "fake context" }, given[1], given[2]) or {} + + local fits = true + if #children ~= #expected then + fits = false + else + for i = 1, #children do + local child, expected = children[i], expected[i] + if child._widget ~= expected._widget or + child._width ~= expected._width or + child._height ~= expected._height or + not matrix_equals(child._matrix, expected._matrix) then + fits = false + break + end + end + end + + if state.mod == fits then + return true + end + + -- For proper error message, mess with the arguments + arguments[1] = expected + arguments[2] = children + arguments[3] = given[1] + arguments[4] = given[2] + + return false +end +say:set("assertion.widget_layout.positive", "Expected:\n%s\nbut got:\n%s\nwhen offering (%s, %s) to widget") +assert:register("assertion", "widget_layout", widget_layout, "assertion.widget_layout.positive", "assertion.widget_layout.positive") -- }}} return { - real_check_widget = real_check_widget, - widget_stub = function(width, height) local w = object() + w:add_signal("widget::redraw_needed") + w:add_signal("widget::layout_changed") w.visible = true - w:add_signal("widget::updated") - - w.fit = function() - return width or 10, height or 10 + w.opacity = 1 + if width or height then + w.fit = function() + return width or 10, height or 10 + end end - w.draw = function() end - w._fit_geometry_cache = cache.new(w.fit) - - spy.on(w, "fit") - stub(w, "draw") + w._widget_caches = {} return w end, - - stub_draw_widget = function() - lbase.draw_widget = stub_draw_widget - widgets_drawn = {} - end, - - revert_draw_widget = function() - lbase.draw_widget = real_draw_widget - widgets_drawn = nil - end, - - check_widgets_drawn = function(expected) - assert.is.equals(#expected, #widgets_drawn) - for k, v in pairs(expected) do - -- widget, x, y, width, height - -- Compared like this so we get slightly less bad error messages - assert.is.equals(expected[k][1], widgets_drawn[k][1]) - assert.is.equals(expected[k][2], widgets_drawn[k][2]) - assert.is.equals(expected[k][3], widgets_drawn[k][3]) - assert.is.equals(expected[k][4], widgets_drawn[k][4]) - assert.is.equals(expected[k][5], widgets_drawn[k][5]) - end - widgets_drawn = {} - end } -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/spec/wibox/widget/textbox_spec.lua b/spec/wibox/widget/textbox_spec.lua new file mode 100644 index 00000000..9200a6b6 --- /dev/null +++ b/spec/wibox/widget/textbox_spec.lua @@ -0,0 +1,127 @@ +--------------------------------------------------------------------------- +-- @author Uli Schlachter +-- @copyright 2015 Uli Schlachter +--------------------------------------------------------------------------- + +-- Grml... +_G.awesome = { + xrdb_get_value = function(a, b) + if a ~= "" then error() end + if b ~= "Xft.dpi" then error() end + return nil + end +} + +local textbox = require("wibox.widget.textbox") + +describe("wibox.widget.textbox", function() + local widget + before_each(function() + widget = textbox() + end) + + describe("emitting signals", function() + local redraw_needed, layout_changed + before_each(function() + widget:connect_signal("widget::redraw_needed", function() + redraw_needed = redraw_needed + 1 + end) + widget:connect_signal("widget::layout_changed", function() + layout_changed = layout_changed + 1 + end) + redraw_needed, layout_changed = 0, 0 + end) + + it("text and markup", function() + assert.is.equal(0, redraw_needed) + assert.is.equal(0, layout_changed) + + widget:set_text("text") + assert.is.equal(1, redraw_needed) + assert.is.equal(1, layout_changed) + + widget:set_text("text") + assert.is.equal(1, redraw_needed) + assert.is.equal(1, layout_changed) + + widget:set_text("text") + assert.is.equal(2, redraw_needed) + assert.is.equal(2, layout_changed) + + widget:set_markup("text") + assert.is.equal(3, redraw_needed) + assert.is.equal(3, layout_changed) + + widget:set_markup("text") + assert.is.equal(3, redraw_needed) + assert.is.equal(3, layout_changed) + end) + + it("set_ellipsize", function() + assert.is.equal(0, redraw_needed) + assert.is.equal(0, layout_changed) + + widget:set_ellipsize("end") + assert.is.equal(0, redraw_needed) + assert.is.equal(0, layout_changed) + + widget:set_ellipsize("none") + assert.is.equal(1, redraw_needed) + assert.is.equal(1, layout_changed) + end) + + it("set_wrap", function() + assert.is.equal(0, redraw_needed) + assert.is.equal(0, layout_changed) + + widget:set_wrap("word_char") + assert.is.equal(0, redraw_needed) + assert.is.equal(0, layout_changed) + + widget:set_wrap("char") + assert.is.equal(1, redraw_needed) + assert.is.equal(1, layout_changed) + end) + + it("set_valign", function() + assert.is.equal(0, redraw_needed) + assert.is.equal(0, layout_changed) + + widget:set_valign("center") + assert.is.equal(0, redraw_needed) + assert.is.equal(0, layout_changed) + + widget:set_valign("top") + assert.is.equal(1, redraw_needed) + assert.is.equal(1, layout_changed) + end) + + it("set_align", function() + assert.is.equal(0, redraw_needed) + assert.is.equal(0, layout_changed) + + widget:set_align("left") + assert.is.equal(0, redraw_needed) + assert.is.equal(0, layout_changed) + + widget:set_align("right") + assert.is.equal(1, redraw_needed) + assert.is.equal(1, layout_changed) + end) + + it("set_font", function() + assert.is.equal(0, redraw_needed) + assert.is.equal(0, layout_changed) + + widget:set_font("foo") + assert.is.equal(1, redraw_needed) + assert.is.equal(1, layout_changed) + + widget:set_font("bar") + assert.is.equal(2, redraw_needed) + assert.is.equal(2, layout_changed) + end) + end) +end) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80