--------------------------------------------------------------------------- -- @author Uli Schlachter -- @copyright 2010 Uli Schlachter -- @release @AWESOME_VERSION@ --------------------------------------------------------------------------- local capi = { drawin = drawin, root = root, awesome = awesome } local setmetatable = setmetatable local pairs = pairs local type = type local table = table local string_format = string.format local color = require("gears.color") local object = require("gears.object") local sort = require("gears.sort") local beautiful = require("beautiful") local surface = require("gears.surface") local cairo = require("lgi").cairo --- This provides widget box windows. Every wibox can also be used as if it were -- a drawin. All drawin functions and properties are also available on wiboxes! -- wibox local wibox = { mt = {} } wibox.layout = require("wibox.layout") wibox.widget = require("wibox.widget") local function do_redraw(_wibox) if not _wibox.drawin.visible then return end local geom = _wibox.drawin:geometry() local cr = cairo.Context(surface(_wibox.drawin.surface)) -- Draw the background cr:save() -- This is pseudo-transparency: We draw the wallpaper in the background local wallpaper = surface(capi.root.wallpaper()) if wallpaper then cr.operator = cairo.Operator.SOURCE cr:set_source_surface(wallpaper, -_wibox.drawin.x, -_wibox.drawin.y) cr:paint() end cr.operator = cairo.Operator.OVER cr:set_source(_wibox.background_color) cr:paint() cr:restore() -- Draw the widget _wibox._widget_geometries = {} if _wibox.widget and not _wibox.widget.__fake_widget then cr:set_source(_wibox.foreground_color) _wibox.widget:draw(_wibox, cr, geom.width, geom.height) _wibox:widget_at(_wibox.widget, 0, 0, geom.width, geom.height) end _wibox.drawin:refresh() end --- Register a widget's position. -- This is internal, don't call it yourself! Only wibox.layout.base.draw_widget -- is allowed to call this. function wibox.widget_at(_wibox, widget, x, y, width, height) local t = { widget = widget, x = x, y = y, width = width, height = height } table.insert(_wibox._widget_geometries, t) end --- Find a widget by a point. -- The wibox must have drawn itself at least once for this to work. -- @param wibox The wibox to look at -- @param x X coordinate of the point -- @param y Y coordinate of the point -- @return A sorted table with all widgets that contain the given point. The -- widgets are sorted by relevance. function wibox.find_widgets(_wibox, x, y) local matches = {} -- Find all widgets that contain the point for k, v in pairs(_wibox._widget_geometries) do local match = true if v.x > x or v.x + v.width <= x then match = false end if v.y > y or v.y + v.height <= y then match = false end if match then table.insert(matches, v) end end -- Sort the matches by area, the assumption here is that widgets don't -- overlap and so smaller widgets are "more specific". local function cmp(a, b) local area_a = a.width * a.height local area_b = b.width * b.height return area_a < area_b end sort(matches, cmp) return matches end --- Set the widget that the wibox displays function wibox.set_widget(_wibox, widget) if _wibox.widget and not _wibox.widget.__fake_widget then -- Disconnect from the old widget so that we aren't updated due to it _wibox.widget:disconnect_signal("widget::updated", _wibox.draw) end if not widget then _wibox.widget = { __fake_widget = true } else _wibox.widget = widget widget:connect_signal("widget::updated", _wibox.draw) end -- Make sure the wibox is updated _wibox.draw() end --- Set the background of the wibox -- @param wibox The wibox to use -- @param c The background to use. This must either be a cairo pattern object, -- nil or a string that gears.color() understands. function wibox.set_bg(_wibox, c) local c = c or "#000000" if type(c) == "string" or type(c) == "table" then c = color(c) end _wibox.background_color = c _wibox.draw() end --- Set the foreground of the wibox -- @param wibox The wibox to use -- @param c The foreground to use. This must either be a cairo pattern object, -- nil or a string that gears.color() understands. function wibox.set_fg(_wibox, c) local c = c or "#FFFFFF" if type(c) == "string" or type(c) == "table" then c = color(c) end _wibox.foreground_color = c _wibox.draw() end --- Helper function to make wibox:buttons() work as expected function wibox.buttons(box, ...) return box.drawin:buttons(...) end --- Helper function to make wibox:struts() work as expected function wibox.struts(box, ...) return box.drawin:struts(...) end --- Helper function to make wibox:geometry() work as expected function wibox.geometry(box, ...) return box.drawin:geometry(...) end local function emit_difference(name, list, skip) local function in_table(table, val) for k, v in pairs(table) do if v == val then return true end end return false end for k, v in pairs(list) do if not in_table(skip, v) then v:emit_signal(name) end end end local function handle_leave(_wibox) emit_difference("mouse::leave", _wibox._widgets_under_mouse, {}) _wibox._widgets_under_mouse = {} end local function handle_motion(_wibox, x, y) if x < 0 or y < 0 or x > _wibox.drawin.width or y > _wibox.drawin.height then return handle_leave(_wibox) end -- Build a plain list of all widgets on that point local widgets_list = _wibox:find_widgets(x, y) local widgets = {} for k, v in pairs(widgets_list) do widgets[#widgets + 1] = v.widget end -- First, "leave" all widgets that were left emit_difference("mouse::leave", _wibox._widgets_under_mouse, widgets) -- Then enter some widgets emit_difference("mouse::enter", widgets, _wibox._widgets_under_mouse) _wibox._widgets_under_mouse = widgets end local function setup_signals(_wibox) local w = _wibox.drawin local function clone_signal(name) _wibox:add_signal(name) -- When "name" is emitted on wibox.drawin, also emit it on wibox w:connect_signal(name, function(_, ...) _wibox:emit_signal(name, ...) end) end clone_signal("mouse::enter") clone_signal("mouse::leave") clone_signal("mouse::move") clone_signal("property::border_color") clone_signal("property::border_width") clone_signal("property::buttons") clone_signal("property::cursor") clone_signal("property::height") clone_signal("property::ontop") clone_signal("property::opacity") clone_signal("property::struts") clone_signal("property::visible") clone_signal("property::widgets") clone_signal("property::width") clone_signal("property::x") clone_signal("property::y") -- Update the wibox when its geometry changes w:connect_signal("property::height", _wibox.draw) w:connect_signal("property::width", _wibox.draw) w:connect_signal("property::visible", function() if w.visible then capi.awesome.connect_signal("wallpaper_changed", _wibox.draw) _wibox.draw() else capi.awesome.disconnect_signal("wallpaper_changed", _wibox.draw) end end) local function button_signal(name) w:connect_signal(name, function(w, x, y, ...) local widgets = _wibox:find_widgets(x, y) for k, v in pairs(widgets) do -- Calculate x/y inside of the widget local lx = x - v.x local ly = y - v.y v.widget:emit_signal(name, lx, ly, ...) end end) end button_signal("button::press") button_signal("button::release") _wibox:connect_signal("mouse::move", handle_motion) _wibox:connect_signal("mouse::leave", handle_leave) end local function new(args) local ret = object() local w = capi.drawin(args) ret.drawin = w for k, v in pairs(wibox) do if type(v) == "function" then ret[k] = v end end -- This is to make sure that the wibox will only be redrawn once even when -- we receive multiple widget::updated signals. ret._redraw_pending = false ret._do_redraw = function() ret._redraw_pending = false capi.awesome.disconnect_signal("refresh", ret._do_redraw) do_redraw(ret) end -- Connect our signal when we need a redraw ret.draw = function() if not ret._redraw_pending then capi.awesome.connect_signal("refresh", ret._do_redraw) ret._redraw_pending = true end end setup_signals(ret) -- Set the default background ret:set_bg(args.bg or beautiful.bg_normal) ret:set_fg(args.fg or beautiful.fg_normal) -- Make sure the wibox is drawn at least once ret.draw() -- Due to the metatable below, we need this trick ret.widget = { __fake_widget = true } ret._widget_geometries = {} ret._widgets_under_mouse = {} -- Redirect all non-existing indexes to the "real" drawin setmetatable(ret, { __index = w, __newindex = w }) return ret end --- Redraw a wibox. You should never have to call this explicitely because it is -- automatically called when needed. -- @param wibox -- @name draw -- @class function --- Widget box object. -- Every wibox "inherits" from a drawin and you can use all of drawin's -- functions directly on this as well. When creating a wibox, you can specify a -- "fg" and a "bg" color as keys in the table that is passed to the constructor. -- All other arguments will be passed to drawin's constructor. -- @class table -- @name drawin function wibox.mt:__call(...) return new(...) end return setmetatable(wibox, wibox.mt) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80