Merge branch 'new_widget_system3'

This commit is contained in:
Uli Schlachter 2015-09-11 17:17:06 +02:00
commit ca9242da4b
25 changed files with 1524 additions and 574 deletions

View File

@ -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

View File

@ -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

View File

@ -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

260
lib/wibox/hierarchy.lua Normal file
View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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)

View File

@ -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 {})

View File

@ -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")

View File

@ -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)

View File

@ -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.
<pre><code>function widget:fit(context, width, height)
-- Find the maximum square available
local m = math.min(width, height)
return m, m
end</code></pre>
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
<code>cr:reset_clip()</code>, as redraws will not be handled correctly in this
case.
<pre><code>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</code></pre>
There are two signals configured for a widget. When the result that :fit would
return changes, the <code>widget::layout_changed</code> signal has to be
emitted. If this actually causes layout changes, the affected areas will be
redrawn. The other signal is <code>widget::redraw_needed</code>. 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,
<code>widget::layout_changed</code> 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 <code>child</code> inside of your
widget, you can do it like this:
<pre><code>-- 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</code></pre>
Finally, if you want to influence how children are drawn, there are four
callbacks available that all get similar arguments:
<pre><code>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)</code></pre>
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:
<pre><code>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</code></pre>
In pseudo-code, the call sequence for the drawing callbacks during a redraw
looks like this:
<pre><code>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)</code></pre>
@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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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("<b>text</b>")
assert.is.equal(2, redraw_needed)
assert.is.equal(2, layout_changed)
widget:set_markup("<b>text</b>")
assert.is.equal(3, redraw_needed)
assert.is.equal(3, layout_changed)
widget:set_markup("<b>text</b>")
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