diff --git a/lib/awful/widget/graph.lua b/lib/awful/widget/graph.lua index b5b4309c6..fdd83198e 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 08d679238..4932207be 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 ed0a79390..82f7914f5 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 000000000..7ad7f4fd0 --- /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 5ea72c748..7d865c4c7 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 0d33ed642..e31fb7242 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 a92052769..000000000 --- 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 5a0c975c7..3d6539286 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 1a2ea1dbb..fc2519934 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 021867cd1..47d983c45 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 000b5cfb9..98289e08d 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 0fc2117e2..edb2c8f55 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 b03924095..fcd942304 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 cf929c096..b4772963f 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 5847cc3eb..cca1c1d0d 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 16f2b8eea..562670663 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 e623525db..34d46335e 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 a7e9d3b8d..011edd103 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 a26e93047..dfa39e522 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 000000000..98a1abad5
--- /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 4ea7772d6..81ad7a40b 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 744ffb8c1..0e5c1a859 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 a324861c5..ac83500a8 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 f6f859a98..14f51d3f1 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 000000000..9200a6b64
--- /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