Import lib/wibox/, a new widget system in lua

Signed-off-by: Uli Schlachter <psychon@znc.in>
This commit is contained in:
Uli Schlachter 2010-10-06 12:42:56 +02:00
parent a924a92e07
commit 2eae7e5cf4
14 changed files with 1583 additions and 0 deletions

254
lib/wibox/init.lua.in Normal file
View File

@ -0,0 +1,254 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
require("wibox.layout")
require("wibox.widget")
local capi = {
drawin = drawin,
oocairo = oocairo,
timer = timer
}
local setmetatable = setmetatable
local pairs = pairs
local type = type
local table = table
local color = require("gears.color")
local object = require("gears.object")
local sort = require("gears.sort")
local beautiful = require("beautiful")
module("wibox")
local function do_redraw(wibox)
if not wibox.drawin.screen or not wibox.drawin.visible then
return
end
local geom = wibox.drawin:geometry()
local cr = capi.oocairo.context_create(wibox.drawin.surface)
-- Clear the drawin
cr:save()
cr:set_operator("source")
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 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 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 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 set_bg(wibox, c)
local c = c
if type(c) == "string" 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 set_fg(wibox, c)
local c = c
if type(c) == "string" then
c = color(c)
end
wibox.foreground_color = c
wibox.draw()
end
--- Helper function to make wibox:buttons() work as expected
function buttons(box, ...)
return box.drawin:buttons(...)
end
--- Helper function to make wibox:struts() work as expected
function struts(box, ...)
return box.drawin:struts(...)
end
--- Helper function to make wibox:geometry() work as expected
function geometry(box, ...)
return box.drawin:geometry(...)
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, wibox, ...)
end)
end
clone_signal("mouse::enter")
clone_signal("mouse::leave")
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::screen")
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::screen", wibox.draw)
w:connect_signal("property::visible", wibox.draw)
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")
end
local function new(args)
local ret = object()
local w = capi.drawin(args)
ret.drawin = w
for k, v in pairs(_M) do
if type(v) == "function" then
ret[k] = v
end
end
-- We use a timer with timeout 0, this makes sure that the wibox will only
-- redrawn once even if there are multiple widget::updated events
local t = capi.timer({ timeout = 0 })
local function update_wibox()
t:stop()
do_redraw(ret)
end
t:connect_signal("timeout", update_wibox)
-- Start our timer when the widget changed
ret.draw = function()
if not t.started then
t:start()
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 = {}
-- Redirect all non-existing indexes to the "real" drawin
setmetatable(ret, {
__index = w,
__newindex = w
})
return ret
end
setmetatable(_M, { __call = function(_, ...) return new(...) end })
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80

View File

@ -0,0 +1,149 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
local setmetatable = setmetatable
local table = table
local pairs = pairs
local type = type
local base = require("wibox.layout.base")
local widget_base = require("wibox.widget.base")
module("wibox.layout.align")
-- Draw the given align layout. dir describes the orientation of the layout, "x"
-- means horizontal while "y" is vertical.
local function draw(dir, layout, wibox, cr, width, height)
local size_first = 0
local size_third = 0
local size_limit = dir == "y" and height or width
if layout.first then
local w, h = width, height
if dir == "y" then
_, h = layout.first:fit(w, h)
size_first = h
else
w, _ = layout.first:fit(w, h)
size_first = w
end
base.draw_widget(wibox, cr, layout.first, 0, 0, w, h)
end
if layout.third and size_first < size_limit then
local w, h, x, y
if dir == "y" then
w, h = width, height - size_first
_, h = layout.third:fit(w, h)
x, y = 0, height - h
size_third = h
else
w, h = width - size_first, height
w, _ = layout.third:fit(w, h)
x, y = width - w, 0
size_third = w
end
base.draw_widget(wibox, cr, layout.third, x, y, w, h)
end
if layout.second and size_first + size_third < size_limit then
local x, y, w, h
if dir == "y" then
x, y = 0, size_first
w, h = width, size_limit - size_first - size_third
else
x, y = size_first, 0
w, h = size_limit - size_first - size_third, height
end
base.draw_widget(wibox, cr, layout.second, x, y, w, h)
end
end
local function widget_changed(layout, old_w, new_w)
if old_w then
old_w:disconnect_signal("widget::updated", layout._emit_updated)
end
if new_w then
widget_base.check_widget(new_w)
new_w:connect_signal("widget::updated", layout._emit_updated)
end
layout._emit_updated()
end
--- Set the layout's first widget. This is the widget that is at the left/top
function set_first(layout, widget)
widget_changed(layout, layout.first, widget)
layout.first = widget
end
--- Set the layout's second widget. This is the centered one.
function set_second(layout, widget)
widget_changed(layout, widget)
layout.second = widget
end
--- Set the layout's third widget. This is the widget that is at the right/bottom
function set_third(layout, widget)
widget_changed(layout, widget)
layout.third = widget
end
function reset(layout)
for k, v in pairs({ "first", "second", "third" }) do
layout[v] = nil
end
layout:emit_signal("widget::updated")
end
local function get_layout(dir)
local function draw_dir(layout, wibox, cr, width, height)
draw(dir, layout, wibox, cr, width, height)
end
local ret = widget_base.make_widget()
ret.draw = draw_dir
ret.fit = function(box, ...) return ... end
ret._emit_updated = function()
ret:emit_signal("widget::updated")
end
for k, v in pairs(_M) do
if type(v) == "function" then
ret[k] = v
end
end
return ret
end
--- Returns a new horizontal align layout. An align layout can display up to
-- three widgets. The widget set via :set_left() is left-aligned. :set_right()
-- sets a widget which will be right-aligned. The remaining space between those
-- two will be given to the widget set via :set_middle().
function horizontal()
local ret = get_layout("x")
ret.set_left = ret.set_first
ret.set_middle = ret.set_second
ret.set_right = ret.set_third
return ret
end
--- Returns a new vertical align layout. An align layout can display up to
-- three widgets. The widget set via :set_top() is top-aligned. :set_bottom()
-- sets a widget which will be bottom-aligned. The remaining space between those
-- two will be given to the widget set via :set_middle().
function vertical()
local ret = get_layout("y")
ret.set_top = ret.set_first
ret.set_middle = ret.set_second
ret.set_bottom = ret.set_third
return ret
end
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80

View File

@ -0,0 +1,66 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
local pairs = pairs
local pcall = pcall
local print = print
module("wibox.layout.base")
--- Figure out the geometry in device coordinate space. This will break if
-- someone rotates the coordinate space by a non-multiple of 90°.
function rect_to_device_geometry(cr, x, y, width, height)
local function min(a, b)
if a < b then return a end
return b
end
local function max(a, b)
if a > b then return a end
return b
end
local x1, y1 = cr:user_to_device(x, y)
local x2, y2 = cr:user_to_device(x + width, y + height)
local x = min(x1, x2)
local y = min(y1, y2)
local width = max(x1, x2) - x
local height = max(y1, y2) - y
return x, y, width, height
end
--- Draw a widget via a cairo context
-- @param wibox The wibox on which we are drawing
-- @param cr The cairo context used
-- @param widget The widget to draw (this uses widget:draw(cr, width, height)).
-- @param x The position that the widget should get
-- @param y The position that the widget should get
-- @param width The widget's width
-- @param height The widget's height
function draw_widget(wibox, cr, widget, x, y, width, height)
-- Use save() / restore() so that our modifications aren't permanent
cr:save()
-- Move (0, 0) to the place where the widget should show up
cr:translate(x, y)
-- Make sure the widget cannot draw outside of the allowed area
cr:rectangle(0, 0, width, height)
cr:clip()
-- Let the widget draw itself
local success, msg = pcall(widget.draw, widget, wibox, cr, width, height)
if not success then
print("Error while drawing widget: " .. msg)
end
-- Register the widget for input handling
wibox:widget_at(widget, rect_to_device_geometry(cr, 0, 0, width, height))
cr:restore()
end
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80

View File

@ -0,0 +1,153 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
local base = require("wibox.layout.base")
local widget_base = require("wibox.widget.base")
local table = table
local pairs = pairs
module("wibox.layout.fixed")
--- Draw a fixed layout. Each widget gets just the space it asks for.
-- @param dir "x" for a horizontal layout and "y" for vertical.
-- @param widgets The widgets to draw.
-- @param fill_space Use all the available space, giving all that is left to the
-- last widget
-- @param cr The cairo context to use.
-- @param width The available width.
-- @param height The available height.
-- @return The total space needed by the layout.
function draw_fixed(dir, widgets, fill_space, wibox, cr, width, height)
local pos = 0
for k, v in pairs(widgets) do
local x, y, w, h
if dir == "y" then
x, y = 0, pos
w, h = width, height - pos
if k ~= #widgets or not fill_space then
_, h = v:fit(w, h);
end
pos = pos + h
else
x, y = pos, 0
w, h = width - pos, height
if k ~= #widgets or not fill_space then
w, _ = v:fit(w, h);
end
pos = pos + w
end
base.draw_widget(wibox, cr, v, x, y, w, h)
if (dir == "y" and pos >= height) or
(dir ~= "y" and pos >= width) then
break
end
end
end
--- Add a widget to the given fixed layout
function add(layout, widget)
widget_base.check_widget(widget)
table.insert(layout.widgets, widget)
widget:connect_signal("widget::updated", layout._emit_updated)
layout._emit_updated()
end
--- Fit the fixed layout into the given space
-- @param dir "x" for a horizontal layout and "y" for vertical.
-- @param widgets The widgets to fit.
-- @param orig_width The available width.
-- @param orig_height The available height.
function fit_fixed(dir, widgets, orig_width, orig_height)
local width, height = orig_width, orig_height
local used_in_dir, used_max = 0, 0
for k, v in pairs(widgets) do
local w, h = v:fit(width, height)
local in_dir, max
if dir == "y" then
max, in_dir = w, h
height = height - in_dir
else
in_dir, max = w, h
width = width - in_dir
end
if max > used_max then
used_max = max
end
used_in_dir = used_in_dir + in_dir
if width <= 0 or height <= 0 then
if dir == "y" then
used_in_dir = orig_height
else
used_in_dir = orig_width
end
break
end
end
if dir == "y" then
return used_max, used_in_dir
end
return used_in_dir, used_max
end
--- Reset a fixed layout. This removes all widgets from the layout.
function reset(layout)
for k, v in pairs(layout.widget) do
v:disconnect_signal("widget::updated", layout._emit_updated)
end
layout.widget = {}
layout:emit_signal("widget::updated")
end
--- Set the layout's fill_space property. If this property is true, the last
-- widget will get all the space that is left. If this is false, the last widget
-- won't be handled specially and there can be space left unused.
function fill_space(layout, val)
layout._fill_space = val
layout:emit_signal("widget::updated")
end
local function get_layout(dir)
local function draw(layout, ...)
draw_fixed(dir, layout.widgets, layout._fill_space, ...)
end
local function fit(layout, ...)
return fit_fixed(dir, layout.widgets, ...)
end
local ret = widget_base.make_widget()
ret.draw = draw
ret.fit = fit
ret.add = add
ret.reset = reset
ret.fill_space = fill_space
ret.widgets = {}
ret._emit_updated = function()
ret:emit_signal("widget::updated")
end
return ret
end
--- Returns a new horizontal fixed layout. Each widget will get as much space as it
-- asks for and each widget will be drawn next to its neighboring widget.
-- Widgets can be added via :add().
function horizontal()
return get_layout("x")
end
--- Returns a new vertical fixed layout. Each widget will get as much space as it
-- asks for and each widget will be drawn next to its neighboring widget.
-- Widgets can be added via :add().
function vertical()
return get_layout("y")
end
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80

View File

@ -0,0 +1,107 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
local base = require("wibox.layout.base")
local fixed = require("wibox.layout.fixed")
local widget_base = require("wibox.widget.base")
local table = table
local pairs = pairs
local floor = math.floor
module("wibox.layout.flex")
local function round(x)
return floor(x + 0.5)
end
--- Draw a flex layout. Each widget gets an equal share of the available space
-- @param dir "x" for a horizontal layout and "y" for vertical.
-- @param widgets The widgets to draw.
-- @param cr The cairo context to use.
-- @param width The available width.
-- @param height The available height.
-- @return The total space needed by the layout.
function draw_flex(dir, widgets, wibox, cr, width, height)
local pos = 0
local num = #widgets
local space_per_item
if dir == "y" then
space_per_item = height / num
else
space_per_item = width / num
end
for k, v in pairs(widgets) do
local x, y, w, h
if dir == "y" then
x, y = 0, round(pos)
w, h = width, floor(space_per_item)
else
x, y = round(pos), 0
w, h = floor(space_per_item), height
end
base.draw_widget(wibox, cr, v, x, y, w, h)
pos = pos + space_per_item
if (dir == "y" and pos >= height) or
(dir ~= "y" and pos >= width) then
break
end
end
end
local function add(layout, widget)
widget_base.check_widget(widget)
table.insert(layout.widgets, widget)
widget:connect_signal("widget::updated", layout._emit_updated)
layout._emit_updated()
end
local function reset(layout)
for k, v in pairs(layout.widgets) do
v:disconnect_signal("widget::updated", layout._emit_updated)
end
layout.widgets = {}
layout:emit_signal("widget::updated")
end
local function get_layout(dir)
local function draw(layout, wibox, cr, width, height)
draw_flex(dir, layout.widgets, wibox, cr, width, height)
end
local function fit(layout, width, height)
return fixed.fit_fixed(dir, layout.widgets, width, height)
end
local ret = widget_base.make_widget()
ret.draw = draw
ret.fit = fit
ret.add = add
ret.reset = reset
ret.widgets = {}
ret._emit_updated = function()
ret:emit_signal("widget::updated")
end
return ret
end
--- Returns a new horizontal flex layout. A flex layout shares the available space
-- equally among all widgets. Widgets can be added via :add(widget).
function horizontal()
return get_layout("x")
end
--- Returns a new vertical flex layout. A flex layout shares the available space
-- equally among all widgets. Widgets can be added via :add(widget).
function vertical()
return get_layout("y")
end
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80

View File

@ -0,0 +1,16 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
require("wibox.layout.base")
require("wibox.layout.fixed")
require("wibox.layout.align")
require("wibox.layout.flex")
require("wibox.layout.rotate")
require("wibox.layout.margin")
module("wibox.layout")
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80

View File

@ -0,0 +1,127 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
local pairs = pairs
local type = type
local setmetatable = setmetatable
local base = require("wibox.layout.base")
local widget_base = require("wibox.widget.base")
module("wibox.layout.margin")
--- Draw a margin layout
function draw(layout, wibox, cr, width, height)
local x = layout.left
local y = layout.top
local w = layout.right
local h = layout.bottom
if not layout.widget or width <= x + w or height <= y + h then
return
end
base.draw_widget(wibox, cr, layout.widget, x, y, width - x - w, height - y - h)
end
--- Fit a margin layout into the given space
function fit(layout, width, height)
local extra_w = layout.left + layout.right
local extra_h = layout.top + layout.bottom
local w, h = 0, 0
if layout.widget then
w, h = layout.widget:fit(width - extra_w, height - extra_h)
end
return w + extra_w, h + extra_h
end
--- Set the widget that this layout adds a margin on.
function set_widget(layout, widget)
if layout.widget then
layout.widget:disconnect_signal("widget::updated", layout._emit_updated)
end
if widget then
widget_base.check_widget(widget)
widget:connect_signal("widget::updated", layout._emit_updated)
end
layout.widget = widget
layout._emit_updated()
end
--- Set all the margins to val.
function set_margins(layout, val)
layout.left = val
layout.right = val
layout.top = val
layout.bottom = val
layout:emit_signal("widget::updated")
end
--- Reset this layout. The widget will be unreferenced and the margins set to 0.
function reset(layout)
layout:set_widget(nil)
layout:set_margins(0)
end
--- Set the left margin that this layout adds to its widget.
-- @param layout The layout you are modifying.
-- @param margin The new margin to use.
-- @name set_left
-- @class function
--- Set the right margin that this layout adds to its widget.
-- @param layout The layout you are modifying.
-- @param margin The new margin to use.
-- @name set_right
-- @class function
--- Set the top margin that this layout adds to its widget.
-- @param layout The layout you are modifying.
-- @param margin The new margin to use.
-- @name set_top
-- @class function
--- Set the bottom margin that this layout adds to its widget.
-- @param layout The layout you are modifying.
-- @param margin The new margin to use.
-- @name set_bottom
-- @class function
-- Create setters for each direction
for k, v in pairs({ "left", "right", "top", "bottom" }) do
_M["set_" .. v] = function(layout, val)
layout[v] = val
layout:emit_signal("widget::updated")
end
end
--- Returns a new margin layout.
-- @param widget A widget to use (optional)
-- @param margin A margin to use on every side of the widget (optional)
local function new(widget, margin)
local ret = widget_base.make_widget()
for k, v in pairs(_M) do
if type(v) == "function" then
ret[k] = v
end
end
ret._emit_updated = function()
ret:emit_signal("widget::updated")
end
ret:set_margins(margin or 0)
if widget then
ret:set_widget(widget)
end
return ret
end
setmetatable(_M, { __call = function(_, ...) return new(...) end })
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80

View File

@ -0,0 +1,123 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
local error = error
local pairs = pairs
local pi = math.pi
local type = type
local setmetatable = setmetatable
local tostring = tostring
local base = require("wibox.layout.base")
local widget_base = require("wibox.widget.base")
module("wibox.layout.rotate")
local function transform(layout, width, height)
local dir = layout:get_direction()
if dir == "east" or dir == "west" then
return height, width
end
return width, height
end
--- Draw this layout
function draw(layout, wibox, cr, width, height)
if not layout.widget then return { width = 0, height = 0 } end
cr:save()
local dir = layout:get_direction()
if dir == "west" then
cr:rotate(pi / 2)
cr:translate(0, -width)
elseif dir == "south" then
cr:rotate(pi)
cr:translate(-width, -height)
elseif dir == "east" then
cr:rotate(3 * pi / 2)
cr:translate(-height, 0)
end
-- Since we rotated, we might have to swap width and height.
-- transform() does that for us.
layout.widget:draw(wibox, cr, transform(layout, width, height))
-- Undo the rotation and translation from above.
cr:restore()
end
--- Fit this layout into the given area
function fit(layout, width, height)
if not layout.widget then
return 0, 0
end
return transform(layout, layout.widget:fit(transform(layout, width, height)))
end
--- Set the widget that this layout rotates.
function set_widget(layout, widget)
if layout.widget then
layout.widget:disconnect_signal("widget::updated", layout._emit_updated)
end
if widget then
widget_base.check_widget(widget)
widget:connect_signal("widget::updated", layout._emit_updated)
end
layout.widget = widget
layout._emit_updated()
end
--- Reset this layout. The widget will be removed and the rotation reset.
function reset(layout)
layout.direction = nil
layout:set_widget(nil)
end
--- Set the direction of this rotating layout. Valid values are "north", "east",
-- "south" and "west". On an invalid value, this function will throw an error.
function set_direction(layout, dir)
local allowed = {
north = true,
east = true,
south = true,
west = true
}
if not allowed[dir] then
error("Invalid direction for rotate layout: " .. tostring(dir))
end
layout.direction = dir
layout._emit_updated()
end
--- Get the direction of this rotating layout
function get_direction(layout)
return layout.direction or "north"
end
--- Returns a new rotate layout. A rotate layout rotates a given widget. Use
-- :set_widget() to set the widget and :set_direction() for the direction.
-- The default direction is "north" which doesn't change anything.
local function new()
local ret = widget_base.make_widget()
for k, v in pairs(_M) do
if type(v) == "function" then
ret[k] = v
end
end
ret._emit_updated = function()
ret:emit_signal("widget::updated")
end
return ret
end
setmetatable(_M, { __call = function(_, ...) return new(...) end })
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80

View File

@ -0,0 +1,102 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
local oocairo = require("oocairo")
local base = require("wibox.widget.base")
local color = require("gears.color")
local widget_base = require("wibox.widget.base")
local setmetatable = setmetatable
local pairs = pairs
local type = type
module("wibox.widget.background")
--- Draw this widget
function draw(box, wibox, cr, width, height)
if not box.widget then
return
end
cr:save()
if box.background then
cr:set_source(box.background)
cr:paint()
end
if box.bgimage then
local pattern = oocairo.pattern_create_for_surface(box.bgimage)
cr:set_source(pattern)
cr:paint()
end
cr:restore()
box.widget:draw(wibox, cr, width, height)
end
--- Fit this widget into the given area
function fit(box, width, height)
if not box.widget then
return 0, 0
end
return box.widget:fit(width, height)
end
--- Set the widget that is drawn on top of the background
function set_widget(box, widget)
if box.widget then
box.widget:disconnect_signal("widget::updated", box._emit_updated)
end
if widget then
widget_base.check_widget(widget)
widget:connect_signal("widget::updated", box._emit_updated)
end
box.widget = widget
box._emit_updated()
end
--- Set the background to use
function set_bg(box, bg)
if bg then
box.background = color(bg)
else
box.background = nil
end
box._emit_updated()
end
--- Set the background image to use
function set_bgimage(box, image)
local image = image
if type(image) == "string" then
image = oocairo.image_surface_create_from_png(image)
end
box.bgimage = image
box._emit_updated()
end
local function new()
local ret = base.make_widget()
for k, v in pairs(_M) do
if type(v) == "function" then
ret[k] = v
end
end
ret._emit_updated = function()
ret:emit_signal("widget::updated")
end
return ret
end
setmetatable(_M, { __call = function (_, ...) return new(...) end })
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80

View File

@ -0,0 +1,122 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
local object = require("gears.object")
local pairs = pairs
local error = error
local type = type
local table = table
module("wibox.widget.base")
--- Set/get a widget's buttons
function buttons(widget, buttons)
if buttons then
widget.widget_buttons = buttons
end
return widget.widget_buttons
end
--- Handle a button event on a widget. This is used internally.
function handle_button(event, widget, x, y, button, modifiers)
local function is_any(mod)
if #mod ~= 1 then
return false
end
if mod[1] ~= "Any" then
return false
end
return true
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
v:emit_signal(event)
end
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
-- widget. It will be equivalent to this widget.
function make_widget(proxy)
local ret = object()
-- This signal is used by layouts to find out when they have to update.
ret:add_signal("widget::updated")
-- Mouse input, oh noes!
ret:add_signal("button::press")
ret:add_signal("button::release")
-- No buttons yet
ret.widget_buttons = {}
ret.buttons = buttons
-- Make buttons work
ret:connect_signal("button::press", function(...)
return handle_button("press", ...)
end)
ret:connect_signal("button::release", function(...)
return handle_button("release", ...)
end)
if proxy then
ret.size = function(_, ...) return proxy:size(...) end
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")
end)
end
return ret
end
--- Do some sanity checking on widget. This function raises a lua error if
-- widget is not a valid widget.
function check_widget(widget)
if type(widget) ~= "table" then
error("widget is not a table?!")
end
for k, func in pairs({ "draw", "fit", "add_signal", "connect_signal", "disconnect_signal" }) do
if type(widget[func]) ~= "function" then
error("widget's " .. func .. "() is not a function?!")
end
end
local width, height = widget:fit(0, 0)
if type(width) ~= "number" or type(height) ~= "number" then
error("widget's fit() didn't return two numbers")
end
end
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80

View File

@ -0,0 +1,114 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
local oocairo = require("oocairo")
local base = require("wibox.widget.base")
local setmetatable = setmetatable
local pairs = pairs
local type = type
local pcall = pcall
local print = print
module("wibox.widget.imagebox")
--- Draw an imagebox with the given cairo context in the given geometry.
function draw(box, wibox, cr, width, height)
if not box.image then return end
cr:save()
if not box.resize_forbidden then
-- Let's scale the image so that it fits into (width, height)
local w = box.image:get_width()
local h = box.image:get_height()
local aspect = width / w
local aspect_h = height / h
if aspect > aspect_h then aspect = aspect_h end
cr:scale(aspect, aspect)
end
cr:set_source(box.image)
cr:paint()
cr:restore()
end
--- Fit the imagebox into the given geometry
function fit(box, width, height)
if not box.image then
return 0, 0
end
local w = box.image:get_width()
local h = box.image:get_height()
if w > width then
h = h * width / w
w = width
end
if h > height then
w = w * height / h
h = height
end
if not box.resize_forbidden then
local aspect = width / w
local aspect_h = height / h
-- Use the smaller one of the two aspect ratios.
if aspect > aspect_h then aspect = aspect_h end
w, h = w * aspect, h * aspect
end
return w, h
end
--- Set an imagebox' image
-- @param image Either a string or a cairo image surface. A string is
-- interpreted as the path to a png image file.
function set_image(box, image)
local image = image
if type(image) == "string" then
local success, result = pcall(oocairo.image_surface_create_from_png, image)
if not success then
print("Error while reading '" .. image .. "': " .. result)
return false
end
image = result
end
box.image = image
box:emit_signal("widget::updated")
return true
end
--- Should the image be resized to fit into the available space?
-- @param allowed If false, the image will be clipped, else it will be resized
-- to fit into the available space.
function set_resize(box, allowed)
box.resize_forbidden = not allowed
box:emit_signal("widget::updated")
end
-- Returns a new imagebox
local function new()
local ret = base.make_widget()
for k, v in pairs(_M) do
if type(v) == "function" then
ret[k] = v
end
end
return ret
end
setmetatable(_M, { __call = function (_, ...) return new(...) end })
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80

View File

@ -0,0 +1,15 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
require("wibox.widget.base")
require("wibox.widget.textbox")
require("wibox.widget.imagebox")
require("wibox.widget.background")
require("wibox.widget.systray")
module("wibox.widget")
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80

View File

@ -0,0 +1,68 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
local wbase = require("wibox.widget.base")
local lbase = require("wibox.layout.base")
local capi = { awesome = awesome }
local setmetatable = setmetatable
local error = error
module("wibox.widget.systray")
local created_systray = false
local horizontal = true
local base_size = 16
function draw(box, wibox, cr, width, height)
local x, y, width, height = lbase.rect_to_device_geometry(cr, 0, 0, width, height)
local num_entries = capi.awesome.systray()
local width, height = width, height
local in_dir, ortho, base_size
if horizontal then
in_dir, ortho = width, height
else
ortho, in_dir = width, height
end
if ortho * num_entries <= in_dir then
base = ortho
else
base = in_dir / num_entries
end
capi.awesome.systray(wibox.drawin, x, y, base, horizontal)
end
function fit(box, width, height)
local num_entries = capi.awesome.systray()
if horizontal then
return base_size * num_entries, base_size
end
return base_size, base_size * num_entries
end
local function new(horiz)
local ret = wbase.make_widget()
horizontal = horiz
if created_systray then
error("More than one systray created!")
end
created_systray = true
ret.fit = fit
ret.draw = draw
ret.set_base_size = function(_, size) base_size = size end
capi.awesome.connect_signal("systray::update", function()
ret:emit_signal("widget::updated")
end)
return ret
end
setmetatable(_M, { __call = function (_, ...) return new(...) end })
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80

View File

@ -0,0 +1,167 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
local oopango = require("oopango")
local oocairo = require("oocairo")
local base = require("wibox.widget.base")
local beautiful = require("beautiful")
local type = type
local unpack = unpack
local setmetatable = setmetatable
local pairs = pairs
module("wibox.widget.textbox")
-- Setup a pango layout for the given textbox and cairo context
local function setup_layout(box, cr, width, height)
local layout = oopango.cairo.layout_create(cr)
layout:set_ellipsize(box.ellipsize)
layout:set_wrap(box.wrap)
layout:set_width(oopango.units_from_number(width))
layout:set_height(oopango.units_from_number(height))
if box.markup then
layout:set_markup(box.text)
else
layout:set_text(box.text)
end
if box.font then
layout:set_font_description(box.font)
else
layout:set_font_description(beautiful.get_font())
end
local ink, logical = layout:get_pixel_extents()
local offset = 0
if box.valign == "center" then
offset = (height - logical.height) / 2
elseif box.valign == "bottom" then
offset = height - logical.height
end
if offset > 0 then
cr:move_to(0, offset)
end
return layout
end
-- Get the size that the given textbox covers.
-- If layout is given, it's :get_width()/get_height() is honoured.
local function get_size(box, layout, width, height)
local ret = {
width = 0,
height = 0
}
if box.text then
local layout = layout
if not layout then
-- Create a temporary surface that we need for computing the extents :(
local surface = oocairo.image_surface_create("argb32", 1, 1)
local cr = oocairo.context_create(surface)
layout = setup_layout(box, cr, width, height)
end
local ink, logical = layout:get_pixel_extents()
ret.width = logical.width
ret.height = logical.height
end
return ret
end
--- Draw the given textbox on the given cairo context in the given geometry
function draw(box, wibox, cr, width, height)
if not box.text then return end
local layout = setup_layout(box, cr, width, height)
oopango.cairo.update_layout(cr, layout)
oopango.cairo.show_layout(cr, layout)
end
--- Fit the given textbox
function fit(box, width, height)
local res = get_size(box, nil, width, height)
return res.width, res.height
end
--- Test if a textbox' text is valid. If it isn't, a lua error will be thrown.
function check(box)
-- This creates a pango layout for the string and so forces pango to verify
-- whether we have some sane input for it
get_size(box, nil, -1, -1)
end
--- Set a textbox' text.
-- @param text The text to set. This can contain pango markup (e.g. <b>bold</b>)
function set_markup(box, text)
box.text = text
box.markup = true
box:emit_signal("widget::updated")
end
--- Set a textbox' text.
-- @param text The text to display. Pango markup is ignored and shown as-is.
function set_text(box, text)
box.text = text
box.markup = false
box:emit_signal("widget::updated")
end
--- Set a textbox' ellipsize mode.
-- @param mode Where should long lines be shortened? "start", "middle" or "end"
function set_ellipsize(box, mode)
local allowed = { none = true, start = true, middle = true, ["end"] = true }
if allowed[mode] then
box.ellipsize = mode
box:emit_signal("widget::updated")
end
end
--- Set a textbox' wrap mode.
-- @param mode Where to wrap? After "word", "char" or "word_char"
function set_wrap(box, mode)
local allowed = { word = true, char = true, word_char = true }
if allowed[mode] then
box.wrap = mode
box:emit_signal("widget::updated")
end
end
--- Set a textbox' vertical alignment
-- @param mode Where should the textbox be drawn? "top", "center" or "bottom"
function set_valign(box, mode)
local allowed = { top = true, center = true, bottom = true }
if allowed[mode] then
box.valign = mode
box:emit_signal("widget::updated")
end
end
-- Returns a new textbox
local function new()
local ret = base.make_widget()
for k, v in pairs(_M) do
if type(v) == "function" then
ret[k] = v
end
end
ret.ellipsize = "end"
ret.wrap = "word_char"
ret.valign = "center"
return ret
end
setmetatable(_M, { __call = function (_, ...) return new(...) end })
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80