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) table.remove(values, 1)
end end
_graph:emit_signal("widget::updated") _graph:emit_signal("widget::redraw_needed")
return _graph return _graph
end end
@ -205,7 +205,7 @@ end
function graph:set_height(height) function graph:set_height(height)
if height >= 5 then if height >= 5 then
data[self].height = height data[self].height = height
self:emit_signal("widget::updated") self:emit_signal("widget::layout_changed")
end end
return self return self
end end
@ -215,7 +215,7 @@ end
function graph:set_width(width) function graph:set_width(width)
if width >= 5 then if width >= 5 then
data[self].width = width data[self].width = width
self:emit_signal("widget::updated") self:emit_signal("widget::layout_changed")
end end
return self return self
end end
@ -226,7 +226,7 @@ for _, prop in ipairs(properties) do
graph["set_" .. prop] = function(_graph, value) graph["set_" .. prop] = function(_graph, value)
if data[_graph][prop] ~= value then if data[_graph][prop] ~= value then
data[_graph][prop] = value data[_graph][prop] = value
_graph:emit_signal("widget::updated") _graph:emit_signal("widget::redraw_needed")
end end
return _graph return _graph
end end

View File

@ -164,7 +164,7 @@ function progressbar:set_value(value)
local value = value or 0 local value = value or 0
local max_value = data[self].max_value local max_value = data[self].max_value
data[self].value = math.min(max_value, math.max(0, 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 return self
end end
@ -172,7 +172,7 @@ end
-- @param height The height to set. -- @param height The height to set.
function progressbar:set_height(height) function progressbar:set_height(height)
data[self].height = height data[self].height = height
self:emit_signal("widget::updated") self:emit_signal("widget::layout_changed")
return self return self
end end
@ -180,7 +180,7 @@ end
-- @param width The width to set. -- @param width The width to set.
function progressbar:set_width(width) function progressbar:set_width(width)
data[self].width = width data[self].width = width
self:emit_signal("widget::updated") self:emit_signal("widget::layout_changed")
return self return self
end end
@ -189,7 +189,7 @@ for _, prop in ipairs(properties) do
if not progressbar["set_" .. prop] then if not progressbar["set_" .. prop] then
progressbar["set_" .. prop] = function(pbar, value) progressbar["set_" .. prop] = function(pbar, value)
data[pbar][prop] = value data[pbar][prop] = value
pbar:emit_signal("widget::updated") pbar:emit_signal("widget::redraw_needed")
return pbar return pbar
end end
end end

View File

@ -20,6 +20,9 @@ local object = require("gears.object")
local sort = require("gears.sort") local sort = require("gears.sort")
local surface = require("gears.surface") local surface = require("gears.surface")
local timer = require("gears.timer") 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 drawables = setmetatable({}, { __mode = 'k' })
local wallpaper = nil local wallpaper = nil
@ -69,6 +72,36 @@ local function do_redraw(self)
local geom = self.drawable:geometry(); local geom = self.drawable:geometry();
local x, y, width, height = geom.x, geom.y, geom.width, geom.height 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 -- Draw the background
cr:save() cr:save()
@ -93,21 +126,9 @@ local function do_redraw(self)
cr:restore() cr:restore()
-- Draw the widget -- Draw the widget
self._widget_geometries = {} if self._widget_hierarchy then
if self.widget and self.widget.visible then
cr:set_source(self.foreground_color) cr:set_source(self.foreground_color)
self._widget_hierarchy:draw(get_widget_context(self), cr)
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)
end end
self.drawable:refresh() self.drawable:refresh()
@ -115,16 +136,33 @@ local function do_redraw(self)
debug.assert(cr.status == "SUCCESS", "Cairo context entered error state: " .. cr.status) debug.assert(cr.status == "SUCCESS", "Cairo context entered error state: " .. cr.status)
end end
--- Register a widget's position. local function find_widgets(drawable, result, hierarchy, x, y)
-- This is internal, don't call it yourself! Only wibox.layout.base.draw_widget local m = hierarchy:get_matrix_from_device()
-- is allowed to call this.
function drawable:widget_at(widget, x, y, width, height) -- Is (x,y) inside of this hierarchy or any child (aka the draw extents)
local t = { local x1, y1 = m:transform_point(x, y)
widget = widget, local x2, y2, width, height = hierarchy:get_draw_extents()
x = x, y = y,drawable = self, if x1 < x2 or x1 >= x2 + width then
width = width, height = height return
} end
table.insert(self._widget_geometries, t) 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 end
--- Find a widget by a point. --- Find a widget by a point.
@ -134,43 +172,20 @@ end
-- @return A sorted table with all widgets that contain the given point. The -- @return A sorted table with all widgets that contain the given point. The
-- widgets are sorted by relevance. -- widgets are sorted by relevance.
function drawable:find_widgets(x, y) function drawable:find_widgets(x, y)
local matches = {} local result = {}
-- Find all widgets that contain the point if self._widget_hierarchy then
for k, v in pairs(self._widget_geometries) do find_widgets(self, result, self._widget_hierarchy, x, y)
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
end end
return result
-- 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
end end
--- Set the widget that the drawable displays --- Set the widget that the drawable displays
function drawable:set_widget(widget) 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 self.widget = widget
if widget then
widget:weak_connect_signal("widget::updated", self.draw)
end
-- Make sure the widget gets drawn -- Make sure the widget gets drawn
self._need_relayout = true
self.draw() self.draw()
end end
@ -192,16 +207,16 @@ function drawable:set_bg(c)
if self._redraw_on_move ~= redraw_on_move then if self._redraw_on_move ~= redraw_on_move then
self._redraw_on_move = redraw_on_move self._redraw_on_move = redraw_on_move
if redraw_on_move then if redraw_on_move then
self.drawable:connect_signal("property::x", self.draw) self.drawable:connect_signal("property::x", self._do_complete_repaint)
self.drawable:connect_signal("property::y", self.draw) self.drawable:connect_signal("property::y", self._do_complete_repaint)
else else
self.drawable:disconnect_signal("property::x", self.draw) self.drawable:disconnect_signal("property::x", self._do_complete_repaint)
self.drawable:disconnect_signal("property::y", self.draw) self.drawable:disconnect_signal("property::y", self._do_complete_repaint)
end end
end end
self.background_color = c self.background_color = c
self.draw() self._do_complete_repaint()
end end
--- Set the foreground of the drawable --- Set the foreground of the drawable
@ -213,7 +228,7 @@ function drawable:set_fg(c)
c = color(c) c = color(c)
end end
self.foreground_color = c self.foreground_color = c
self.draw() self._do_complete_repaint()
end end
local function emit_difference(name, list, skip) local function emit_difference(name, list, skip)
@ -280,6 +295,9 @@ function drawable.new(d, widget_context_skeleton, drawable_name)
local ret = object() local ret = object()
ret.drawable = d ret.drawable = d
ret._widget_context_skeleton = widget_context_skeleton 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) setup_signals(ret)
for k, v in pairs(drawable) do for k, v in pairs(drawable) do
@ -302,8 +320,12 @@ function drawable.new(d, widget_context_skeleton, drawable_name)
ret._redraw_pending = true ret._redraw_pending = true
end end
end end
drawables[ret.draw] = true ret._do_complete_repaint = function()
d:connect_signal("property::surface", ret.draw) 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). -- Currently we aren't redrawing on move (signals not connected).
-- :set_bg() will later recompute this. -- :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) ret:set_fg(beautiful.fg_normal)
-- Initialize internals -- Initialize internals
ret._widget_geometries = {}
ret._widgets_under_mouse = {} ret._widgets_under_mouse = {}
local function button_signal(name) 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::move", function(_, x, y) handle_motion(ret, x, y) end)
d:connect_signal("mouse::leave", function() handle_leave(ret) 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. -- Add __tostring method to metatable.
ret.drawable_name = drawable_name or object.modulename(3) ret.drawable_name = drawable_name or object.modulename(3)
local mt = {} local mt = {}
@ -344,7 +387,7 @@ function drawable.new(d, widget_context_skeleton, drawable_name)
ret = setmetatable(ret, mt) ret = setmetatable(ret, mt)
-- Make sure the drawable is drawn at least once -- Make sure the drawable is drawn at least once
ret.draw() ret._do_complete_repaint()
return ret return ret
end 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.layout = require("wibox.layout")
wibox.widget = require("wibox.widget") wibox.widget = require("wibox.widget")
wibox.drawable = require("wibox.drawable") wibox.drawable = require("wibox.drawable")
wibox.hierarchy = require("wibox.hierarchy")
--- Set the widget that the wibox displays --- Set the widget that the wibox displays
function wibox:set_widget(widget) function wibox:set_widget(widget)

View File

@ -10,17 +10,17 @@ local table = table
local pairs = pairs local pairs = pairs
local type = type local type = type
local floor = math.floor local floor = math.floor
local base = require("wibox.layout.base") local base = require("wibox.widget.base")
local widget_base = require("wibox.widget.base")
local align = {} local align = {}
--- Draw an align layout. --- Calculate the layout of an align layout.
-- @param context The context in which we are drawn. -- @param context The context in which we are drawn.
-- @param cr The cairo context to use.
-- @param width The available width. -- @param width The available width.
-- @param height The available height. -- @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 -- 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 -- 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 -- 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 -- if all the space is taken, skip the rest, and draw just the middle
-- widget -- widget
if size_second >= size_remains then if size_second >= size_remains then
base.draw_widget(context, cr, self.second, 0, 0, width, height) return { base.place_widget_at(self.second, 0, 0, width, height) }
return
else else
-- the middle widget is sized first, the outside widgets are given -- the middle widget is sized first, the outside widgets are given
-- the remaining space if available we will draw later -- the remaining space if available we will draw later
@ -84,7 +83,7 @@ function align:draw(context, cr, width, height)
w = size_remains w = size_remains
end end
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 end
-- size_remains will be <= 0 if first used all the space -- size_remains will be <= 0 if first used all the space
if self.third and size_remains > 0 then if self.third and size_remains > 0 then
@ -110,7 +109,7 @@ function align:draw(context, cr, width, height)
end end
end end
local x, y = width - w, height - h 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 end
-- here we either draw the second widget in the space set aside for it -- 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" -- 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 ) x = floor( (width -w)/2 )
end end
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
end return result
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()
end end
--- Set the layout's first widget. This is the widget that is at the left/top --- Set the layout's first widget. This is the widget that is at the left/top
function align:set_first(widget) function align:set_first(widget)
widget_changed(self, self.first, widget) if self.first == widget then
return
end
self.first = widget self.first = widget
self:emit_signal("widget::layout_changed")
end end
--- Set the layout's second widget. This is the centered one. --- Set the layout's second widget. This is the centered one.
function align:set_second(widget) function align:set_second(widget)
widget_changed(self, self.second, widget) if self.second == widget then
return
end
self.second = widget self.second = widget
self:emit_signal("widget::layout_changed")
end end
--- Set the layout's third widget. This is the widget that is at the right/bottom --- Set the layout's third widget. This is the widget that is at the right/bottom
function align:set_third(widget) function align:set_third(widget)
widget_changed(self, self.third, widget) if self.third == widget then
return
end
self.third = widget self.third = widget
self:emit_signal("widget::layout_changed")
end end
--- Fit the align layout into the given space. The align layout will --- 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 end
return used_in_dir, used_in_other return used_in_dir, used_in_other
end end
--- Set the expand mode which determines how sub widgets expand to take up --- Set the expand mode which determines how sub widgets expand to take up
-- unused space. Options are: -- unused space. Options are:
-- "inside" - Default option. Size of outside widgets is determined using their -- "inside" - Default option. Size of outside widgets is determined using their
@ -210,22 +209,19 @@ function align:set_expand(mode)
else else
self._expand = "inside" self._expand = "inside"
end end
self:emit_signal("widget::updated") self:emit_signal("widget::layout_changed")
end end
function align:reset() function align:reset()
for k, v in pairs({ "first", "second", "third" }) do for k, v in pairs({ "first", "second", "third" }) do
self[v] = nil self[v] = nil
end end
self:emit_signal("widget::updated") self:emit_signal("widget::layout_changed")
end end
local function get_layout(dir) local function get_layout(dir)
local ret = widget_base.make_widget() local ret = base.make_widget()
ret.dir = dir ret.dir = dir
ret._emit_updated = function()
ret:emit_signal("widget::updated")
end
for k, v in pairs(align) do for k, v in pairs(align) do
if type(v) == "function" then 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 pairs = pairs
local type = type local type = type
local setmetatable = setmetatable local setmetatable = setmetatable
local base = require("wibox.layout.base") local base = require("wibox.widget.base")
local widget_base = require("wibox.widget.base")
local math = math local math = math
local constraint = { mt = {} } local constraint = { mt = {} }
--- Draw a constraint layout --- Layout a constraint layout
function constraint:draw(context, cr, width, height) function constraint:layout(context, width, height)
if not self.widget then if self.widget then
return return { base.place_widget_at(self.widget, 0, 0, width, height) }
end end
base.draw_widget(context, cr, self.widget, 0, 0, width, height)
end end
--- Fit a constraint layout into the given space --- Fit a constraint layout into the given space
@ -43,15 +40,8 @@ end
--- Set the widget that this layout adds a constraint on. --- Set the widget that this layout adds a constraint on.
function constraint:set_widget(widget) 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.widget = widget
self:emit_signal("widget::updated") self:emit_signal("widget::layout_changed")
end end
--- Set the strategy to use for the constraining. Valid values are 'max', --- Set the strategy to use for the constraining. Valid values are 'max',
@ -74,19 +64,19 @@ function constraint:set_strategy(val)
end end
self._strategy = func[val] self._strategy = func[val]
self:emit_signal("widget::updated") self:emit_signal("widget::layout_changed")
end end
--- Set the maximum width to val. nil for no width limit. --- Set the maximum width to val. nil for no width limit.
function constraint:set_width(val) function constraint:set_width(val)
self._width = val self._width = val
self:emit_signal("widget::updated") self:emit_signal("widget::layout_changed")
end end
--- Set the maximum height to val. nil for no height limit. --- Set the maximum height to val. nil for no height limit.
function constraint:set_height(val) function constraint:set_height(val)
self._height = val self._height = val
self:emit_signal("widget::updated") self:emit_signal("widget::layout_changed")
end end
--- Reset this layout. The widget will be unreferenced, strategy set to "max" --- 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
end end
ret._emit_updated = function()
ret:emit_signal("widget::updated")
end
ret:set_strategy(strategy or "max") ret:set_strategy(strategy or "max")
ret:set_width(width) ret:set_width(width)
ret:set_height(height) ret:set_height(height)

View File

@ -5,21 +5,19 @@
-- @classmod wibox.layout.fixed -- @classmod wibox.layout.fixed
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
local base = require("wibox.layout.base") local base = require("wibox.widget.base")
local widget_base = require("wibox.widget.base")
local table = table local table = table
local pairs = pairs local pairs = pairs
local fixed = {} 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 context The context in which we are drawn.
-- @param cr The cairo context to use.
-- @param width The available width. -- @param width The available width.
-- @param height The available height. -- @param height The available height.
-- @return The total space needed by the layout. function fixed:layout(context, width, height)
function fixed:draw(context, cr, width, height) local result = {}
local pos,spacing = 0,self._spacing or 0 local pos,spacing = 0, self._spacing
for k, v in pairs(self.widgets) do for k, v in pairs(self.widgets) do
local x, y, w, h, _ local x, y, w, h, _
@ -46,16 +44,16 @@ function fixed:draw(context, cr, width, height)
(self.dir ~= "y" and pos-spacing > width) then (self.dir ~= "y" and pos-spacing > width) then
break break
end end
base.draw_widget(context, cr, v, x, y, w, h) table.insert(result, base.place_widget_at(v, x, y, w, h))
end end
return result
end end
--- Add a widget to the given fixed layout --- Add a widget to the given fixed layout
function fixed:add(widget) function fixed:add(widget)
widget_base.check_widget(widget) base.check_widget(widget)
table.insert(self.widgets, widget) table.insert(self.widgets, widget)
widget:weak_connect_signal("widget::updated", self._emit_updated) self:emit_signal("widget::layout_changed")
self._emit_updated()
end end
--- Fit the fixed layout into the given space --- Fit the fixed layout into the given space
@ -91,7 +89,7 @@ function fixed:fit(context, orig_width, orig_height)
end end
end end
local spacing = ((self._spacing or 0)*(#self.widgets-1)) local spacing = self._spacing * (#self.widgets-1)
if self.dir == "y" then if self.dir == "y" then
return used_max, used_in_dir + spacing return used_max, used_in_dir + spacing
@ -101,23 +99,22 @@ end
--- Reset a fixed layout. This removes all widgets from the layout. --- Reset a fixed layout. This removes all widgets from the layout.
function fixed:reset() function fixed:reset()
for k, v in pairs(self.widgets) do
v:disconnect_signal("widget::updated", self._emit_updated)
end
self.widgets = {} self.widgets = {}
self:emit_signal("widget::updated") self:emit_signal("widget::layout_changed")
end end
--- Set the layout's fill_space property. If this property is true, the last --- 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 -- 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. -- won't be handled specially and there can be space left unused.
function fixed:fill_space(val) function fixed:fill_space(val)
self._fill_space = val if self._fill_space ~= val then
self:emit_signal("widget::updated") self._fill_space = not not val
self:emit_signal("widget::layout_changed")
end
end end
local function get_layout(dir) local function get_layout(dir)
local ret = widget_base.make_widget() local ret = base.make_widget()
for k, v in pairs(fixed) do for k, v in pairs(fixed) do
if type(v) == "function" then if type(v) == "function" then
@ -127,9 +124,8 @@ local function get_layout(dir)
ret.dir = dir ret.dir = dir
ret.widgets = {} ret.widgets = {}
ret._emit_updated = function() ret:set_spacing(0)
ret:emit_signal("widget::updated") ret:fill_space(false)
end
return ret return ret
end end
@ -151,8 +147,10 @@ end
--- Add spacing between each layout widgets --- Add spacing between each layout widgets
-- @param spacing Spacing between widgets. -- @param spacing Spacing between widgets.
function fixed:set_spacing(spacing) function fixed:set_spacing(spacing)
self._spacing = spacing if self._spacing ~= spacing then
self:emit_signal("widget::updated") self._spacing = spacing
self:emit_signal("widget::layout_changed")
end
end end
return fixed return fixed

View File

@ -5,8 +5,7 @@
-- @classmod wibox.layout.flex -- @classmod wibox.layout.flex
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
local base = require("wibox.layout.base") local base = require("wibox.widget.base")
local widget_base = require("wibox.widget.base")
local table = table local table = table
local pairs = pairs local pairs = pairs
local floor = math.floor local floor = math.floor
@ -14,14 +13,17 @@ local round = require("awful.util").round
local flex = {} 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 context The context in which we are drawn.
-- @param cr The cairo context to use.
-- @param width The available width. -- @param width The available width.
-- @param height The available height. -- @param height The available height.
-- @return The total space needed by the layout. function flex:layout(context, width, height)
function flex:draw(context, cr, width, height) local result = {}
local pos,spacing = 0,self._spacing or 0 local pos,spacing = 0, self._spacing
local num = #self.widgets local num = #self.widgets
local total_spacing = (spacing*(num-1)) local total_spacing = (spacing*(num-1))
@ -45,7 +47,7 @@ function flex:draw(context, cr, width, height)
x, y = round(pos), 0 x, y = round(pos), 0
w, h = floor(space_per_item), height w, h = floor(space_per_item), height
end 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 pos = pos + space_per_item + spacing
@ -54,21 +56,8 @@ function flex:draw(context, cr, width, height)
break break
end end
end end
end
function flex:add(widget) return result
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")
end end
--- Fit the flex layout into the given space. --- 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) #self.widgets * self._max_widget_size)
end end
local spacing = ((self._spacing or 0)*(#self.widgets-1)) local spacing = self._spacing * (#self.widgets-1)
if self.dir == "y" then if self.dir == "y" then
return used_in_other, used_in_dir + spacing 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 return used_in_dir + spacing, used_in_other
end end
function flex:reset() function flex:add(widget)
for k, v in pairs(self.widgets) do base.check_widget(widget)
v:disconnect_signal("widget::updated", self._emit_updated) 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
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.widgets = {}
self._max_widget_size = nil self._max_widget_size = nil
self:emit_signal("widget::updated") self:emit_signal("widget::layout_changed")
end end
local function get_layout(dir) local function get_layout(dir)
local ret = widget_base.make_widget() local ret = base.make_widget()
for k, v in pairs(flex) do for k, v in pairs(flex) do
if type(v) == "function" then if type(v) == "function" then
@ -127,9 +138,7 @@ local function get_layout(dir)
ret.dir = dir ret.dir = dir
ret.widgets = {} ret.widgets = {}
ret._emit_updated = function() ret:set_spacing(0)
ret:emit_signal("widget::updated")
end
return ret return ret
end end
@ -146,13 +155,6 @@ function flex.vertical()
return get_layout("y") return get_layout("y")
end 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 return flex
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -9,7 +9,6 @@
return return
{ {
base = require("wibox.layout.base");
fixed = require("wibox.layout.fixed"); fixed = require("wibox.layout.fixed");
align = require("wibox.layout.align"); align = require("wibox.layout.align");
flex = require("wibox.layout.flex"); flex = require("wibox.layout.flex");

View File

@ -8,8 +8,7 @@
local pairs = pairs local pairs = pairs
local type = type local type = type
local setmetatable = setmetatable local setmetatable = setmetatable
local base = require("wibox.layout.base") local base = require("wibox.widget.base")
local widget_base = require("wibox.widget.base")
local gcolor = require("gears.color") local gcolor = require("gears.color")
local cairo = require("lgi").cairo local cairo = require("lgi").cairo
@ -28,16 +27,24 @@ function margin:draw(context, cr, width, height)
end end
if color then if color then
cr:save()
cr:set_source(color) cr:set_source(color)
cr:rectangle(0, 0, width, height) cr:rectangle(0, 0, width, height)
cr:rectangle(x, y, width - x - w, height - y - h) cr:rectangle(x, y, width - x - w, height - y - h)
cr:set_fill_rule(cairo.FillRule.EVEN_ODD) cr:set_fill_rule(cairo.FillRule.EVEN_ODD)
cr:fill() cr:fill()
cr:restore()
end 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 end
--- Fit a margin layout into the given space --- Fit a margin layout into the given space
@ -53,15 +60,11 @@ end
--- Set the widget that this layout adds a margin on. --- Set the widget that this layout adds a margin on.
function margin:set_widget(widget) function margin:set_widget(widget)
if self.widget then
self.widget:disconnect_signal("widget::updated", self._emit_updated)
end
if widget then if widget then
widget_base.check_widget(widget) base.check_widget(widget)
widget:weak_connect_signal("widget::updated", self._emit_updated)
end end
self.widget = widget self.widget = widget
self._emit_updated() self:emit_signal("widget::layout_changed")
end end
--- Set all the margins to val. --- Set all the margins to val.
@ -70,13 +73,13 @@ function margin:set_margins(val)
self.right = val self.right = val
self.top = val self.top = val
self.bottom = val self.bottom = val
self:emit_signal("widget::updated") self:emit_signal("widget::layout_changed")
end end
--- Set the margins color to color --- Set the margins color to color
function margin:set_color(color) function margin:set_color(color)
self.color = color and gcolor(color) self.color = color and gcolor(color)
self._emit_updated() self:emit_signal("widget::redraw_needed")
end end
--- Reset this layout. The widget will be unreferenced, the margins set to 0 --- 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 for k, v in pairs({ "left", "right", "top", "bottom" }) do
margin["set_" .. v] = function(layout, val) margin["set_" .. v] = function(layout, val)
layout[v] = val layout[v] = val
layout:emit_signal("widget::updated") layout:emit_signal("widget::layout_changed")
end end
end end
@ -127,7 +130,7 @@ end
-- @param[opt] bottom A margin to use on the bottom side of the widget. -- @param[opt] bottom A margin to use on the bottom side of the widget.
-- @param[opt] color A color for the margins. -- @param[opt] color A color for the margins.
local function new(widget, left, right, top, bottom, color) 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 for k, v in pairs(margin) do
if type(v) == "function" then if type(v) == "function" then
@ -135,10 +138,6 @@ local function new(widget, left, right, top, bottom, color)
end end
end end
ret._emit_updated = function()
ret:emit_signal("widget::updated")
end
ret:set_left(left or 0) ret:set_left(left or 0)
ret:set_right(right or 0) ret:set_right(right or 0)
ret:set_top(top or 0) ret:set_top(top or 0)

View File

@ -10,21 +10,20 @@ local error = error
local pairs = pairs local pairs = pairs
local ipairs = ipairs local ipairs = ipairs
local setmetatable = setmetatable local setmetatable = setmetatable
local base = require("wibox.layout.base") local base = require("wibox.widget.base")
local widget_base = require("wibox.widget.base") local Matrix = require("lgi").cairo.Matrix
local mirror = { mt = {} } local mirror = { mt = {} }
--- Draw this layout --- Layout this layout
function mirror:draw(context, cr, width, height) function mirror:layout(context, cr, width, height)
if not self.widget then return end if not self.widget then return end
if not self.horizontal and not self.vertical then if not self.horizontal and not self.vertical then
base.draw_widget(wibox, cr, self.widget, 0, 0, width, height) base.draw_widget(wibox, cr, self.widget, 0, 0, width, height)
return -- nothing changed return
end end
cr:save() local m = Matrix.create_identity()
local t = { x = 0, y = 0 } -- translation local t = { x = 0, y = 0 } -- translation
local s = { x = 1, y = 1 } -- scale local s = { x = 1, y = 1 } -- scale
if self.horizontal then if self.horizontal then
@ -35,13 +34,10 @@ function mirror:draw(context, cr, width, height)
t.x = width t.x = width
s.x = -1 s.x = -1
end end
cr:translate(t.x, t.y) m:translate(t.x, t.y)
cr:scale(s.x, s.y) m:scale(s.x, s.y)
self.widget:draw(context, cr, width, height) return base.place_widget_via_matrix(widget, m, width, height)
-- Undo the scale and translation from above.
cr:restore()
end end
--- Fit this layout into the given area --- Fit this layout into the given area
@ -55,15 +51,11 @@ end
--- Set the widget that this layout mirrors. --- Set the widget that this layout mirrors.
-- @param widget The widget to mirror -- @param widget The widget to mirror
function mirror:set_widget(widget) function mirror:set_widget(widget)
if self.widget then
self.widget:disconnect_signal("widget::updated", self._emit_updated)
end
if widget then if widget then
widget_base.check_widget(widget) base.check_widget(widget)
widget:weak_connect_signal("widget::updated", self._emit_updated)
end end
self.widget = widget self.widget = widget
self._emit_updated() self:emit_signal("widget::layout_changed")
end end
--- Reset this layout. The widget will be removed and the axes reset. --- 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] self[ref] = reflection[ref]
end end
end end
self._emit_updated() self:emit_signal("widget::layout_changed")
end end
--- Get the reflection of this mirror layout. --- Get the reflection of this mirror layout.
@ -101,7 +93,7 @@ end
-- @param[opt] widget The widget to display. -- @param[opt] widget The widget to display.
-- @param[opt] reflection A table describing the reflection to apply. -- @param[opt] reflection A table describing the reflection to apply.
local function new(widget, reflection) local function new(widget, reflection)
local ret = widget_base.make_widget() local ret = base.make_widget()
ret.horizontal = false ret.horizontal = false
ret.vertical = false ret.vertical = false
@ -111,10 +103,6 @@ local function new(widget, reflection)
end end
end end
ret._emit_updated = function()
ret:emit_signal("widget::updated")
end
ret:set_widget(widget) ret:set_widget(widget)
ret:set_reflection(reflection or {}) ret:set_reflection(reflection or {})

View File

@ -11,8 +11,8 @@ local pi = math.pi
local type = type local type = type
local setmetatable = setmetatable local setmetatable = setmetatable
local tostring = tostring local tostring = tostring
local base = require("wibox.layout.base") local base = require("wibox.widget.base")
local widget_base = require("wibox.widget.base") local Matrix = require("lgi").cairo.Matrix
local rotate = { mt = {} } local rotate = { mt = {} }
@ -24,28 +24,29 @@ local function transform(layout, width, height)
return width, height return width, height
end end
--- Draw this layout --- Layout this layout
function rotate:draw(context, cr, width, height) function rotate:layout(context, width, height)
if not self.widget or not self.widget.visible then if not self.widget or not self.widget.visible then
return return
end end
local dir = self:get_direction() local dir = self:get_direction()
local m = Matrix.create_identity()
if dir == "west" then if dir == "west" then
cr:rotate(pi / 2) m:rotate(pi / 2)
cr:translate(0, -width) m:translate(0, -width)
elseif dir == "south" then elseif dir == "south" then
cr:rotate(pi) m:rotate(pi)
cr:translate(-width, -height) m:translate(-width, -height)
elseif dir == "east" then elseif dir == "east" then
cr:rotate(3 * pi / 2) m:rotate(3 * pi / 2)
cr:translate(-height, 0) m:translate(-height, 0)
end end
-- Since we rotated, we might have to swap width and height. -- Since we rotated, we might have to swap width and height.
-- transform() does that for us. -- 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 end
--- Fit this layout into the given area --- Fit this layout into the given area
@ -58,15 +59,11 @@ end
--- Set the widget that this layout rotates. --- Set the widget that this layout rotates.
function rotate:set_widget(widget) function rotate:set_widget(widget)
if self.widget then
self.widget:disconnect_signal("widget::updated", self._emit_updated)
end
if widget then if widget then
widget_base.check_widget(widget) base.check_widget(widget)
widget:weak_connect_signal("widget::updated", self._emit_updated)
end end
self.widget = widget self.widget = widget
self._emit_updated() self:emit_signal("widget::layout_changed")
end end
--- Reset this layout. The widget will be removed and the rotation reset. --- Reset this layout. The widget will be removed and the rotation reset.
@ -90,7 +87,7 @@ function rotate:set_direction(dir)
end end
self.direction = dir self.direction = dir
self._emit_updated() self:emit_signal("widget::layout_changed")
end end
--- Get the direction of this rotating layout --- Get the direction of this rotating layout
@ -104,7 +101,7 @@ end
-- @param[opt] widget The widget to display. -- @param[opt] widget The widget to display.
-- @param[opt] dir The direction to rotate to. -- @param[opt] dir The direction to rotate to.
local function new(widget, dir) local function new(widget, dir)
local ret = widget_base.make_widget() local ret = base.make_widget()
for k, v in pairs(rotate) do for k, v in pairs(rotate) do
if type(v) == "function" then if type(v) == "function" then
@ -112,10 +109,6 @@ local function new(widget, dir)
end end
end end
ret._emit_updated = function()
ret:emit_signal("widget::updated")
end
ret:set_widget(widget) ret:set_widget(widget)
ret:set_direction(dir or "north") ret:set_direction(dir or "north")

View File

@ -7,7 +7,6 @@
local base = require("wibox.widget.base") local base = require("wibox.widget.base")
local color = require("gears.color") local color = require("gears.color")
local layout_base = require("wibox.layout.base")
local surface = require("gears.surface") local surface = require("gears.surface")
local cairo = require("lgi").cairo local cairo = require("lgi").cairo
local setmetatable = setmetatable local setmetatable = setmetatable
@ -22,8 +21,6 @@ function background:draw(context, cr, width, height)
return return
end end
cr:save()
if self.background then if self.background then
cr:set_source(self.background) cr:set_source(self.background)
cr:paint() cr:paint()
@ -33,16 +30,19 @@ function background:draw(context, cr, width, height)
cr:set_source(pattern) cr:set_source(pattern)
cr:paint() cr:paint()
end end
end
cr:restore() --- Prepare drawing the children of this widget
function background:before_draw_children(wibox, cr, width, height)
if self.foreground then if self.foreground then
cr:save()
cr:set_source(self.foreground) cr:set_source(self.foreground)
end end
layout_base.draw_widget(context, cr, self.widget, 0, 0, width, height) end
if self.foreground then
cr:restore() --- 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
end end
@ -52,20 +52,16 @@ function background:fit(context, width, height)
return 0, 0 return 0, 0
end end
return layout_base.fit_widget(context, self.widget, width, height) return base.fit_widget(context, self.widget, width, height)
end end
--- Set the widget that is drawn on top of the background --- Set the widget that is drawn on top of the background
function background:set_widget(widget) function background:set_widget(widget)
if self.widget then
self.widget:disconnect_signal("widget::updated", self._emit_updated)
end
if widget then if widget then
base.check_widget(widget) base.check_widget(widget)
widget:weak_connect_signal("widget::updated", self._emit_updated)
end end
self.widget = widget self.widget = widget
self._emit_updated() self:emit_signal("widget::layout_changed")
end end
--- Set the background to use --- Set the background to use
@ -75,7 +71,7 @@ function background:set_bg(bg)
else else
self.background = nil self.background = nil
end end
self._emit_updated() self:emit_signal("widget::redraw_needed")
end end
--- Set the foreground to use --- Set the foreground to use
@ -85,13 +81,13 @@ function background:set_fg(fg)
else else
self.foreground = nil self.foreground = nil
end end
self._emit_updated() self:emit_signal("widget::redraw_needed")
end end
--- Set the background image to use --- Set the background image to use
function background:set_bgimage(image) function background:set_bgimage(image)
self.bgimage = surface.load(image) self.bgimage = surface.load(image)
self._emit_updated() self:emit_signal("widget::redraw_needed")
end end
--- Returns a new background layout. A background layout applies a background --- Returns a new background layout. A background layout applies a background
@ -107,10 +103,6 @@ local function new(widget, bg)
end end
end end
ret._emit_updated = function()
ret:emit_signal("widget::updated")
end
ret:set_widget(widget) ret:set_widget(widget)
ret:set_bg(bg) ret:set_bg(bg)

View File

@ -2,13 +2,14 @@
-- @author Uli Schlachter -- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter -- @copyright 2010 Uli Schlachter
-- @release @AWESOME_VERSION@ -- @release @AWESOME_VERSION@
-- @classmod wibox.widget.base -- @module wibox.widget.base
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
local debug = require("gears.debug") local debug = require("gears.debug")
local object = require("gears.object") local object = require("gears.object")
local cache = require("gears.cache") local cache = require("gears.cache")
local matrix = require("gears.matrix") local matrix = require("gears.matrix")
local Matrix = require("lgi").cairo.Matrix
local setmetatable = setmetatable local setmetatable = setmetatable
local pairs = pairs local pairs = pairs
local type = type local type = type
@ -16,13 +17,144 @@ local table = table
local base = {} 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 --- Figure out the geometry in device coordinate space. This gives only tight
-- bounds if no rotations by non-multiples of 90° are used. -- bounds if no rotations by non-multiples of 90° are used.
function base.rect_to_device_geometry(cr, x, y, width, height) function base.rect_to_device_geometry(cr, x, y, width, height)
return matrix.transform_rectangle(cr.matrix, x, y, width, height) return matrix.transform_rectangle(cr.matrix, x, y, width, height)
end 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) function base:buttons(_buttons)
if _buttons then if _buttons then
self.widget_buttons = _buttons self.widget_buttons = _buttons
@ -31,7 +163,8 @@ function base:buttons(_buttons)
return self.widget_buttons return self.widget_buttons
end 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) function base.handle_button(event, widget, x, y, button, modifiers, geometry)
local function is_any(mod) local function is_any(mod)
return #mod == 1 and mod[1] == "Any" return #mod == 1 and mod[1] == "Any"
@ -68,23 +201,174 @@ function base.handle_button(event, widget, x, y, button, modifiers, geometry)
end end
end end
--- Create a new widget. All widgets have to be generated via this function so --- Create widget placement information. This should be used for a widget's
-- that the needed signals are added and mouse input handling is set up. -- `:layout()` callback.
-- @param proxy If this is set, the returned widget will be a proxy for this -- @param widget The widget that should be placed.
-- widget. It will be equivalent to this widget. -- @param mat A cairo matrix transforming from the parent widget's coordinate
-- @tparam[opt] string widget_name Name of the widget. If not set, it will be -- system. For example, use cairo.Matrix.create_translate(1, 2) to draw a
-- set automatically via `gears.object.modulename`. -- 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) function base.make_widget(proxy, widget_name)
local ret = object() local ret = object()
-- This signal is used by layouts to find out when they have to update. -- 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! -- Mouse input, oh noes!
ret:add_signal("button::press") ret:add_signal("button::press")
ret:add_signal("button::release") ret:add_signal("button::release")
ret:add_signal("mouse::enter") ret:add_signal("mouse::enter")
ret:add_signal("mouse::leave") 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 -- No buttons yet
ret.widget_buttons = {} ret.widget_buttons = {}
ret.buttons = base.buttons ret.buttons = base.buttons
@ -98,20 +382,24 @@ function base.make_widget(proxy, widget_name)
end) end)
if proxy then if proxy then
ret.draw = function(_, ...) return proxy:draw(...) end ret.fit = function(_, context, width, height)
ret.fit = function(_, ...) return proxy:fit(...) end return base.fit_widget(context, proxy, width, height)
proxy:connect_signal("widget::updated", function() end
ret:emit_signal("widget::updated") 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)
end end
-- Add a geometry for base.fit_widget() that is cleared when necessary -- Set up caches
local function cb(...) clear_caches(ret)
return ret:fit(...) ret:connect_signal("widget::layout_changed", function()
end clear_caches(ret)
ret._fit_geometry_cache = cache.new(cb)
ret:connect_signal("widget::updated", function()
ret._fit_geometry_cache = cache.new(cb)
end) end)
-- Add visible property and setter. -- Add visible property and setter.
@ -119,7 +407,9 @@ function base.make_widget(proxy, widget_name)
function ret:set_visible(b) function ret:set_visible(b)
if b ~= self.visible then if b ~= self.visible then
self.visible = b 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
end end
@ -128,7 +418,7 @@ function base.make_widget(proxy, widget_name)
function ret:set_opacity(b) function ret:set_opacity(b)
if b ~= self.opacity then if b ~= self.opacity then
self.opacity = b self.opacity = b
self:emit_signal("widget::updated") self:emit_signal("widget::redraw")
end end
end end
@ -144,23 +434,16 @@ end
--- Generate an empty widget which takes no space and displays nothing --- Generate an empty widget which takes no space and displays nothing
function base.empty_widget() function base.empty_widget()
local widget = base.make_widget() return base.make_widget()
widget.draw = function() end
widget.fit = function() return 0, 0 end
return widget
end end
--- Do some sanity checking on widget. This function raises a lua error if --- Do some sanity checking on widget. This function raises a lua error if
-- widget is not a valid widget. -- widget is not a valid widget.
function base.check_widget(widget) function base.check_widget(widget)
debug.assert(type(widget) == "table") 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") debug.assert(type(widget[func]) == "function", func .. " is not a function")
end end
local width, height = widget:fit({}, 0, 0)
debug.assert(type(width) == "number")
debug.assert(type(height) == "number")
end end
return base return base

View File

@ -20,8 +20,6 @@ function imagebox:draw(context, cr, width, height)
if not self._image then return end if not self._image then return end
if width == 0 or height == 0 then return end if width == 0 or height == 0 then return end
cr:save()
if not self.resize_forbidden then if not self.resize_forbidden then
-- Let's scale the image so that it fits into (width, height) -- Let's scale the image so that it fits into (width, height)
local w = self._image:get_width() local w = self._image:get_width()
@ -34,8 +32,6 @@ function imagebox:draw(context, cr, width, height)
end end
cr:set_source_surface(self._image, 0, 0) cr:set_source_surface(self._image, 0, 0)
cr:paint() cr:paint()
cr:restore()
end end
--- Fit the imagebox into the given geometry --- Fit the imagebox into the given geometry
@ -99,9 +95,14 @@ function imagebox:set_image(image)
end end
end end
if self._image == image then
return
end
self._image = image self._image = image
self:emit_signal("widget::updated") self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed")
return true return true
end end
@ -110,7 +111,8 @@ end
-- to fit into the available space. -- to fit into the available space.
function imagebox:set_resize(allowed) function imagebox:set_resize(allowed)
self.resize_forbidden = not allowed self.resize_forbidden = not allowed
self:emit_signal("widget::updated") self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed")
end end
--- Returns a new imagebox --- Returns a new imagebox

View File

@ -79,7 +79,7 @@ local function new(revers)
end end
capi.awesome.connect_signal("systray::update", function() capi.awesome.connect_signal("systray::update", function()
ret:emit_signal("widget::updated") ret:emit_signal("widget::layout_changed")
end) end)
return ret return ret

View File

@ -70,7 +70,8 @@ function textbox:set_markup(text)
self._markup = text self._markup = text
self._layout.text = parsed self._layout.text = parsed
self._layout.attributes = attr self._layout.attributes = attr
self:emit_signal("widget::updated") self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed")
end end
--- Set a textbox' text. --- Set a textbox' text.
@ -82,7 +83,8 @@ function textbox:set_text(text)
self._markup = nil self._markup = nil
self._layout.text = text self._layout.text = text
self._layout.attributes = nil self._layout.attributes = nil
self:emit_signal("widget::updated") self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed")
end end
--- Set a textbox' ellipsize mode. --- Set a textbox' ellipsize mode.
@ -94,7 +96,8 @@ function textbox:set_ellipsize(mode)
return return
end end
self._layout:set_ellipsize(allowed[mode]) 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
end end
@ -107,7 +110,8 @@ function textbox:set_wrap(mode)
return return
end end
self._layout:set_wrap(allowed[mode]) 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
end end
@ -120,7 +124,8 @@ function textbox:set_valign(mode)
return return
end end
self._valign = mode self._valign = mode
self:emit_signal("widget::updated") self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed")
end end
end end
@ -133,7 +138,8 @@ function textbox:set_align(mode)
return return
end end
self._layout:set_alignment(allowed[mode]) 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
end end
@ -141,6 +147,8 @@ end
-- @param font The font description as string -- @param font The font description as string
function textbox:set_font(font) function textbox:set_font(font)
self._layout:set_font_description(beautiful.get_font(font)) self._layout:set_font_description(beautiful.get_font(font))
self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed")
end end
-- Returns a new textbox -- 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 align = require("wibox.layout.align")
local utils = require("wibox.test_utils") local utils = require("wibox.test_utils")
local p = require("wibox.widget.base").place_widget_at
describe("wibox.layout.flex", function() describe("wibox.layout.align", function()
before_each(utils.stub_draw_widget)
after_each(utils.revert_draw_widget)
describe("expand=none", function() describe("expand=none", function()
local layout local layout
before_each(function() before_each(function()
@ -21,9 +19,8 @@ describe("wibox.layout.flex", function()
assert.widget_fit(layout, { 10, 10 }, { 0, 0 }) assert.widget_fit(layout, { 10, 10 }, { 0, 0 })
end) end)
it("empty layout draw", function() it("empty layout layout", function()
layout:draw(nil, nil, 0, 0) assert.is.same({}, layout:layout(0, 0))
utils.check_widgets_drawn({})
end) end)
describe("with widgets", function() describe("with widgets", function()
@ -44,12 +41,11 @@ describe("wibox.layout.flex", function()
assert.widget_fit(layout, { 100, 100 }, { 15, 35 }) assert.widget_fit(layout, { 100, 100 }, { 15, 35 })
end) end)
it("draw", function() it("layout", function()
layout:draw("wibox", "cr", 100, 100) assert.widget_layout(layout, { 100, 100 }, {
utils.check_widgets_drawn({ p(first, 0, 0, 100, 10),
{ first, 0, 0, 100, 10 }, p(third, 0, 90, 100, 10),
{ third, 0, 90, 100, 10 }, p(second, 0, 42, 100, 15),
{ second, 0, 42, 100, 15 },
}) })
end) end)
end) end)
@ -59,12 +55,11 @@ describe("wibox.layout.flex", function()
assert.widget_fit(layout, { 5, 100 }, { 5, 35 }) assert.widget_fit(layout, { 5, 100 }, { 5, 35 })
end) end)
it("draw", function() it("layout", function()
layout:draw("wibox", "cr", 5, 100) assert.widget_layout(layout, { 5, 100 }, {
utils.check_widgets_drawn({ p(first, 0, 0, 5, 10),
{ first, 0, 0, 5, 10 }, p(third, 0, 90, 5, 10),
{ third, 0, 90, 5, 10 }, p(second, 0, 42, 5, 15),
{ second, 0, 42, 5, 15 },
}) })
end) end)
end) end)
@ -74,12 +69,11 @@ describe("wibox.layout.flex", function()
assert.widget_fit(layout, { 100, 20 }, { 15, 20 }) assert.widget_fit(layout, { 100, 20 }, { 15, 20 })
end) end)
it("draw", function() it("layout", function()
layout:draw("wibox", "cr", 100, 20) assert.widget_layout(layout, { 100, 20 }, {
utils.check_widgets_drawn({ p(first, 0, 0, 100, 2),
{ first, 0, 0, 100, 2 }, p(third, 0, 18, 100, 2),
{ third, 0, 18, 100, 2 }, p(second, 0, 2, 100, 15),
{ second, 0, 2, 100, 15 },
}) })
end) end)
end) end)
@ -97,9 +91,8 @@ describe("wibox.layout.flex", function()
assert.widget_fit(layout, { 10, 10 }, { 0, 0 }) assert.widget_fit(layout, { 10, 10 }, { 0, 0 })
end) end)
it("empty layout draw", function() it("empty layout layout", function()
layout:draw(nil, nil, 0, 0) assert.widget_layout(layout, { 0, 0 }, {})
utils.check_widgets_drawn({})
end) end)
describe("with widgets", function() describe("with widgets", function()
@ -120,12 +113,11 @@ describe("wibox.layout.flex", function()
assert.widget_fit(layout, { 100, 100 }, { 15, 35 }) assert.widget_fit(layout, { 100, 100 }, { 15, 35 })
end) end)
it("draw", function() it("layout", function()
layout:draw("wibox", "cr", 100, 100) assert.widget_layout(layout, { 100, 100 }, {
utils.check_widgets_drawn({ p(first, 0, 0, 100, 42),
{ first, 0, 0, 100, 42 }, p(third, 0, 58, 100, 42),
{ third, 0, 58, 100, 42 }, p(second, 0, 42, 100, 15),
{ second, 0, 42, 100, 15 },
}) })
end) end)
end) end)
@ -135,12 +127,11 @@ describe("wibox.layout.flex", function()
assert.widget_fit(layout, { 5, 100 }, { 5, 35 }) assert.widget_fit(layout, { 5, 100 }, { 5, 35 })
end) end)
it("draw", function() it("layout", function()
layout:draw("wibox", "cr", 5, 100) assert.widget_layout(layout, { 5, 100 }, {
utils.check_widgets_drawn({ p(first, 0, 0, 5, 42),
{ first, 0, 0, 5, 42 }, p(third, 0, 58, 5, 42),
{ third, 0, 58, 5, 42 }, p(second, 0, 42, 5, 15),
{ second, 0, 42, 5, 15 },
}) })
end) end)
end) end)
@ -150,12 +141,11 @@ describe("wibox.layout.flex", function()
assert.widget_fit(layout, { 100, 20 }, { 15, 20 }) assert.widget_fit(layout, { 100, 20 }, { 15, 20 })
end) end)
it("draw", function() it("layout", function()
layout:draw("wibox", "cr", 100, 20) assert.widget_layout(layout, { 100, 20 }, {
utils.check_widgets_drawn({ p(first, 0, 0, 100, 2),
{ first, 0, 0, 100, 2 }, p(third, 0, 18, 100, 2),
{ third, 0, 18, 100, 2 }, p(second, 0, 2, 100, 15),
{ second, 0, 2, 100, 15 },
}) })
end) end)
end) end)
@ -173,9 +163,8 @@ describe("wibox.layout.flex", function()
assert.widget_fit(layout, { 10, 10 }, { 0, 0 }) assert.widget_fit(layout, { 10, 10 }, { 0, 0 })
end) end)
it("empty layout draw", function() it("empty layout layout", function()
layout:draw(nil, nil, 0, 0) assert.widget_layout(layout, { 0, 0 }, {})
utils.check_widgets_drawn({})
end) end)
describe("with widgets", function() describe("with widgets", function()
@ -196,12 +185,11 @@ describe("wibox.layout.flex", function()
assert.widget_fit(layout, { 100, 100 }, { 15, 35 }) assert.widget_fit(layout, { 100, 100 }, { 15, 35 })
end) end)
it("draw", function() it("layout", function()
layout:draw("wibox", "cr", 100, 100) assert.widget_layout(layout, { 100, 100 }, {
utils.check_widgets_drawn({ p(first, 0, 0, 100, 10),
{ first, 0, 0, 100, 10 }, p(third, 0, 90, 100, 10),
{ third, 0, 90, 100, 10 }, p(second, 0, 10, 100, 80),
{ second, 0, 10, 100, 80 },
}) })
end) end)
end) end)
@ -211,12 +199,11 @@ describe("wibox.layout.flex", function()
assert.widget_fit(layout, { 5, 100 }, { 5, 35 }) assert.widget_fit(layout, { 5, 100 }, { 5, 35 })
end) end)
it("draw", function() it("layout", function()
layout:draw("wibox", "cr", 5, 100) assert.widget_layout(layout, { 5, 100 }, {
utils.check_widgets_drawn({ p(first, 0, 0, 5, 10),
{ first, 0, 0, 5, 10 }, p(third, 0, 90, 5, 10),
{ third, 0, 90, 5, 10 }, p(second, 0, 10, 5, 80),
{ second, 0, 10, 5, 80 },
}) })
end) end)
end) end)
@ -226,17 +213,59 @@ describe("wibox.layout.flex", function()
assert.widget_fit(layout, { 100, 20 }, { 15, 20 }) assert.widget_fit(layout, { 100, 20 }, { 15, 20 })
end) end)
it("draw", function() it("layout", function()
layout:draw("wibox", "cr", 100, 20) assert.widget_layout(layout, { 100, 20 }, {
--- XXX: Shouldn't this also draw part of the second widget? p(first, 0, 0, 100, 10),
utils.check_widgets_drawn({ p(third, 0, 10, 100, 10),
{ first, 0, 0, 100, 10 },
{ third, 0, 10, 100, 10 },
}) })
end) end)
end) end)
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) end)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 -- 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 fixed = require("wibox.layout.fixed")
local base = require("wibox.widget.base")
local utils = require("wibox.test_utils") local utils = require("wibox.test_utils")
local p = require("wibox.widget.base").place_widget_at
describe("wibox.layout.fixed", function() describe("wibox.layout.fixed", function()
local layout local layout
@ -12,16 +14,12 @@ describe("wibox.layout.fixed", function()
layout = fixed.vertical() layout = fixed.vertical()
end) end)
before_each(utils.stub_draw_widget)
after_each(utils.revert_draw_widget)
it("empty layout fit", function() it("empty layout fit", function()
assert.widget_fit(layout, { 10, 10 }, { 0, 0 }) assert.widget_fit(layout, { 10, 10 }, { 0, 0 })
end) end)
it("empty layout draw", function() it("empty layout layout", function()
layout:draw(nil, nil, 0, 0) assert.widget_layout(layout, { 0, 0 }, {})
utils.check_widgets_drawn({})
end) end)
describe("with widgets", function() describe("with widgets", function()
@ -42,12 +40,11 @@ describe("wibox.layout.fixed", function()
assert.widget_fit(layout, { 100, 100 }, { 15, 35 }) assert.widget_fit(layout, { 100, 100 }, { 15, 35 })
end) end)
it("draw", function() it("layout", function()
layout:draw("wibox", "cr", 100, 100) assert.widget_layout(layout, { 100, 100 }, {
utils.check_widgets_drawn({ p(first, 0, 0, 100, 10),
{ first, 0, 0, 100, 10 }, p(second, 0, 10, 100, 15),
{ second, 0, 10, 100, 15 }, p(third, 0, 25, 100, 10),
{ third, 0, 25, 100, 10 },
}) })
end) end)
end) end)
@ -57,32 +54,82 @@ describe("wibox.layout.fixed", function()
assert.widget_fit(layout, { 5, 100 }, { 5, 35 }) assert.widget_fit(layout, { 5, 100 }, { 5, 35 })
end) end)
it("draw", function() it("layout", function()
layout:draw("wibox", "cr", 5, 100) assert.widget_layout(layout, { 5, 100 }, {
utils.check_widgets_drawn({ p(first, 0, 0, 5, 10),
{ first, 0, 0, 5, 10 }, p(second, 0, 10, 5, 15),
{ second, 0, 10, 5, 15 }, p(third, 0, 25, 5, 10),
{ third, 0, 25, 5, 10 },
}) })
end) end)
end) end)
describe("without enough width", function() describe("without enough width", function()
it("fit", function() it("fit", function()
-- XXX: Is this really what should happen?
assert.widget_fit(layout, { 100, 20 }, { 15, 20 }) assert.widget_fit(layout, { 100, 20 }, { 15, 20 })
end) end)
it("draw", function() it("layout", function()
layout:draw("wibox", "cr", 100, 20) assert.widget_layout(layout, { 100, 20 }, {
utils.check_widgets_drawn({ p(first, 0, 0, 100, 10),
{ first, 0, 0, 100, 10 }, p(second, 0, 10, 100, 10),
{ second, 0, 10, 100, 10 }, p(third, 0, 20, 100, 0),
{ third, 0, 20, 100, 0 },
}) })
end) end)
end) 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) end)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 -- 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 flex = require("wibox.layout.flex")
local base = require("wibox.widget.base")
local utils = require("wibox.test_utils") local utils = require("wibox.test_utils")
local p = require("wibox.widget.base").place_widget_at
describe("wibox.layout.flex", function() describe("wibox.layout.flex", function()
local layout local layout
@ -12,16 +14,12 @@ describe("wibox.layout.flex", function()
layout = flex.vertical() layout = flex.vertical()
end) end)
before_each(utils.stub_draw_widget)
after_each(utils.revert_draw_widget)
it("empty layout fit", function() it("empty layout fit", function()
assert.widget_fit(layout, { 10, 10 }, { 0, 0 }) assert.widget_fit(layout, { 10, 10 }, { 0, 0 })
utils.check_widgets_drawn({})
end) end)
it("empty layout draw", function() it("empty layout layout", function()
layout:draw(nil, nil, 0, 0) assert.widget_layout(layout, { 0, 0 }, {})
end) end)
describe("with widgets", function() describe("with widgets", function()
@ -42,12 +40,11 @@ describe("wibox.layout.flex", function()
assert.widget_fit(layout, { 100, 100 }, { 15, 35 }) assert.widget_fit(layout, { 100, 100 }, { 15, 35 })
end) end)
it("draw", function() it("layout", function()
layout:draw("wibox", "cr", 100, 100) assert.widget_layout(layout, { 100, 100 }, {
utils.check_widgets_drawn({ p(first, 0, 0, 100, 33),
{ first, 0, 0, 100, 33 }, p(second, 0, 33, 100, 33),
{ second, 0, 33, 100, 33 }, p(third, 0, 67, 100, 33),
{ third, 0, 67, 100, 33 },
}) })
end) end)
end) end)
@ -57,12 +54,11 @@ describe("wibox.layout.flex", function()
assert.widget_fit(layout, { 5, 100 }, { 5, 35 }) assert.widget_fit(layout, { 5, 100 }, { 5, 35 })
end) end)
it("draw", function() it("layout", function()
layout:draw("wibox", "cr", 5, 100) assert.widget_layout(layout, { 5, 100 }, {
utils.check_widgets_drawn({ p(first, 0, 0, 5, 33),
{ first, 0, 0, 5, 33 }, p(second, 0, 33, 5, 33),
{ second, 0, 33, 5, 33 }, p(third, 0, 67, 5, 33),
{ third, 0, 67, 5, 33 },
}) })
end) end)
end) end)
@ -72,16 +68,68 @@ describe("wibox.layout.flex", function()
assert.widget_fit(layout, { 100, 20 }, { 15, 20 }) assert.widget_fit(layout, { 100, 20 }, { 15, 20 })
end) end)
it("draw", function() it("layout", function()
layout:draw("wibox", "cr", 100, 20) assert.widget_layout(layout, { 100, 20 }, {
utils.check_widgets_drawn({ p(first, 0, 0, 100, 6),
{ first, 0, 0, 100, 6 }, p(second, 0, 7, 100, 6),
{ second, 0, 7, 100, 6 }, p(third, 0, 13, 100, 6),
{ third, 0, 13, 100, 6 },
}) })
end) end)
end) 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) end)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -5,37 +5,21 @@
local object = require("gears.object") local object = require("gears.object")
local cache = require("gears.cache") local cache = require("gears.cache")
local wbase = require("wibox.widget.base") local matrix_equals = require("gears.matrix").equals
local lbase = require("wibox.layout.base") local base = require("wibox.widget.base")
local say = require("say") local say = require("say")
local assert = require("luassert") 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 -- {{{ Own widget-based assertions
local function widget_fit(state, arguments) local function widget_fit(state, arguments)
if #arguments ~= 3 then if #arguments ~= 3 then
return false error("Have " .. #arguments .. " arguments, but need 3")
end end
local widget = arguments[1] local widget = arguments[1]
local given = arguments[2] local given = arguments[2]
local expected = arguments[3] 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 local fits = expected[1] == w and expected[2] == h
if state.mod == fits then if state.mod == fits then
@ -52,51 +36,65 @@ local function widget_fit(state, arguments)
end end
say:set("assertion.widget_fit.positive", "Offering (%s, %s) to widget and expected (%s, %s), but got (%s, %s)") 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") 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 { return {
real_check_widget = real_check_widget,
widget_stub = function(width, height) widget_stub = function(width, height)
local w = object() local w = object()
w:add_signal("widget::redraw_needed")
w:add_signal("widget::layout_changed")
w.visible = true w.visible = true
w:add_signal("widget::updated") w.opacity = 1
if width or height then
w.fit = function() w.fit = function()
return width or 10, height or 10 return width or 10, height or 10
end
end end
w.draw = function() end w._widget_caches = {}
w._fit_geometry_cache = cache.new(w.fit)
spy.on(w, "fit")
stub(w, "draw")
return w return w
end, 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 -- 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