2010-10-06 12:42:56 +02:00
|
|
|
---------------------------------------------------------------------------
|
|
|
|
-- @author Uli Schlachter
|
|
|
|
-- @copyright 2010 Uli Schlachter
|
|
|
|
-- @release @AWESOME_VERSION@
|
2015-02-25 11:18:53 +01:00
|
|
|
-- @classmod wibox.widget.base
|
2010-10-06 12:42:56 +02:00
|
|
|
---------------------------------------------------------------------------
|
|
|
|
|
2010-10-22 14:50:45 +02:00
|
|
|
local debug = require("gears.debug")
|
2010-10-06 12:42:56 +02:00
|
|
|
local object = require("gears.object")
|
2015-07-25 15:59:41 +02:00
|
|
|
local matrix = require("gears.matrix")
|
2015-06-14 16:01:56 +02:00
|
|
|
local Matrix = require("lgi").cairo.Matrix
|
2013-08-21 12:26:47 +02:00
|
|
|
local setmetatable = setmetatable
|
2010-10-06 12:42:56 +02:00
|
|
|
local pairs = pairs
|
|
|
|
local type = type
|
|
|
|
local table = table
|
|
|
|
|
2012-06-12 15:55:10 +02:00
|
|
|
local base = {}
|
2010-10-06 12:42:56 +02:00
|
|
|
|
2015-07-25 15:52:53 +02:00
|
|
|
--- Figure out the geometry in device coordinate space. This gives only tight
|
|
|
|
-- bounds if no rotations by non-multiples of 90° are used.
|
|
|
|
function base.rect_to_device_geometry(cr, x, y, width, height)
|
2015-07-25 15:59:41 +02:00
|
|
|
return matrix.transform_rectangle(cr.matrix, x, y, width, height)
|
2015-07-25 15:52:53 +02:00
|
|
|
end
|
|
|
|
|
2015-06-14 16:01:56 +02:00
|
|
|
--- 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}.
|
2012-11-18 20:44:03 +01:00
|
|
|
function base:buttons(_buttons)
|
2012-06-12 15:55:10 +02:00
|
|
|
if _buttons then
|
2012-11-18 20:44:03 +01:00
|
|
|
self.widget_buttons = _buttons
|
2010-10-06 12:42:56 +02:00
|
|
|
end
|
|
|
|
|
2012-11-18 20:44:03 +01:00
|
|
|
return self.widget_buttons
|
2010-10-06 12:42:56 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
--- Handle a button event on a widget. This is used internally.
|
2013-03-22 05:52:02 +01:00
|
|
|
function base.handle_button(event, widget, x, y, button, modifiers, geometry)
|
2010-10-06 12:42:56 +02:00
|
|
|
local function is_any(mod)
|
2012-05-13 00:23:23 +02:00
|
|
|
return #mod == 1 and mod[1] == "Any"
|
2010-10-06 12:42:56 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
local function tables_equal(a, b)
|
|
|
|
if #a ~= #b then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
for k, v in pairs(b) do
|
|
|
|
if a[k] ~= v then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Find all matching button objects
|
|
|
|
local matches = {}
|
|
|
|
for k, v in pairs(widget.widget_buttons) do
|
|
|
|
local match = true
|
|
|
|
-- Is it the right button?
|
|
|
|
if v.button ~= 0 and v.button ~= button then match = false end
|
|
|
|
-- Are the correct modifiers pressed?
|
|
|
|
if (not is_any(v.modifiers)) and (not tables_equal(v.modifiers, modifiers)) then match = false end
|
|
|
|
if match then
|
|
|
|
table.insert(matches, v)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Emit the signals
|
|
|
|
for k, v in pairs(matches) do
|
2013-03-22 05:52:02 +01:00
|
|
|
v:emit_signal(event,geometry)
|
2010-10-06 12:42:56 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-06-14 16:01:56 +02:00
|
|
|
--- 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
|
|
|
|
|
2010-10-06 12:42:56 +02:00
|
|
|
--- 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
|
|
|
|
-- widget. It will be equivalent to this widget.
|
2015-07-23 21:42:54 +02:00
|
|
|
-- @tparam[opt] string widget_name Name of the widget. If not set, it will be
|
|
|
|
-- set automatically via `gears.object.modulename`.
|
|
|
|
function base.make_widget(proxy, widget_name)
|
2010-10-06 12:42:56 +02:00
|
|
|
local ret = object()
|
|
|
|
|
|
|
|
-- This signal is used by layouts to find out when they have to update.
|
2015-06-14 16:01:56 +02:00
|
|
|
ret:add_signal("widget::layout_changed")
|
|
|
|
ret:add_signal("widget::redraw_needed")
|
2010-10-06 12:42:56 +02:00
|
|
|
-- Mouse input, oh noes!
|
|
|
|
ret:add_signal("button::press")
|
|
|
|
ret:add_signal("button::release")
|
2010-10-06 21:17:05 +02:00
|
|
|
ret:add_signal("mouse::enter")
|
|
|
|
ret:add_signal("mouse::leave")
|
2010-10-06 12:42:56 +02:00
|
|
|
|
2015-06-14 16:01:56 +02:00
|
|
|
-- 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)
|
|
|
|
|
2010-10-06 12:42:56 +02:00
|
|
|
-- No buttons yet
|
|
|
|
ret.widget_buttons = {}
|
2012-06-12 15:55:10 +02:00
|
|
|
ret.buttons = base.buttons
|
2010-10-06 12:42:56 +02:00
|
|
|
|
|
|
|
-- Make buttons work
|
|
|
|
ret:connect_signal("button::press", function(...)
|
2012-06-12 15:55:10 +02:00
|
|
|
return base.handle_button("press", ...)
|
2010-10-06 12:42:56 +02:00
|
|
|
end)
|
|
|
|
ret:connect_signal("button::release", function(...)
|
2012-06-12 15:55:10 +02:00
|
|
|
return base.handle_button("release", ...)
|
2010-10-06 12:42:56 +02:00
|
|
|
end)
|
|
|
|
|
|
|
|
if proxy then
|
2015-06-14 16:01:56 +02:00
|
|
|
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")
|
2010-10-06 12:42:56 +02:00
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2015-07-17 18:23:55 +02:00
|
|
|
-- Add visible property and setter.
|
|
|
|
ret.visible = true
|
|
|
|
function ret:set_visible(b)
|
|
|
|
if b ~= self.visible then
|
|
|
|
self.visible = b
|
2015-06-14 16:01:56 +02:00
|
|
|
self:emit_signal("widget::layout_changed")
|
|
|
|
-- In case something ignored fit and drew the widget anyway
|
|
|
|
self:emit_signal("widget::redraw_needed")
|
2015-07-17 18:23:55 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-08-13 00:31:06 +02:00
|
|
|
-- Add opacity property and setter.
|
|
|
|
ret.opacity = 1
|
|
|
|
function ret:set_opacity(b)
|
|
|
|
if b ~= self.opacity then
|
|
|
|
self.opacity = b
|
2015-06-14 16:01:56 +02:00
|
|
|
self:emit_signal("widget::redraw")
|
2015-08-13 00:31:06 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-07-23 21:42:54 +02:00
|
|
|
-- Add __tostring method to metatable.
|
|
|
|
ret.widget_name = widget_name or object.modulename(3)
|
|
|
|
local mt = {}
|
2015-07-29 21:09:08 +02:00
|
|
|
local orig_string = tostring(ret)
|
2015-07-23 21:42:54 +02:00
|
|
|
mt.__tostring = function(o)
|
2015-07-29 21:09:08 +02:00
|
|
|
return string.format("%s (%s)", ret.widget_name, orig_string)
|
2015-07-23 21:42:54 +02:00
|
|
|
end
|
|
|
|
return setmetatable(ret, mt)
|
2010-10-06 12:42:56 +02:00
|
|
|
end
|
|
|
|
|
2012-11-27 22:55:42 +01:00
|
|
|
--- Generate an empty widget which takes no space and displays nothing
|
|
|
|
function base.empty_widget()
|
2015-06-14 16:01:56 +02:00
|
|
|
return base.make_widget()
|
2012-11-27 22:55:42 +01:00
|
|
|
end
|
|
|
|
|
2010-10-06 12:42:56 +02:00
|
|
|
--- Do some sanity checking on widget. This function raises a lua error if
|
|
|
|
-- widget is not a valid widget.
|
2012-06-12 15:55:10 +02:00
|
|
|
function base.check_widget(widget)
|
2010-10-22 14:50:45 +02:00
|
|
|
debug.assert(type(widget) == "table")
|
2015-06-14 16:01:56 +02:00
|
|
|
for k, func in pairs({ "add_signal", "connect_signal", "disconnect_signal" }) do
|
2010-10-22 14:50:45 +02:00
|
|
|
debug.assert(type(widget[func]) == "function", func .. " is not a function")
|
2010-10-06 12:42:56 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-06-12 15:55:10 +02:00
|
|
|
return base
|
|
|
|
|
2011-09-11 16:50:01 +02:00
|
|
|
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|