Explicitly track dependencies between widgets
Before this, dependencies between widgets where implicitly discovered by recursive calls to base.fit_widget() and base.layout_widget(). However, it is too easy to get this wrong (just call one of these functions from outside of a widget's :fit() / :layout() function) and the resulting mess would be hard to debug. Thus, this commit changes the API so that callers have to identify themselves and we can explicitly record the dependency between the widgets involved. This also fixes a bug where no dependencies were tracked for widgets after :set_visible(false). Whoops... Sorry for breaking the API for adding this. Signed-off-by: Uli Schlachter <psychon@znc.in>
This commit is contained in:
parent
2330040eb5
commit
41a8fabf4c
|
@ -12,12 +12,13 @@
|
||||||
local matrix = require("gears.matrix")
|
local matrix = require("gears.matrix")
|
||||||
local cairo = require("lgi").cairo
|
local cairo = require("lgi").cairo
|
||||||
local base = require("wibox.widget.base")
|
local base = require("wibox.widget.base")
|
||||||
|
local no_parent = base.no_parent_I_know_what_I_am_doing
|
||||||
|
|
||||||
local hierarchy = {}
|
local hierarchy = {}
|
||||||
|
|
||||||
local function hierarchy_new(context, widget, width, height, redraw_callback, layout_callback, callback_arg,
|
local function hierarchy_new(context, widget, width, height, redraw_callback, layout_callback, callback_arg,
|
||||||
matrix_to_parent, matrix_to_device)
|
matrix_to_parent, matrix_to_device)
|
||||||
local children = base.layout_widget(context, widget, width, height)
|
local children = base.layout_widget(no_parent, context, widget, width, height)
|
||||||
local draws_x1, draws_y1, draws_x2, draws_y2 = 0, 0, width, height
|
local draws_x1, draws_y1, draws_x2, draws_y2 = 0, 0, width, height
|
||||||
local result = {
|
local result = {
|
||||||
_matrix = matrix_to_parent,
|
_matrix = matrix_to_parent,
|
||||||
|
|
|
@ -40,7 +40,7 @@ function align:layout(context, width, height)
|
||||||
-- if the second widget doesn't exist, we will prioritise the first one
|
-- if the second widget doesn't exist, we will prioritise the first one
|
||||||
-- instead
|
-- instead
|
||||||
if self._expand ~= "inside" and self.second then
|
if self._expand ~= "inside" and self.second then
|
||||||
local w, h = base.fit_widget(context, self.second, width, height)
|
local w, h = base.fit_widget(self, context, self.second, width, height)
|
||||||
size_second = self.dir == "y" and h or w
|
size_second = self.dir == "y" and h or w
|
||||||
-- 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
|
||||||
|
@ -59,7 +59,7 @@ function align:layout(context, width, height)
|
||||||
-- into the remaining space
|
-- into the remaining space
|
||||||
if self._expand ~= "outside" then
|
if self._expand ~= "outside" then
|
||||||
if self.dir == "y" then
|
if self.dir == "y" then
|
||||||
_, h = base.fit_widget(context, self.first, width, size_remains)
|
_, h = base.fit_widget(self, context, self.first, width, size_remains)
|
||||||
size_first = h
|
size_first = h
|
||||||
-- for "inside", the third widget will get a chance to use the
|
-- for "inside", the third widget will get a chance to use the
|
||||||
-- remaining space, then the middle widget. For "none" we give
|
-- remaining space, then the middle widget. For "none" we give
|
||||||
|
@ -70,7 +70,7 @@ function align:layout(context, width, height)
|
||||||
size_remains = size_remains - h
|
size_remains = size_remains - h
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
w, _ = base.fit_widget(context, self.first, size_remains, height)
|
w, _ = base.fit_widget(self, context, self.first, size_remains, height)
|
||||||
size_first = w
|
size_first = w
|
||||||
if self._expand == "inside" or not self.second then
|
if self._expand == "inside" or not self.second then
|
||||||
size_remains = size_remains - w
|
size_remains = size_remains - w
|
||||||
|
@ -90,13 +90,13 @@ function align:layout(context, width, height)
|
||||||
local w, h, _ = width, height, nil
|
local w, h, _ = width, height, nil
|
||||||
if self._expand ~= "outside" then
|
if self._expand ~= "outside" then
|
||||||
if self.dir == "y" then
|
if self.dir == "y" then
|
||||||
_, h = base.fit_widget(context, self.third, width, size_remains)
|
_, h = base.fit_widget(self, context, self.third, width, size_remains)
|
||||||
-- give the middle widget the rest of the space for "inside" mode
|
-- give the middle widget the rest of the space for "inside" mode
|
||||||
if self._expand == "inside" then
|
if self._expand == "inside" then
|
||||||
size_remains = size_remains - h
|
size_remains = size_remains - h
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
w, _ = base.fit_widget(context, self.third, size_remains, height)
|
w, _ = base.fit_widget(self, context, self.third, size_remains, height)
|
||||||
if self._expand == "inside" then
|
if self._expand == "inside" then
|
||||||
size_remains = size_remains - w
|
size_remains = size_remains - w
|
||||||
end
|
end
|
||||||
|
@ -125,10 +125,10 @@ function align:layout(context, width, height)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if self.dir == "y" then
|
if self.dir == "y" then
|
||||||
_, h = base.fit_widget(context, self.second, width, size_second)
|
_, h = base.fit_widget(self, context, self.second, width, size_second)
|
||||||
y = floor( (height - h)/2 )
|
y = floor( (height - h)/2 )
|
||||||
else
|
else
|
||||||
w, _ = base.fit_widget(context, self.second, size_second, height)
|
w, _ = base.fit_widget(self, context, self.second, size_second, height)
|
||||||
x = floor( (width -w)/2 )
|
x = floor( (width -w)/2 )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -175,7 +175,7 @@ function align:fit(context, orig_width, orig_height)
|
||||||
local used_in_other = 0
|
local used_in_other = 0
|
||||||
|
|
||||||
for k, v in pairs{self.first, self.second, self.third} do
|
for k, v in pairs{self.first, self.second, self.third} do
|
||||||
local w, h = base.fit_widget(context, v, orig_width, orig_height)
|
local w, h = base.fit_widget(self, context, v, orig_width, orig_height)
|
||||||
|
|
||||||
local max = self.dir == "y" and w or h
|
local max = self.dir == "y" and w or h
|
||||||
if max > used_in_other then
|
if max > used_in_other then
|
||||||
|
|
|
@ -27,7 +27,7 @@ function constraint:fit(context, width, height)
|
||||||
w = self._strategy(width, self._width)
|
w = self._strategy(width, self._width)
|
||||||
h = self._strategy(height, self._height)
|
h = self._strategy(height, self._height)
|
||||||
|
|
||||||
w, h = base.fit_widget(context, self.widget, w, h)
|
w, h = base.fit_widget(self, context, self.widget, w, h)
|
||||||
else
|
else
|
||||||
w, h = 0, 0
|
w, h = 0, 0
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,7 +26,7 @@ function fixed:layout(context, width, height)
|
||||||
x, y = 0, pos
|
x, y = 0, pos
|
||||||
w, h = width, height - pos
|
w, h = width, height - pos
|
||||||
if k ~= #self.widgets or not self._fill_space then
|
if k ~= #self.widgets or not self._fill_space then
|
||||||
_, h = base.fit_widget(context, v, w, h);
|
_, h = base.fit_widget(self, context, v, w, h);
|
||||||
end
|
end
|
||||||
pos = pos + h + spacing
|
pos = pos + h + spacing
|
||||||
in_dir = h
|
in_dir = h
|
||||||
|
@ -34,7 +34,7 @@ function fixed:layout(context, width, height)
|
||||||
x, y = pos, 0
|
x, y = pos, 0
|
||||||
w, h = width - pos, height
|
w, h = width - pos, height
|
||||||
if k ~= #self.widgets or not self._fill_space then
|
if k ~= #self.widgets or not self._fill_space then
|
||||||
w, _ = base.fit_widget(context, v, w, h);
|
w, _ = base.fit_widget(self, context, v, w, h);
|
||||||
end
|
end
|
||||||
pos = pos + w + spacing
|
pos = pos + w + spacing
|
||||||
in_dir = w
|
in_dir = w
|
||||||
|
@ -65,7 +65,7 @@ function fixed:fit(context, orig_width, orig_height)
|
||||||
local used_in_dir, used_max = 0, 0
|
local used_in_dir, used_max = 0, 0
|
||||||
|
|
||||||
for k, v in pairs(self.widgets) do
|
for k, v in pairs(self.widgets) do
|
||||||
local w, h = base.fit_widget(context, v, width, height)
|
local w, h = base.fit_widget(self, context, v, width, height)
|
||||||
local in_dir, max
|
local in_dir, max
|
||||||
if self.dir == "y" then
|
if self.dir == "y" then
|
||||||
max, in_dir = w, h
|
max, in_dir = w, h
|
||||||
|
|
|
@ -73,7 +73,7 @@ function flex:fit(context, orig_width, orig_height)
|
||||||
local sub_width = self.dir == "y" and orig_width or orig_width / #self.widgets
|
local sub_width = self.dir == "y" and orig_width or orig_width / #self.widgets
|
||||||
|
|
||||||
for k, v in pairs(self.widgets) do
|
for k, v in pairs(self.widgets) do
|
||||||
local w, h = base.fit_widget(context, v, sub_width, sub_height)
|
local w, h = base.fit_widget(self, context, v, sub_width, sub_height)
|
||||||
|
|
||||||
local max = self.dir == "y" and w or h
|
local max = self.dir == "y" and w or h
|
||||||
if max > used_in_other then
|
if max > used_in_other then
|
||||||
|
|
|
@ -53,7 +53,7 @@ function margin:fit(context, width, height)
|
||||||
local extra_h = self.top + self.bottom
|
local extra_h = self.top + self.bottom
|
||||||
local w, h = 0, 0
|
local w, h = 0, 0
|
||||||
if self.widget then
|
if self.widget then
|
||||||
w, h = base.fit_widget(context, self.widget, width - extra_w, height - extra_h)
|
w, h = base.fit_widget(self, context, self.widget, width - extra_w, height - extra_h)
|
||||||
end
|
end
|
||||||
return w + extra_w, h + extra_h
|
return w + extra_w, h + extra_h
|
||||||
end
|
end
|
||||||
|
|
|
@ -45,7 +45,7 @@ function mirror:fit(context, ...)
|
||||||
if not self.widget then
|
if not self.widget then
|
||||||
return 0, 0
|
return 0, 0
|
||||||
end
|
end
|
||||||
return base.fit_widget(context, self.widget, ...)
|
return base.fit_widget(self, context, self.widget, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Set the widget that this layout mirrors.
|
--- Set the widget that this layout mirrors.
|
||||||
|
|
|
@ -54,7 +54,7 @@ function rotate:fit(context, width, height)
|
||||||
if not self.widget then
|
if not self.widget then
|
||||||
return 0, 0
|
return 0, 0
|
||||||
end
|
end
|
||||||
return transform(self, base.fit_widget(context, self.widget, transform(self, width, height)))
|
return transform(self, base.fit_widget(self, context, self.widget, transform(self, width, height)))
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Set the widget that this layout rotates.
|
--- Set the widget that this layout rotates.
|
||||||
|
|
|
@ -52,7 +52,7 @@ function background:fit(context, width, height)
|
||||||
return 0, 0
|
return 0, 0
|
||||||
end
|
end
|
||||||
|
|
||||||
return base.fit_widget(context, self.widget, width, height)
|
return base.fit_widget(self, 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
|
||||||
|
|
|
@ -19,41 +19,12 @@ local base = {}
|
||||||
|
|
||||||
-- {{{ Caches
|
-- {{{ Caches
|
||||||
|
|
||||||
local call_stack = {}
|
|
||||||
-- Indexes are widgets, allow them to be garbage-collected
|
-- Indexes are widgets, allow them to be garbage-collected
|
||||||
local widget_dependencies = setmetatable({}, { __mode = "k" })
|
local widget_dependencies = setmetatable({}, { __mode = "k" })
|
||||||
|
|
||||||
-- Don't do this in unit tests
|
-- Get the cache of the given kind for this widget. This returns a gears.cache
|
||||||
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.
|
-- that calls the callback of kind `kind` on the widget.
|
||||||
local function get_cache_and_record_deps(widget, kind)
|
local function get_cache(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
|
if not widget._widget_caches[kind] then
|
||||||
widget._widget_caches[kind] = cache.new(function(...)
|
widget._widget_caches[kind] = cache.new(function(...)
|
||||||
return widget[kind](widget, ...)
|
return widget[kind](widget, ...)
|
||||||
|
@ -62,24 +33,36 @@ local function get_cache_and_record_deps(widget, kind)
|
||||||
return widget._widget_caches[kind]
|
return widget._widget_caches[kind]
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Each call to the above function should be followed by a call to this
|
-- Special value to skip the dependency recording that is normally done by
|
||||||
-- function. Everything in-between is recorded as a dependency (it's
|
-- base.fit_widget() and base.layout_widget(). The caller must ensure that no
|
||||||
-- complicated...).
|
-- caches depend on the result of the call and/or must handle the childs
|
||||||
local function put_cache(widget)
|
-- widget::layout_changed signal correctly when using this.
|
||||||
assert(#call_stack ~= 0)
|
base.no_parent_I_know_what_I_am_doing = {}
|
||||||
if table.remove(call_stack) ~= widget then
|
|
||||||
put_cache(widget)
|
-- Record a dependency from parent to child: The layout of parent depends on the
|
||||||
|
-- layout of child.
|
||||||
|
local function record_dependency(parent, child)
|
||||||
|
if parent == base.no_parent_I_know_what_I_am_doing then
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
base.check_widget(parent)
|
||||||
|
base.check_widget(child)
|
||||||
|
|
||||||
|
local deps = widget_dependencies[child] or {}
|
||||||
|
deps[parent] = true
|
||||||
|
widget_dependencies[child] = deps
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Clear the caches for `widget` and all widgets that depend on it.
|
-- Clear the caches for `widget` and all widgets that depend on it.
|
||||||
local function clear_caches(widget)
|
local clear_caches
|
||||||
for w in pairs(widget_dependencies[widget] or {}) do
|
function clear_caches(widget)
|
||||||
widget_dependencies[w] = {}
|
local deps = widget_dependencies[widget] or {}
|
||||||
w._widget_caches = {}
|
|
||||||
end
|
|
||||||
widget_dependencies[widget] = {}
|
widget_dependencies[widget] = {}
|
||||||
widget._widget_caches = {}
|
widget._widget_caches = {}
|
||||||
|
for w in pairs(deps) do
|
||||||
|
clear_caches(w)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
|
@ -92,12 +75,15 @@ end
|
||||||
--- Fit a widget for the given available width and height. This calls the
|
--- 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
|
-- widget's `:fit` callback and caches the result for later use. Never call
|
||||||
-- `:fit` directly, but always through this function!
|
-- `:fit` directly, but always through this function!
|
||||||
|
-- @param parent The parent widget which requests this information.
|
||||||
-- @param context The context in which we are fit.
|
-- @param context The context in which we are fit.
|
||||||
-- @param widget The widget to fit (this uses widget:fit(width, height)).
|
-- @param widget The widget to fit (this uses widget:fit(width, height)).
|
||||||
-- @param width The available width for the widget
|
-- @param width The available width for the widget
|
||||||
-- @param height The available height for the widget
|
-- @param height The available height for the widget
|
||||||
-- @return The width and height that the widget wants to use
|
-- @return The width and height that the widget wants to use
|
||||||
function base.fit_widget(context, widget, width, height)
|
function base.fit_widget(parent, context, widget, width, height)
|
||||||
|
record_dependency(parent, widget)
|
||||||
|
|
||||||
if not widget.visible then
|
if not widget.visible then
|
||||||
return 0, 0
|
return 0, 0
|
||||||
end
|
end
|
||||||
|
@ -108,12 +94,10 @@ function base.fit_widget(context, widget, width, height)
|
||||||
|
|
||||||
local w, h = 0, 0
|
local w, h = 0, 0
|
||||||
if widget.fit then
|
if widget.fit then
|
||||||
local cache = get_cache_and_record_deps(widget, "fit")
|
w, h = get_cache(widget, "fit"):get(context, width, height)
|
||||||
w, h = cache:get(context, width, height)
|
|
||||||
put_cache(widget)
|
|
||||||
else
|
else
|
||||||
-- If it has no fit method, calculate based on the size of children
|
-- If it has no fit method, calculate based on the size of children
|
||||||
local children = base.layout_widget(context, widget, width, height)
|
local children = base.layout_widget(parent, context, widget, width, height)
|
||||||
for _, info in ipairs(children or {}) do
|
for _, info in ipairs(children or {}) do
|
||||||
local x, y, w2, h2 = matrix.transform_rectangle(info._matrix,
|
local x, y, w2, h2 = matrix.transform_rectangle(info._matrix,
|
||||||
0, 0, info._width, info._height)
|
0, 0, info._width, info._height)
|
||||||
|
@ -131,12 +115,15 @@ end
|
||||||
-- widget's `:layout` callback and caches the result for later use. Never call
|
-- widget's `:layout` callback and caches the result for later use. Never call
|
||||||
-- `:layout` directly, but always through this function! However, normally there
|
-- `:layout` directly, but always through this function! However, normally there
|
||||||
-- shouldn't be any reason why you need to use this function.
|
-- shouldn't be any reason why you need to use this function.
|
||||||
|
-- @param parent The parent widget which requests this information.
|
||||||
-- @param context The context in which we are laid out.
|
-- @param context The context in which we are laid out.
|
||||||
-- @param widget The widget to layout (this uses widget:layout(context, width, height)).
|
-- @param widget The widget to layout (this uses widget:layout(context, width, height)).
|
||||||
-- @param width The available width for the widget
|
-- @param width The available width for the widget
|
||||||
-- @param height The available height for the widget
|
-- @param height The available height for the widget
|
||||||
-- @return The result from the widget's `:layout` callback.
|
-- @return The result from the widget's `:layout` callback.
|
||||||
function base.layout_widget(context, widget, width, height)
|
function base.layout_widget(parent, context, widget, width, height)
|
||||||
|
record_dependency(parent, widget)
|
||||||
|
|
||||||
if not widget.visible then
|
if not widget.visible then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -146,10 +133,7 @@ function base.layout_widget(context, widget, width, height)
|
||||||
local height = math.max(0, height)
|
local height = math.max(0, height)
|
||||||
|
|
||||||
if widget.layout then
|
if widget.layout then
|
||||||
local cache = get_cache_and_record_deps(widget, "layout")
|
return get_cache(widget, "layout"):get(context, width, height)
|
||||||
local result = cache:get(context, width, height)
|
|
||||||
put_cache(widget)
|
|
||||||
return result
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -383,7 +367,7 @@ function base.make_widget(proxy, widget_name)
|
||||||
|
|
||||||
if proxy then
|
if proxy then
|
||||||
ret.fit = function(_, context, width, height)
|
ret.fit = function(_, context, width, height)
|
||||||
return base.fit_widget(context, proxy, width, height)
|
return base.fit_widget(ret, context, proxy, width, height)
|
||||||
end
|
end
|
||||||
ret.layout = function(_, context, width, height)
|
ret.layout = function(_, context, width, height)
|
||||||
return { base.place_widget_at(proxy, 0, 0, width, height) }
|
return { base.place_widget_at(proxy, 0, 0, width, height) }
|
||||||
|
|
|
@ -9,6 +9,7 @@ local matrix_equals = require("gears.matrix").equals
|
||||||
local base = require("wibox.widget.base")
|
local base = require("wibox.widget.base")
|
||||||
local say = require("say")
|
local say = require("say")
|
||||||
local assert = require("luassert")
|
local assert = require("luassert")
|
||||||
|
local no_parent = base.no_parent_I_know_what_I_am_doing
|
||||||
|
|
||||||
-- {{{ Own widget-based assertions
|
-- {{{ Own widget-based assertions
|
||||||
local function widget_fit(state, arguments)
|
local function widget_fit(state, arguments)
|
||||||
|
@ -19,7 +20,7 @@ local function widget_fit(state, arguments)
|
||||||
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 = base.fit_widget({ "fake context" }, widget, given[1], given[2])
|
local w, h = base.fit_widget(no_parent, { "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
|
||||||
|
|
Loading…
Reference in New Issue