Prepare wibox.widget.base for new widget system

Signed-off-by: Uli Schlachter <psychon@znc.in>
This commit is contained in:
Uli Schlachter 2015-06-14 16:01:56 +02:00
parent ad9e57d0c1
commit 4785b63755
2 changed files with 124 additions and 28 deletions

View File

@ -26,7 +26,7 @@ function base.fit_widget(context, widget, width, height)
local width = math.max(0, width) local width = math.max(0, width)
local height = math.max(0, height) local height = math.max(0, height)
local w, h = widget._fit_geometry_cache:get(context, width, height) local w, h = widget:fit(context, width, height)
-- Also sanitize the output. -- Also sanitize the output.
w = math.max(0, math.min(w, width)) w = math.max(0, math.min(w, width))

View File

@ -7,8 +7,8 @@
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 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
@ -22,7 +22,67 @@ 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
w, h = widget:fit(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)
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
return widget:layout(context, width, height)
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
@ -68,6 +128,40 @@ function base.handle_button(event, widget, x, y, button, modifiers, geometry)
end end
end end
--- Create widget placement information. This should be used for a widget's
-- :layout() callback.
-- @param widget The widget that should be placed.
-- @param mat A cairo matrix transforming from the parent widget's coordinate
-- system. For example, use cairo.Matrix.create_translate(1, 2) to draw a
-- widget at position (1, 2) relative to the parent widget.
-- @param width The width of the widget in its own coordinate system. That is,
-- after applying the transformation matrix.
-- @param height The height of the widget in its own coordinate system. That is,
-- after applying the transformation matrix.
-- @return An opaque object that can be returned from :layout()
function base.place_widget_via_matrix(widget, mat, width, height)
return {
_widget = widget,
_width = width,
_height = height,
_matrix = matrix.copy(mat)
}
end
--- Create widget placement information. This should be used for a widget's
-- :layout() callback.
-- @param widget The widget that should be placed.
-- @param x The x coordinate for the widget.
-- @param y The y coordinate for the widget.
-- @param width The width of the widget in its own coordinate system. That is,
-- after applying the transformation matrix.
-- @param height The height of the widget in its own coordinate system. That is,
-- after applying the transformation matrix.
-- @return An opaque object that can be returned from :layout()
function base.place_widget_at(widget, x, y, width, height)
return base.place_widget_via_matrix(widget, Matrix.create_translate(x, y), width, height)
end
--- Create a new widget. All widgets have to be generated via this function so --- 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. -- that the needed signals are added and mouse input handling is set up.
-- @param proxy If this is set, the returned widget will be a proxy for this -- @param proxy If this is set, the returned widget will be a proxy for this
@ -78,13 +172,22 @@ 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,28 +201,28 @@ 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
local function cb(...)
return ret:fit(...)
end
ret._fit_geometry_cache = cache.new(cb)
ret:connect_signal("widget::updated", function()
ret._fit_geometry_cache = cache.new(cb)
end)
-- Add visible property and setter. -- Add visible property and setter.
ret.visible = true ret.visible = true
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 +231,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 +247,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