From 4785b63755c2b180ff636f1f0f5cb85dd9052c0f Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 14 Jun 2015 16:01:56 +0200 Subject: [PATCH] Prepare wibox.widget.base for new widget system Signed-off-by: Uli Schlachter --- lib/wibox/layout/base.lua | 2 +- lib/wibox/widget/base.lua | 150 +++++++++++++++++++++++++++++++------- 2 files changed, 124 insertions(+), 28 deletions(-) diff --git a/lib/wibox/layout/base.lua b/lib/wibox/layout/base.lua index a92052769..2ba8b99a1 100644 --- a/lib/wibox/layout/base.lua +++ b/lib/wibox/layout/base.lua @@ -26,7 +26,7 @@ function base.fit_widget(context, widget, width, height) local width = math.max(0, width) 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. w = math.max(0, math.min(w, width)) diff --git a/lib/wibox/widget/base.lua b/lib/wibox/widget/base.lua index 16f2b8eea..22b612a76 100644 --- a/lib/wibox/widget/base.lua +++ b/lib/wibox/widget/base.lua @@ -7,8 +7,8 @@ local debug = require("gears.debug") local object = require("gears.object") -local cache = require("gears.cache") local matrix = require("gears.matrix") +local Matrix = require("lgi").cairo.Matrix local setmetatable = setmetatable local pairs = pairs local type = type @@ -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) 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) if _buttons then self.widget_buttons = _buttons @@ -68,6 +128,40 @@ function base.handle_button(event, widget, x, y, button, modifiers, geometry) 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 -- 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 @@ -78,13 +172,22 @@ function base.make_widget(proxy, widget_name) local ret = object() -- This signal is used by layouts to find out when they have to update. - ret:add_signal("widget::updated") + ret:add_signal("widget::layout_changed") + ret:add_signal("widget::redraw_needed") -- Mouse input, oh noes! ret:add_signal("button::press") ret:add_signal("button::release") ret:add_signal("mouse::enter") ret:add_signal("mouse::leave") + -- Backwards compatibility + -- TODO: Remove this + ret:add_signal("widget::updated") + ret:connect_signal("widget::updated", function() + ret:emit_signal("widget::layout_changed") + ret:emit_signal("widget::redraw_needed") + end) + -- No buttons yet ret.widget_buttons = {} ret.buttons = base.buttons @@ -98,28 +201,28 @@ function base.make_widget(proxy, widget_name) end) if proxy then - ret.draw = function(_, ...) return proxy:draw(...) end - ret.fit = function(_, ...) return proxy:fit(...) end - proxy:connect_signal("widget::updated", function() - ret:emit_signal("widget::updated") + ret.fit = function(_, context, width, height) + return base.fit_widget(context, proxy, width, height) + end + ret.layout = function(_, context, width, height) + return { base.place_widget_at(proxy, 0, 0, width, height) } + end + proxy:connect_signal("widget::layout_changed", function() + ret:emit_signal("widget::layout_changed") + end) + proxy:connect_signal("widget::redraw_needed", function() + ret:emit_signal("widget::redraw_needed") end) end - -- Add a geometry for base.fit_widget() that is cleared when necessary - local function cb(...) - return ret:fit(...) - end - ret._fit_geometry_cache = cache.new(cb) - ret:connect_signal("widget::updated", function() - ret._fit_geometry_cache = cache.new(cb) - end) - -- Add visible property and setter. ret.visible = true function ret:set_visible(b) if b ~= self.visible then self.visible = b - self:emit_signal("widget::updated") + self:emit_signal("widget::layout_changed") + -- In case something ignored fit and drew the widget anyway + self:emit_signal("widget::redraw_needed") end end @@ -128,7 +231,7 @@ function base.make_widget(proxy, widget_name) function ret:set_opacity(b) if b ~= self.opacity then self.opacity = b - self:emit_signal("widget::updated") + self:emit_signal("widget::redraw") end end @@ -144,23 +247,16 @@ end --- Generate an empty widget which takes no space and displays nothing function base.empty_widget() - local widget = base.make_widget() - widget.draw = function() end - widget.fit = function() return 0, 0 end - return widget + return base.make_widget() end --- Do some sanity checking on widget. This function raises a lua error if -- widget is not a valid widget. function base.check_widget(widget) debug.assert(type(widget) == "table") - for k, func in pairs({ "draw", "fit", "add_signal", "connect_signal", "disconnect_signal" }) do + for k, func in pairs({ "add_signal", "connect_signal", "disconnect_signal" }) do debug.assert(type(widget[func]) == "function", func .. " is not a function") end - - local width, height = widget:fit({}, 0, 0) - debug.assert(type(width) == "number") - debug.assert(type(height) == "number") end return base