imagebox: Support a CSS file.

Before, the CSS had to be inline. Now that the border container uses
this code, it makes sense to make it more flexible.
This commit is contained in:
Emmanuel Lepage Vallee 2022-01-03 22:29:33 -08:00
parent 5c9ffb8ef3
commit ab121e9ac0
1 changed files with 84 additions and 11 deletions

View File

@ -31,12 +31,17 @@ local base = require("wibox.widget.base")
local surface = require("gears.surface") local surface = require("gears.surface")
local gtable = require("gears.table") local gtable = require("gears.table")
local gdebug = require("gears.debug") local gdebug = require("gears.debug")
local gfs = require("gears.filesystem")
local setmetatable = setmetatable local setmetatable = setmetatable
local type = type local type = type
local math = math local math = math
local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1) local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
-- Placeholder table to represent an emty stylesheet.
-- It has to be defined here to avoid being GCed
local empty_stylesheet = {}
local policies_to_extents = { local policies_to_extents = {
["pad"] = cairo.Extend.PAD, ["pad"] = cairo.Extend.PAD,
["repeat"] = cairo.Extend.REPEAT, ["repeat"] = cairo.Extend.REPEAT,
@ -55,18 +60,25 @@ end
local imagebox = { mt = {} } local imagebox = { mt = {} }
local rsvg_handle_cache = setmetatable({}, { __mode = 'k' }) local rsvg_handle_cache = setmetatable({}, { __mode = 'k' })
local stylesheet_cache = {}
--Load rsvg handle form image file --Load rsvg handle form image file
-- @tparam string file Path to svg file. -- @tparam string file Path to svg file.
-- @return Rsvg handle -- @return Rsvg handle
-- @treturn table A table where cached data can be stored. -- @treturn table A table where cached data can be stored.
function imagebox._load_rsvg_handle(file) function imagebox._load_rsvg_handle(file, style)
-- Make sure this is called in the right order.
assert((not style) or (style and stylesheet_cache[style]))
local style_ref = style and stylesheet_cache[style] or empty_stylesheet
if not Rsvg then return end if not Rsvg then return end
local cache = (rsvg_handle_cache[file] or {})["handle"] local bucket = rsvg_handle_cache[file] or {}
local cache = (bucket[style_ref] or {})["handle"]
if cache then if cache then
return cache, rsvg_handle_cache[file] return cache, bucket[style_ref]
end end
local handle, err local handle, err
@ -78,9 +90,10 @@ function imagebox._load_rsvg_handle(file)
end end
if not err then if not err then
rsvg_handle_cache[file] = rsvg_handle_cache[file] or {} rsvg_handle_cache[file] = rsvg_handle_cache[file] or setmetatable({}, {__mode = "k"})
rsvg_handle_cache[file]["handle"] = handle rsvg_handle_cache[file][style_ref] = rsvg_handle_cache[file][style_ref] or {}
return handle, rsvg_handle_cache[file] rsvg_handle_cache[file][style_ref]["handle"] = handle
return handle, rsvg_handle_cache[file][style_ref]
end end
end end
@ -117,7 +130,7 @@ end
---@treturn boolean True if image was successfully applied ---@treturn boolean True if image was successfully applied
local function load_and_apply(ib, file, image_loader, image_setter) local function load_and_apply(ib, file, image_loader, image_setter)
local image_applied local image_applied
local object, cache = image_loader(file) local object, cache = image_loader(file, ib._private.stylesheet_og)
if object then if object then
image_applied = image_setter(ib, object, cache) image_applied = image_setter(ib, object, cache)
@ -125,6 +138,38 @@ local function load_and_apply(ib, file, image_loader, image_setter)
return image_applied return image_applied
end end
--- Support both CSS data and filepath for the stylsheet.
function imagebox._get_stylesheet(self, content_or_path)
if not content_or_path then return nil end
-- Always set the entry because the image cache uses it.
stylesheet_cache[content_or_path] = stylesheet_cache[content_or_path]
or setmetatable({}, {__mode = "v"})
if gfs.file_readable(content_or_path) then
local ret
local _, obj = next(stylesheet_cache[content_or_path])
if obj then
ret = obj._private.stylesheet
table.insert(stylesheet_cache[content_or_path], self)
else
local f = io.open(content_or_path, 'r')
if not f then return nil end
ret = f:read("*all")
f:close()
table.insert(stylesheet_cache[content_or_path], self)
end
return ret
else
return content_or_path
end
end
---Update the cached size depending on the stylesheet and dpi. ---Update the cached size depending on the stylesheet and dpi.
-- --
-- It's necessary because a single RSVG handle can be used by -- It's necessary because a single RSVG handle can be used by
@ -443,8 +488,7 @@ end
-- If the image is an SVG (vector graphics), this property allows to set -- If the image is an SVG (vector graphics), this property allows to set
-- a CSS stylesheet. It can be used to set colors and much more. -- a CSS stylesheet. It can be used to set colors and much more.
-- --
-- Note that this property is a string, not a path. If the stylesheet is -- The value can be either CSS data or a file path.
-- stored on disk, read the content first.
-- --
--@DOC_wibox_widget_imagebox_stylesheet_EXAMPLE@ --@DOC_wibox_widget_imagebox_stylesheet_EXAMPLE@
-- --
@ -483,18 +527,47 @@ end
-- @propemits true false -- @propemits true false
-- @see dpi -- @see dpi
for _, prop in ipairs {"stylesheet", "dpi", "auto_dpi"} do for _, prop in ipairs {"dpi", "auto_dpi"} do
imagebox["set_" .. prop] = function(self, value) imagebox["set_" .. prop] = function(self, value)
local old = self._private[prop]
-- It will be set in :fit and :draw. The handle is shared -- It will be set in :fit and :draw. The handle is shared
-- by multiple imagebox, so it cannot be set just once. -- by multiple imagebox, so it cannot be set just once.
self._private[prop] = value self._private[prop] = value
self:emit_signal("widget::redraw_needed") self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed") self:emit_signal("widget::layout_changed")
self:emit_signal("property::" .. prop) self:emit_signal("property::" .. prop, value, old)
end end
end end
function imagebox:set_stylesheet(value)
if value == self._private.stylesheet_og then return end
local old = self._private.stylesheet_og
if old and stylesheet_cache[old] then
for k, v in ipairs(stylesheet_cache[old]) do
if self == v then
table.remove(stylesheet_cache[old], k)
break
end
end
end
local content = imagebox._get_stylesheet(self, value)
self._private.stylesheet = content
self._private.stylesheet_og = value
-- Refresh the pixmap.
self.image = self._private.original_image
self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed")
self:emit_signal("property::stylesheet", value)
end
function imagebox:set_resize(allowed) function imagebox:set_resize(allowed)
self._private.resize = allowed self._private.resize = allowed