Merge pull request #458 from psychon/explicit_widget_deps

Explicitly track dependencies between widgets

Closes https://github.com/awesomeWM/awesome/pull/458.
This commit is contained in:
Daniel Hahler 2015-09-21 21:12:36 +02:00
commit b0d7e6bb6c
11 changed files with 59 additions and 73 deletions

View File

@ -12,12 +12,13 @@
local matrix = require("gears.matrix")
local cairo = require("lgi").cairo
local base = require("wibox.widget.base")
local no_parent = base.no_parent_I_know_what_I_am_doing
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 children = base.layout_widget(no_parent, context, widget, width, height)
local draws_x1, draws_y1, draws_x2, draws_y2 = 0, 0, width, height
local result = {
_matrix = matrix_to_parent,

View File

@ -40,7 +40,7 @@ function align:layout(context, width, height)
-- if the second widget doesn't exist, we will prioritise the first one
-- instead
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
-- if all the space is taken, skip the rest, and draw just the middle
-- widget
@ -59,7 +59,7 @@ function align:layout(context, width, height)
-- into the remaining space
if self._expand ~= "outside" 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
-- for "inside", the third widget will get a chance to use the
-- 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
end
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
if self._expand == "inside" or not self.second then
size_remains = size_remains - w
@ -90,13 +90,13 @@ function align:layout(context, width, height)
local w, h, _ = width, height, nil
if self._expand ~= "outside" 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
if self._expand == "inside" then
size_remains = size_remains - h
end
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
size_remains = size_remains - w
end
@ -125,10 +125,10 @@ function align:layout(context, width, height)
end
else
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 )
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 )
end
end
@ -175,7 +175,7 @@ function align:fit(context, orig_width, orig_height)
local used_in_other = 0
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
if max > used_in_other then

View File

@ -27,7 +27,7 @@ function constraint:fit(context, width, height)
w = self._strategy(width, self._width)
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
w, h = 0, 0
end

View File

@ -26,7 +26,7 @@ function fixed:layout(context, width, height)
x, y = 0, pos
w, h = width, height - pos
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
pos = pos + h + spacing
in_dir = h
@ -34,7 +34,7 @@ function fixed:layout(context, width, height)
x, y = pos, 0
w, h = width - pos, height
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
pos = pos + w + spacing
in_dir = w
@ -65,7 +65,7 @@ function fixed:fit(context, orig_width, orig_height)
local used_in_dir, used_max = 0, 0
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
if self.dir == "y" then
max, in_dir = w, h

View File

@ -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
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
if max > used_in_other then

View File

@ -53,7 +53,7 @@ function margin:fit(context, width, height)
local extra_h = self.top + self.bottom
local w, h = 0, 0
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
return w + extra_w, h + extra_h
end

View File

@ -45,7 +45,7 @@ function mirror:fit(context, ...)
if not self.widget then
return 0, 0
end
return base.fit_widget(context, self.widget, ...)
return base.fit_widget(self, context, self.widget, ...)
end
--- Set the widget that this layout mirrors.

View File

@ -54,7 +54,7 @@ function rotate:fit(context, width, height)
if not self.widget then
return 0, 0
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
--- Set the widget that this layout rotates.

View File

@ -52,7 +52,7 @@ function background:fit(context, width, height)
return 0, 0
end
return base.fit_widget(context, self.widget, width, height)
return base.fit_widget(self, context, self.widget, width, height)
end
--- Set the widget that is drawn on top of the background

View File

@ -18,41 +18,12 @@ 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
-- Get the cache of the given kind for this widget. This returns a gears.cache
-- 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
local function get_cache(widget, kind)
if not widget._widget_caches[kind] then
widget._widget_caches[kind] = cache.new(function(...)
return widget[kind](widget, ...)
@ -61,24 +32,36 @@ local function get_cache_and_record_deps(widget, kind)
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)
-- Special value to skip the dependency recording that is normally done by
-- base.fit_widget() and base.layout_widget(). The caller must ensure that no
-- caches depend on the result of the call and/or must handle the childs
-- widget::layout_changed signal correctly when using this.
base.no_parent_I_know_what_I_am_doing = {}
-- 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
base.check_widget(parent)
base.check_widget(child)
local deps = widget_dependencies[child] or {}
deps[parent] = true
widget_dependencies[child] = deps
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
local clear_caches
function clear_caches(widget)
local deps = widget_dependencies[widget] or {}
widget_dependencies[widget] = {}
widget._widget_caches = {}
for w in pairs(deps) do
clear_caches(w)
end
end
-- }}}
@ -91,12 +74,15 @@ end
--- 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 parent The parent widget which requests this information.
-- @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)
function base.fit_widget(parent, context, widget, width, height)
record_dependency(parent, widget)
if not widget.visible then
return 0, 0
end
@ -107,12 +93,10 @@ function base.fit_widget(context, widget, width, 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)
w, h = get_cache(widget, "fit"):get(context, width, height)
else
-- 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
local x, y, w2, h2 = matrix.transform_rectangle(info._matrix,
0, 0, info._width, info._height)
@ -130,12 +114,15 @@ end
-- 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 parent The parent widget which requests this information.
-- @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)
function base.layout_widget(parent, context, widget, width, height)
record_dependency(parent, widget)
if not widget.visible then
return
end
@ -145,10 +132,7 @@ function base.layout_widget(context, widget, width, height)
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
return get_cache(widget, "layout"):get(context, width, height)
end
end
@ -382,7 +366,7 @@ function base.make_widget(proxy, widget_name)
if proxy then
ret.fit = function(_, context, width, height)
return base.fit_widget(context, proxy, width, height)
return base.fit_widget(ret, context, proxy, width, height)
end
ret.layout = function(_, context, width, height)
return { base.place_widget_at(proxy, 0, 0, width, height) }

View File

@ -9,6 +9,7 @@ local matrix_equals = require("gears.matrix").equals
local base = require("wibox.widget.base")
local say = require("say")
local assert = require("luassert")
local no_parent = base.no_parent_I_know_what_I_am_doing
-- {{{ Own widget-based assertions
local function widget_fit(state, arguments)
@ -19,7 +20,7 @@ local function widget_fit(state, arguments)
local widget = arguments[1]
local given = arguments[2]
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
if state.mod == fits then