diff --git a/lib/wibox/container/background.lua b/lib/wibox/container/background.lua
index 8262d6a7e..fc9633065 100644
--- a/lib/wibox/container/background.lua
+++ b/lib/wibox/container/background.lua
@@ -23,6 +23,141 @@ local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility
local background = { mt = {} }
+-- If a background is resized with the mouse, it might create a large
+-- number of scaled texture. After the cache grows beyond this number, it
+-- is purged.
+local MAX_CACHE_SIZE = 10
+local global_scaled_pattern_cache = setmetatable({}, {__mode = "k"})
+
+local function clone_stops(input, output)
+ local err, count = input:get_color_stop_count()
+
+ if err ~= "SUCCESS" then return end
+
+ for idx = 0, count-1 do
+ local _, off, r, g, b, a = input:get_color_stop_rgba(idx)
+ output:add_color_stop_rgba(off, r, g, b, a)
+ end
+end
+
+local function stretch_lineal_gradient(input, width, height)
+ -- First, get the original values.
+ local err, x0, y0, x1, y1 = input:get_linear_points()
+
+ if err ~= "SUCCESS" then
+ return input
+ end
+
+
+ local new_x0, new_y0 = x1 == 0 and 0 or ((x0/x1)*width), y1 == 0 and 0 or ((y0/y1)*height)
+
+ local output = cairo.Pattern.create_linear(new_x0, new_y0, width, height)
+
+ clone_stops(input, output)
+
+ return output
+end
+
+local function stretch_radial_gradient(input, width, height)
+ local err, cx0, cy0, radius0, cx1, cy1, radius1 = input:get_radial_circles()
+
+ if err ~= "SUCCESS" then
+ return input
+ end
+
+ -- Create a box for the original gradient, starting at 0x0.
+ local x1 = math.max(cx0 + radius0, cx1 + radius1)
+ local y1 = math.max(cy0 + radius0, cy1 + radius1)
+
+ -- Now scale this box to `width`x`height`
+ local x_factor, y_factor = width/x1, height/y1
+ local rad_factor = math.sqrt(width^2 + height^2) / math.sqrt(x1^1+y1^2)
+
+ local output = cairo.Pattern.create_radial(
+ cx0*x_factor, cy0*y_factor, radius0*rad_factor, cx1*x_factor, cx1*y_factor, radius1*rad_factor
+ )
+
+ clone_stops(input, output)
+
+ return output
+end
+
+local function get_pattern_size(pat)
+ local t = pat.type
+
+ if t == "LINEAR" then
+ local _, _, _, x1, y1 = pat:get_linear_points()
+
+ return x1, y1
+ elseif t == "RADIAL" then
+ local _, _, _, cx1, cy1, _ = pat:get_radial_circles()
+
+ return cx1, cy1
+ end
+end
+
+local function stretch_common(self, width, height)
+ if (not self._private.background) and (not self._private.bgimage) then return end
+
+ if not (self._private.stretch_horizontally or self._private.stretch_vertically) then
+ return self._private.background, self._private.bgimage
+ end
+
+ local old = self._private.background
+ local size_w, size_h = get_pattern_size(old)
+
+ -- Note that technically, we could handle cairo.SurfacePattern and
+ -- cairo.RasterSourcePattern. However, this might create some surprising
+ -- results. For example switching to a different theme which uses tiled
+ -- pattern would change the "meaning" of this property.
+ if not size_w then return end
+
+ -- Don't try to resize zero-sized patterns. They are used for
+ -- generic liear gradient means "there is no gradient in this axis"
+ if self._private.stretch_vertically and size_h ~= 0 then
+ size_h = height
+ end
+
+ if self._private.stretch_horizontally and size_w ~= 0 then
+ size_w = width
+ end
+
+ local hash = size_w.."x"..size_h
+
+ if self._private.scale_cache[hash] then
+ return self._private.scale_cache[hash]
+ end
+
+ if global_scaled_pattern_cache[old] and global_scaled_pattern_cache[old][hash] then
+ -- Don't bother clearing the cache, if the pattern is already cached
+ -- elsewhere, it will remain in memory anyway.
+ self._private.scale_cache[hash] = global_scaled_pattern_cache[old][hash]
+ self._private.scale_cache_size = self._private.scale_cache_size + 1
+ end
+
+ local t, new = old.type
+
+ if t == "LINEAR" then
+ new = stretch_lineal_gradient(old, size_w, size_h)
+ elseif t == "RADIAL" then
+ new = stretch_radial_gradient(old, size_w, size_h)
+ end
+
+ global_scaled_pattern_cache[old] = global_scaled_pattern_cache[old] or setmetatable({}, {__mode = "v"})
+ global_scaled_pattern_cache[old][hash] = new
+
+ -- Prevent the memory leak.
+ if self._private.scale_cache_size > MAX_CACHE_SIZE then
+ self._private.scale_cache_size = 0
+ self._private.scale_cache = {}
+ end
+
+ self._private.scale_cache[hash] = new
+ self._private.scale_cache_size = self._private.scale_cache_size + 1
+
+ return new, self._private.bgimage
+end
+
-- The Cairo SVG backend doesn't support surface as patterns correctly.
-- The result is both glitchy and blocky. It is also impossible to introspect.
-- Calling this function replace the normal code path is a "less correct", but
@@ -39,9 +174,11 @@ function background._use_fallback_algorithm()
shape(cr, width, height)
- if self._private.background then
+ local bg = stretch_common(self, width, height)
+
+ if bg then
cr:save() --Save to avoid messing with the original source
- cr:set_source(self._private.background)
+ cr:set_source(bg)
cr:fill_preserve()
cr:restore()
end
@@ -115,15 +252,18 @@ function background:before_draw_children(context, cr, width, height)
cr:push_group_with_content(cairo.Content.COLOR_ALPHA)
end
+ local bg, bgimage = stretch_common(self, width, height)
+
-- Draw the background
- if self._private.background then
+ if bg then
cr:save()
- cr:set_source(self._private.background)
+ cr:set_source(bg)
cr:rectangle(0, 0, width, height)
cr:fill()
cr:restore()
end
- if self._private.bgimage then
+
+ if bgimage then
cr:save()
if type(self._private.bgimage) == "function" then
self._private.bgimage(context, cr, width, height,unpack(self._private.bgimage_args))
@@ -253,6 +393,42 @@ function background:set_children(children)
self:set_widget(children[1])
end
+--- Stretch the background gradient horizontally.
+--
+-- This only works for linear or radial gradients. It does nothing
+-- for solid colors, `bgimage` or raster patterns.
+--
+--@DOC_wibox_container_background_stretch_horizontally_EXAMPLE@
+--
+-- @property stretch_horizontally
+-- @tparam[opt=false] boolean stretch_horizontally
+-- @propemits true false
+-- @see stretch_vertically
+-- @see bg
+-- @see gears.color
+
+--- Stretch the background gradient vertically.
+--
+-- This only works for linear or radial gradients. It does nothing
+-- for solid colors, `bgimage` or raster patterns.
+--
+--@DOC_wibox_container_background_stretch_vertically_EXAMPLE@
+--
+-- @property stretch_vertically
+-- @tparam[opt=false] boolean stretch_vertically
+-- @propemits true false
+-- @see stretch_horizontally
+-- @see bg
+-- @see gears.color
+
+for _, orientation in ipairs {"horizontally", "vertically"} do
+ background["set_stretch_"..orientation] = function(self, value)
+ self._private["stretch_"..orientation] = value
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("property::stretch_"..orientation, value)
+ end
+end
+
--- The background color/pattern/gradient to use.
--
--@DOC_wibox_container_background_bg_EXAMPLE@
@@ -352,7 +528,8 @@ end
--
-- If the shape is set, the border will also be shaped.
--
--- See `wibox.container.background.shape` for an usage example.
+--@DOC_wibox_container_background_border_width_EXAMPLE@
+--
-- @property border_width
-- @tparam[opt=0] number border_width
-- @propertyunit pixel
@@ -400,6 +577,8 @@ end
--- Set the color for the border.
--
+--@DOC_wibox_container_background_border_color_EXAMPLE@
+--
-- See `wibox.container.background.shape` for an usage example.
-- @property border_color
-- @tparam color border_color
@@ -450,10 +629,13 @@ end
--- How the border width affects the contained widget.
--
+--@DOC_wibox_container_background_border_strategy_EXAMPLE@
+--
-- @property border_strategy
-- @tparam[opt="none"] string border_strategy
-- @propertyvalue "none" Just apply the border, do not affect the content size (default).
-- @propertyvalue "inner" Squeeze the size of the content by the border width.
+-- @propemits true false
function background:set_border_strategy(value)
self._private.border_strategy = value
@@ -463,12 +645,24 @@ end
--- The background image to use.
--
+-- This property is deprecated. The `wibox.container.border` provides a much
+-- more fine-grained support for background images. It is now out of the
+-- `wibox.container.background` scope. `wibox.layout.stack` can also be used
+-- to overlay a widget on top of a `wibox.widget.imagebox`. This solution
+-- exposes all availible imagebox properties. Finally, if you wish to use the
+-- `function` callback support, implement the `before_draw_children` method
+-- on any widget. This gives you the same level of control without all the
+-- `bgimage` corner cases.
+--
-- If `image` is a function, it will be called with `(context, cr, width, height)`
-- as arguments. Any other arguments passed to this method will be appended.
--
--- @property bgimage
--- @tparam[opt=nil] image|nil bgimage
+-- @deprecatedproperty bgimage
+-- @tparam string|surface|function bgimage A background image or a function.
-- @see gears.surface
+-- @see wibox.container.border
+-- @see wibox.widget.imagebox
+-- @see wibox.layout.stack
function background:set_bgimage(image, ...)
self._private.bgimage = type(image) == "function" and image or surface.load(image)
@@ -498,6 +692,8 @@ local function new(widget, bg, shape)
gtable.crush(ret, background, true)
ret._private.shape = shape
+ ret._private.scale_cache = {}
+ ret._private.scale_cache_size = 0
ret:set_widget(widget)
ret:set_bg(bg)
diff --git a/lib/wibox/container/border.lua b/lib/wibox/container/border.lua
new file mode 100644
index 000000000..75d2fb475
--- /dev/null
+++ b/lib/wibox/container/border.lua
@@ -0,0 +1,1059 @@
+---------------------------------------------------------------------------
+-- Place widgets or images on the sides, corner and back of another widget.
+--
+-- This widget main use case is to surround another widget with an uniform
+-- border. It can also be used to create soft shadows. It support CSS style
+-- image slicing or widgets within the border. The `awful.layout.align` layout
+-- an also be used to achieve border, but make it very hard to make them
+-- symmetric on both axis.
+--
+-- Note that because of legacy reasons, `wibox.container.background` also has
+-- good support for borders. If you only need simple shaped strokes, the
+-- `background` container is a much better choice. This module is better
+-- suited for background images and border widgets.
+--
+-- Advanced usage
+-- ==============
+--
+-- In this first example, 2 `wibox.container.borders` are combined in a
+-- `wibox.layout.stack`. The bottom one is the "real" border and the top
+-- layer is used to place some widgets. This is useful to built titlebars.
+--
+-- @DOC_wibox_container_border_dual1_EXAMPLE@
+--
+-- This example demonstrates how to use this module to create a client
+-- border with a top titlebar and borders. It does so by embedding a
+-- `wibox.container.border` into another `wibox.container.border`. The outer
+-- container acts as the border around the central area while the inner one
+-- spans the top area and contains the titlebar itself. The outer border
+-- widgets area can be used to implement border resize.
+--
+-- @DOC_wibox_container_border_titlebar1_EXAMPLE@
+--
+-- This container is like every other widgets. It is possible to override the
+-- Cairo drawing functions. This is very useful here to create custom borders
+-- using cairo while still re-using the internal geometry handling code.
+--
+-- @DOC_wibox_container_border_custom_draw1_EXAMPLE@
+--
+--@DOC_wibox_container_defaults_border_EXAMPLE@
+--
+-- @author Emmanuel Lepage-Vallee
+-- @copyright 2021 Emmanuel Lepage-Vallee
+-- @containermod wibox.container.border
+-- @supermodule wibox.widget.base
+local gtable = require("gears.table")
+local imagebox = require("wibox.widget.imagebox")
+local base = require("wibox.widget.base")
+local gsurface = require("gears.surface")
+local cairo = require("lgi").cairo
+
+local components = {
+ "top_left", "top", "top_right", "right", "bottom_right", "bottom",
+ "bottom_left", "left", "fill"
+}
+
+local slice_modes = {
+ top_left = "corners",
+ top = "sides",
+ top_right = "corners",
+ right = "sides",
+ bottom_right = "corners",
+ bottom = "sides",
+ bottom_left = "corners",
+ left = "sides",
+ fill = "filling"
+}
+
+local fit_types = { "corners", "sides", "filling" }
+
+local module = {}
+
+local function imagebox_fit(self, _, w, h)
+ return math.min(w, self.source_width or 0), math.min(h, self.source_height or 0)
+end
+
+local function fit_common(widget, ctx, max_w, max_h)
+ if not widget then return 0, 0 end
+
+ local w, h = widget:fit(ctx, max_w or math.huge, max_h or math.huge)
+
+ return w, h
+end
+
+local function uses_slice(self)
+ return not (self._private.border_widgets or self._private.border_image_widgets)
+end
+
+local function get_widget(self, ctx, component)
+ local wids, imgs = self._private.border_widgets or {}, self._private.border_image_widgets or {}
+ local slices = self._private.slice_cache and self._private.slice_cache[ctx.dpi] or {}
+
+ return wids [component]
+ or imgs [component]
+ or slices[component]
+end
+
+local function get_all_widgets(self, partial, ctx)
+ for _, position in ipairs(components) do
+ partial[position] = partial[position] or get_widget(self, ctx, position)
+ end
+
+ return partial
+end
+
+--- Common code to load the `border_image`.
+local function setup_origin_common(self, ctx)
+ if self._private.original_md then return self._private.original_md end
+
+ local origin_w, origin_h, origin
+
+ local img = self._private.border_image
+
+ -- Try SVG first.
+ local style = imagebox._get_stylesheet(self, self._private.border_image_stylesheet)
+ local handle = type(img) == "string" and imagebox._load_rsvg_handle(img, style) or nil
+
+ if handle then
+ if style then
+ handle:set_stylesheet(style)
+ end
+ handle:set_dpi(self.border_image_dpi or ctx.dpi)
+ local dim = handle:get_dimensions()
+
+ if dim.width > 0 and dim.height > 0 then
+ origin_w, origin_h = dim.width, dim.height
+
+ origin = cairo.ImageSurface(cairo.Format.ARGB32, origin_w, origin_h)
+ local cr = cairo.Context(origin)
+ handle:render_cairo(cr)
+ end
+ end
+
+ if not origin then
+ origin = gsurface(self._private.border_image)
+
+ local err1, _origin_w = pcall(function() return origin:get_width() end)
+ local err2, _origin_h = pcall(function() return origin:get_height() end)
+
+ if err1 and err2 then
+ origin_w, origin_h = _origin_w, _origin_h
+ end
+ end
+
+ return {
+ width = origin_w,
+ height = origin_h,
+ surface = origin,
+ handle = handle
+ }
+end
+
+local function compute_side_borders(self, ctx, max_w, max_h)
+ local override = self._private.borders or {}
+ local mer = self._private.border_merging or {}
+
+ self._private.original_md = setup_origin_common(self, ctx)
+
+ local m = {
+ left = override.left,
+ right = override.right,
+ top = override.top,
+ bottom = override.bottom,
+ }
+
+ -- Limit the border to what the surface can provide.
+ if uses_slice(self) and (m.left or 0) + (m.right or 0) > self._private.original_md.width then
+ local max = math.floor((self._private.original_md.width-1)/2)
+ m.left = math.min(m.left, max)
+ m.right = math.min(m.right, max)
+ end
+
+ -- Limit the border to what the surface can provide.
+ if uses_slice(self) and (m.top or 0) + (m.bottom or 0) > self._private.original_md.height then
+ local max = math.floor((self._private.original_md.height-1)/2)
+ m.top = math.min(m.top, max)
+ m.bottom = math.min(m.bottom, max)
+ end
+
+ local wdgs = {
+ left = get_widget(self, ctx, "left" ) or false,
+ right = get_widget(self, ctx, "right" ) or false,
+ top = get_widget(self, ctx, "top" ) or false,
+ bottom = get_widget(self, ctx, "bottom") or false,
+ }
+
+ -- Call `:fit()` on the sides.
+ local l_w, l_h = fit_common(wdgs.left , ctx, max_w, max_h)
+ local r_w, r_h = fit_common(wdgs.right , ctx, max_w, max_h)
+ local t_w, t_h = fit_common(wdgs.top , ctx, max_w, max_h)
+ local b_w, b_h = fit_common(wdgs.bottom, ctx, max_w, max_h)
+
+ -- Either use the provided borders of the `:fit()` results.
+ m.left = m.left or l_w
+ m.right = m.right or r_w
+ m.top = m.top or t_h
+ m.bottom = m.bottom or b_h
+
+ -- Allow the corner widgets to affect the border size.
+ if self._private.expand_corners then
+ wdgs.top_left = get_widget(self, ctx, "top_left" ) or false
+ wdgs.top_right = get_widget(self, ctx, "top_right" ) or false
+ wdgs.bottom_left = get_widget(self, ctx, "bottom_left" ) or false
+ wdgs.bottom_right = get_widget(self, ctx, "bottom_right") or false
+
+ local tl_w, tl_h = fit_common(wdgs.top_left , ctx, max_w, max_h)
+ local tr_w, tr_h = fit_common(wdgs.top_right , ctx, max_w, max_h)
+ local bl_w, bl_h = fit_common(wdgs.bottom_left , ctx, max_w, max_h)
+ local br_w, br_h = fit_common(wdgs.bottom_right, ctx, max_w, max_h)
+
+ m.left = math.max(m.left , tl_w, bl_w)
+ m.right = math.max(m.right , tr_w, br_w)
+ m.top = math.max(m.top , tl_h, tr_h)
+ m.bottom = math.max(m.bottom, bl_h, br_h)
+ end
+
+ -- The sides should have matching size unless merging is enabled.
+ local minimums = {
+ left = mer.right and l_h or math.max(l_h, r_h),
+ right = mer.left and r_h or math.max(l_h, r_h),
+ top = mer.bottom and t_w or math.max(t_w, b_w),
+ bottom = mer.top and b_w or math.max(t_w, b_w),
+ }
+
+ return m, wdgs, minimums
+end
+
+local function compute_borders(self, ctx, width, height, fit_width, fit_height)
+ local m, widgets, mins = compute_side_borders(self, ctx, width, height)
+ local ret = {}
+
+ -- Add some fallback values.
+ m.left = m.left or 0
+ m.right = m.right or 0
+ m.top = m.top or 0
+ m.bottom = m.bottom or 0
+
+ -- Take the center widget size into account.
+ width = math.min(width , (fit_width or 9999) + m.left + m.right)
+ height = math.min(height, (fit_height or 9999) + m.top + m.right)
+
+ -- Corners (w,h,x,y).
+ ret.top_left = {m.left , m.top , 0 , 0 }
+ ret.top_right = {m.right, m.top , width - m.right, 0 }
+ ret.bottom_left = {m.left , m.bottom, 0 , height - m.bottom}
+ ret.bottom_right = {m.right, m.bottom, width - m.right, height - m.bottom}
+
+ -- Sides (w,h,x,y).
+ ret.left = {m.left , height - m.top - m.bottom, 0 , m.top }
+ ret.right = {m.right , height - m.top - m.bottom, width-m.right, m.top }
+ ret.top = {width - m.left - m.right, m.top , m.left , 0 }
+ ret.bottom = {width - m.left - m.right, m.bottom , m.left , height - m.bottom}
+
+ -- Honor the border_widgets `:fit()`
+ ret.left [2] = math.max(mins.left , ret.left [2])
+ ret.right [2] = math.max(mins.right , ret.right [2])
+ ret.top [1] = math.max(mins.top , ret.top [1])
+ ret.bottom[1] = math.max(mins.bottom, ret.bottom[1])
+
+ -- Center / fill
+ ret.fill = {width - m.left - m.right, height - m.top - m.bottom, m.left, m.top}
+
+ -- Undo what we just did to merge the widgets.
+ if self._private.border_merging then
+ local to_del = {}
+
+ for _, side in ipairs { "top", "bottom" } do
+ if self._private.border_merging[side] then
+ ret[side][1] = width
+ ret[side][3] = 0
+
+ to_del[side.."_left" ] = true
+ to_del[side.."_right"] = true
+ end
+ end
+
+ for _, side in ipairs { "left", "right" } do
+ if self._private.border_merging[side] then
+ ret[side][2] = height
+ ret[side][4] = 0
+
+ to_del["top_" ..side] = true
+ to_del["bottom_"..side] = true
+ end
+ end
+
+ for corner in pairs(to_del) do
+ ret[corner], widgets[corner] = nil, nil
+ end
+ end
+
+ return ret, widgets
+end
+
+local function init_imagebox(self, ib, border_type)
+ ib.horizontal_fit_policy = border_type
+ ib.vertical_fit_policy = border_type
+ ib.upscale = true
+ ib.valign = "center"
+ ib.halign = "center"
+ ib.resize = true
+ ib.scaling_quality = self._private.image_scaling_quality or "nearest"
+end
+
+local function slice(self, ctx, borders)
+ if not self._private.border_image then
+ self._private.sliced_surface = nil
+ return
+ end
+
+ if not self._private.slice then return end
+
+ -- key: dpi, value: widgets
+ self._private.slice_cache = self._private.slice_cache or {}
+
+ if self._private.slice_cache[ctx.dpi] then
+ return self._private.slice_cache[ctx.dpi]
+ end
+
+ self._private.slice_cache[ctx.dpi] = {}
+
+ local ibs, crs = {}, {}
+
+ local md = setup_origin_common(self, ctx)
+ self._private.original_md = md
+
+ if not md.surface then return end
+
+ local scale_w, scale_h = 1, 1
+
+ -- This feature allows to provide very large texture while still sharing
+ -- the same assets between standard DPI and HiDPI computers.
+ if self._private.border_image_dpi and not md.handle then
+ local scale = ctx.dpi/self._private.border_image_dpi
+ scale_w, scale_h = scale, scale
+ end
+
+ local ARGB32 = cairo.Format.ARGB32
+
+ ibs.top_left = cairo.ImageSurface(ARGB32, borders.left[1], borders.top[2])
+ ibs.top = cairo.ImageSurface(ARGB32, md.width - borders.left[1] - borders.right[1], borders.top[2])
+ ibs.top_right = cairo.ImageSurface(ARGB32, borders.right[1], borders.top[2])
+ ibs.right = cairo.ImageSurface(ARGB32, borders.right[1], md.height - borders.top[2] - borders.bottom[2])
+ ibs.bottom_right = cairo.ImageSurface(ARGB32, borders.right[1], borders.bottom[2])
+ ibs.bottom_left = cairo.ImageSurface(ARGB32, borders.left[1], borders.bottom[2])
+ ibs.left = cairo.ImageSurface(ARGB32, borders.left[1], md.height - borders.top[2] - borders.bottom[2])
+ ibs.bottom = cairo.ImageSurface(
+ ARGB32,
+ md.width - borders.left[1] - borders.right[1],
+ borders.bottom[2]
+ )
+ ibs.fill = cairo.ImageSurface(
+ ARGB32,
+ md.width - borders.left[1] - borders.right[1],
+ md.height - borders.top[2] - borders.bottom[2]
+ )
+
+ for _, position in ipairs(components) do
+ crs[position] = cairo.Context(ibs[position])
+ end
+
+ if scale_w > 1 or scale_h > 1 then
+ md.width, md.height = math.ceil(md.width * scale_w), math.ceil(md.height * scale_h)
+
+ local new_origin = cairo.ImageSurface(ARGB32, md.width, md.height)
+ local slice_cr = cairo.Context(new_origin)
+
+ slice_cr:set_source_surface(md.surface)
+ slice_cr:scale(scale_w, scale_h)
+ slice_cr:paint()
+
+ md.surface = new_origin
+ end
+
+ -- Top
+ crs.top:translate(-borders.left[1], 0)
+
+ -- Top right
+ crs.top_right:translate(-md.width + borders.right[1], 0)
+
+ -- Right
+ crs.right:translate(-md.width + borders.right[1], - borders.right[4])
+
+ -- Bottom right
+ crs.bottom_right:translate(-md.width + borders.right[1], -md.height + borders.bottom[2])
+
+ -- Bottom
+ crs.bottom:translate(-borders.left[1], -md.height + borders.bottom[2])
+
+ -- Bottom left
+ crs.bottom_left:translate(0, -md.height + borders.bottom[2])
+
+ -- Left
+ crs.left:translate(0, - borders.top_left[2])
+
+ -- Center
+ crs.fill:translate(-borders.left[1], -borders.top_left[2])
+
+ for _, position in ipairs(components) do
+ crs[position]:set_source_surface(md.surface)
+ crs[position]:paint()
+
+ -- Scaling was attempted, but there is still some corner cases like the
+ -- `fill` :fit()` causing a negative texture size.
+ local ib = imagebox(ibs[position])
+ init_imagebox(self, ib, self._private[slice_modes[position].."_fit_policy"])
+ self._private.slice_cache[ctx.dpi][position] = ib
+ end
+end
+
+local function setup_background(self, ctx)
+ if not self._private.border_image then
+ self._private.sliced_surface = nil
+ return
+ end
+
+ if self._private.background_widget then
+ return self._private.background_widget
+ end
+
+ self._private.original_md = setup_origin_common(self, ctx)
+
+ if not self._private.original_md.surface then return end
+
+ local ib = imagebox(self._private.original_md.surface)
+ self._private.background_widget = ib
+ self._private.background_widget.resize = true
+ ib.horizontal_fit_policy = self._private.filling_fit_policy
+ ib.vertical_fit_policy = self._private.filling_fit_policy
+
+ return self._private.background_widget
+end
+
+local function children_layout(self, borders, width, height, wdg_w, wdg_h)
+ assert(wdg_w > 0 and wdg_h > 0)
+
+ -- This need to be after the other because it has to be on top.
+ if self._private.widget then
+ local p = self._private.paddings or {}
+ if not self._private.honor_borders then
+ wdg_w, wdg_h = width - (p.right or 0) - (p.left or 0), height - (p.top or 0) - (p.bottom or 0)
+ wdg_w, wdg_h = math.max(0, wdg_w), math.max(0, wdg_h)
+
+ return base.place_widget_at(self._private.widget, 0 + (p.left or 0), 0 + (p.top or 0), wdg_w, wdg_h)
+ else
+ wdg_w, wdg_h = wdg_w - (p.right or 0) - (p.left or 0), wdg_h - (p.top or 0) - (p.bottom or 0)
+ wdg_w, wdg_h = math.max(0, wdg_w), math.max(0, wdg_h)
+
+ return base.place_widget_at(
+ self._private.widget, borders.left[1] + (p.left or 0), borders.top[2] + (p.top or 0), wdg_w, wdg_h
+ )
+ end
+ end
+end
+
+-- Delayed initialization of the border_images.
+local function init_border_images(self)
+ self._private.pending_border_images = false
+ local value = self._private.border_images
+
+ if not value then return end
+
+ if type(value) ~= "table" then
+ local ibs = {}
+
+ for _, t in ipairs(fit_types) do
+ local ib = imagebox(value)
+ rawset(ib, "fit", imagebox_fit)
+ init_imagebox(self, ib, self._private[t.."_fit_policy"])
+ ibs[t] = ib
+ end
+
+ self._private.border_image_widgets = {}
+
+ for component, fit_type in pairs(slice_modes) do
+ self._private.border_image_widgets[component] = ibs[fit_type]
+ end
+ else
+ -- Salvage the old objects.
+ local old_widgets = self._private.border_image_widgets
+
+ self._private.border_image_widgets = {}
+
+ for component, img in pairs(value) do
+ local k, v = next(old_widgets)
+
+ if v then
+ old_widgets[k] = nil
+ end
+
+ local ib = v or imagebox()
+ rawset(ib, "fit", imagebox_fit)
+ local mode = slice_modes[component].."_fit_policy"
+ init_imagebox(self, ib, self._private[mode])
+ ib.image = img
+ self._private.border_image_widgets[component] = ib
+ end
+ end
+end
+
+function module:fit(ctx, width, height)
+ if self._private.pending_border_images then
+ init_border_images(self)
+ end
+
+ local w, h, borders = 0, 0
+
+ if self._private.widget then
+ w, h = base.fit_widget(self, ctx, self._private.widget, width, height)
+
+ -- Add padding.
+ if w > 0 and h > 0 and self._private.paddings then
+ w = w + (self._private.paddings.left or 0) + (self._private.paddings.right or 0)
+ h = h + (self._private.paddings.top or 0) + (self._private.paddings.bottom or 0)
+ end
+
+ assert(w >= 0 and h>=0)
+
+ borders = compute_borders(self, ctx, width, height, w, h)
+ else
+ borders = compute_borders(self, ctx, width, height, width, height)
+ end
+
+ -- Make sure the border `fit` are taken into account.
+ w = math.max(borders.top [1], w)
+ h = math.max(borders.left[2], h)
+
+ -- Add the borders around the central widget.
+ w, h = w + borders.left[1] + borders.right[1], h + borders.top[2] + borders.bottom[2]
+
+ assert(w >= 0 and h >= 0)
+
+ return math.min(width, w), math.min(height, h)
+end
+
+function module:layout(ctx, width, height)
+ local positioned = {}
+ local borders, widgets = compute_borders(self, ctx, width, height)
+
+ local wdg_w = width - borders.left[1] - borders.right[1]
+ local wdg_h = height - borders.top[2] - borders.bottom[2]
+
+ if self._private.ontop == false and self._private.widget then
+ table.insert(positioned, children_layout(self, borders, width, height, wdg_w, wdg_h))
+ end
+
+ if self._private.slice then
+ slice(self, ctx, borders)
+ get_all_widgets(self, widgets, ctx)
+
+ -- Use `ipairs` rather than `pairs` on the widget to keep the order stable.
+ for _, position in ipairs(components) do
+ local geo = borders[position]
+ --TODO use unpack
+ if geo and widgets[position] and not (position == "fill" and not self._private.fill) then
+ local place = base.place_widget_at(widgets[position], geo[3], geo[4], geo[1], geo[2])
+ table.insert(positioned, place)
+ end
+ end
+ else
+ local bg = setup_background(self, ctx)
+
+ if bg then
+ table.insert(
+ positioned,
+ base.place_widget_at(bg, 0, 0, width, height)
+ )
+ end
+ end
+
+ if self._private.ontop ~= false and self._private.widget then
+ table.insert(positioned, children_layout(self, borders, width, height, wdg_w, wdg_h))
+ end
+
+ return positioned
+end
+
+--- The widget to display inside of the border.
+--
+-- @property widget
+-- @tparam[opt=nil] widget|nil widget
+
+module.set_widget = base.set_widget_common
+
+function module:get_widget()
+ return self._private.widget
+end
+
+function module:get_children()
+ return {self._private.widget}
+end
+
+function module:set_children(children)
+ self:set_widget(children[1])
+end
+
+--- Reset this layout. The widget will be removed and the rotation reset.
+-- @method reset
+-- @noreturn
+-- @interface container
+function module:reset()
+ self:set_widget(nil)
+end
+
+--- A single border image for the border.
+--
+-- When using this property, the `borders` also **needs** to be specified.
+--
+-- @property border_image
+-- @tparam[opt=nil] string|image|nil border_image
+-- @see borders
+-- @see border_images
+
+function module:set_border_image(value)
+ if self._private.border_image == value then return end
+
+ self._private.slice_cache = nil
+ self._private.original_md = nil
+ self._private.background_widget = nil
+ self._private.border_image = value
+ self:emit_signal("widget::redraw_needed")
+end
+
+--- Sice the `border_image` to fit the content.
+--
+-- This applies a CSS-like modifier to the image. It will use the
+-- values of the `borders` property to split the original image into
+-- multiple smaller images and use the value of `filling_fit_policy`,
+-- `sides_fit_policy` and `corners_fit_policy` to resize the content.
+--
+-- @DOC_wibox_container_border_slice1_EXAMPLE@
+--
+-- @property slice
+-- @tparam[opt=true] boolean slice
+-- @propemits true false
+
+function module:set_slice(value)
+ if self._private.slice == value then return end
+
+ self._private.slice = value
+ self:emit_signal("property::slice", value)
+ self:emit_signal("widget::layout_changed")
+ self:emit_signal("widget::redraw_needed")
+end
+
+--FIXME DPI support remains undocumented because it has unfixed corner
+-- cases.
+-- Set the DPI of the slice surface.
+--
+-- If this is set, the slicer will enable HiDPI mode. It will
+-- resize the surface using the ratio created by the effective
+-- DPI and the source DPI.
+--
+-- If the `border_image` is a `.svg` file, this will override the
+-- SVG default DPI.
+--
+-- @DOC_wibox_container_border_dpi1_EXAMPLE@
+--
+-- @property border_image_dpi
+-- @tparam[opt=nil] number border_image_dpi
+
+function module:set_border_image_dpi(value)
+ self._private.original_md = nil
+ self._private.border_image_dpi = value
+ self:emit_signal("widget::redraw_needed")
+end
+
+--- Set a stylesheet for the slice surface.
+--
+-- This only affect `.svg` based assets. It does nothing for `.png`/`.jpg`
+-- and already loaded `gears.surfaces` based assets.
+--
+-- @DOC_wibox_container_border_stylesheet1_EXAMPLE@
+--
+-- @property border_image_stylesheet
+-- @tparam[opt=""] string border_image_stylesheet CSS data or file path.
+-- @see wibox.widget.imagebox.stylesheet
+
+function module:set_border_image_stylesheet(value)
+ self._private.original_md = nil
+ self._private.border_image_stylesheet = value
+ self:emit_signal("widget::redraw_needed")
+end
+
+--- How the border_image(s) are scaled.
+--
+-- Note that `nearest` and `best` are the most sensible choices. Other
+-- modes may introduce anti-aliasing artifacts at the junction of the various images.
+--
+--
+--
+--
Value
+--
Description
+--
+--
fast
A high-performance filter
+--
good
A reasonable-performance filter
+--
best
The highest-quality available
+--
nearest
Nearest-neighbor filtering (blocky)
+--
bilinear
Linear interpolation in two dimensions
+--
+--
+--
+--
+-- @property image_scaling_quality
+-- @tparam[opt="nearest"] string image_scaling_quality
+-- @propertyvalue "fast" A high-performance filter.
+-- @propertyvalue "good" A reasonable-performance filter.
+-- @propertyvalue "best" The highest-quality available.
+-- @propertyvalue "nearest" Nearest-neighbor filtering (blocky).
+-- @propertyvalue "bilinear" Linear interpolation in two dimensions.
+
+--- Use images for each of the side/corner/filling sections.
+--
+-- This property is for using different images for each component
+-- of the border. If you want to use a single image, use `border_image`.
+--
+-- Please note that this is mutually exclusive for each corner or side with
+-- `border_widgets`. It has priority over `border_image`.
+--
+-- @DOC_wibox_container_border_border_images1_EXAMPLE@
+--
+-- @property border_images
+-- @tparam[opt=nil] table|image|nil border_images
+-- @tparam[opt=nil] string|image|nil border_images.top_left
+-- @tparam[opt=nil] string|image|nil border_images.top
+-- @tparam[opt=nil] string|image|nil border_images.top_right
+-- @tparam[opt=nil] string|image|nil border_images.right
+-- @tparam[opt=nil] string|image|nil border_images.bottom_right
+-- @tparam[opt=nil] string|image|nil border_images.bottom
+-- @tparam[opt=nil] string|image|nil border_images.bottom_left
+-- @tparam[opt=nil] string|image|nil border_images.left
+-- @propemits true false
+-- @see border_image
+
+function module:set_border_images(value)
+ self._private.border_image_widgets = {}
+ self._private.original_md = nil
+ self._private.border_images = value
+
+ -- Delay the initialization because the fit policy might not be
+ -- set yet.
+ self._private.pending_border_images = true
+
+ self:emit_signal("property::border_images", value)
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+end
+
+--- The size of the border on each side.
+--
+-- Ideally, the value should be smaller than half of the original
+-- surface size. For example, if the source is 48x48, then the
+-- maximum size should be 23 pixels. If the value exceed this, it
+-- will be lowered to the maximum value.
+--
+-- @DOC_wibox_container_border_borders1_EXAMPLE@
+--
+-- @property borders
+-- @tparam[opt=0] table|number borders
+-- @tparam[opt=0] number borders.top
+-- @tparam[opt=0] number borders.left
+-- @tparam[opt=0] number borders.right
+-- @tparam[opt=0] number borders.bottom
+-- @negativeallowed false
+
+function module:set_borders(value)
+
+ if type(value) == "number" then
+ value = {
+ top = value,
+ left = value,
+ right = value,
+ bottom = value,
+ }
+ end
+
+ self._private.borders = value
+
+ self:emit_signal("widget::layout_changed")
+end
+
+--- How the sliced image is resized for the border sides.
+--
+-- In the following example, the gradient based border works
+-- will with `fit` and `pad`. The repeated dot works well with
+-- `repeat` and `reflect`. The soft shadow one works regardless
+-- of the mode because the borders are identical.
+--
+-- @DOC_wibox_container_border_sides_fit_policy1_EXAMPLE@
+--
+-- @property sides_fit_policy
+-- @tparam[opt="fit"] string sides_fit_policy
+-- @propertyvalue "fit" (default)
+-- @propertyvalue "repeat"
+-- @propertyvalue "reflect"
+-- @propertyvalue "pad"
+-- @propemits true false
+-- @see wibox.widget.imagebox.vertical_fit_policy
+-- @see wibox.widget.imagebox.horizontal_fit_policy
+
+--- How the sliced image is resized for the border filling.
+--
+-- Also note that if `slice` is set to `false`, this will be used for
+-- the entire background.
+--
+-- @DOC_wibox_container_border_filling_fit_policy1_EXAMPLE@
+--
+-- @property filling_fit_policy
+-- @tparam[opt="fit"] string filling_fit_policy
+-- @propertyvalue "fit" (default)
+-- @propertyvalue "repeat"
+-- @propertyvalue "reflect"
+-- @propertyvalue "pad"
+-- @propemits true false
+-- @see fill
+-- @see wibox.widget.imagebox.vertical_fit_policy
+-- @see wibox.widget.imagebox.horizontal_fit_policy
+
+--- How the sliced image is resized for the border corners.
+--
+-- @DOC_wibox_container_border_corners_fit_policy1_EXAMPLE@
+--
+-- @property corners_fit_policy
+-- @tparam[opt="fit"] string corners_fit_policy
+-- @propertyvalue "fit" (default)
+-- @propertyvalue "repeat"
+-- @propertyvalue "reflect"
+-- @propertyvalue "pad"
+-- @propemits true false
+-- @see wibox.widget.imagebox.vertical_fit_policy
+-- @see wibox.widget.imagebox.horizontal_fit_policy
+
+for _, mode in ipairs {"corners", "sides", "filling" } do
+ module["set_"..mode.."_fit_policy"] = function(self, value)
+ for _, widgets in pairs(self._private.slice_cache or {}) do
+ for position, widget in pairs(widgets) do
+ if slice_modes[position] == mode then
+ widget.horizontal_fit_policy = value
+ widget.vertical_fit_policy = value
+ end
+ end
+ end
+
+ if mode == "filling" and self._private.background_widget then
+ self._private.background_widget.horizontal_fit_policy = value
+ self._private.background_widget.horizontal_fit_policy = value
+ end
+
+ self._private.pending_border_images = true
+ self._private[mode.."_fit_policy"] = value
+ self:emit_signal("property::"..mode.."_fit_policy", value)
+ self:emit_signal("widget::redraw_needed")
+ end
+end
+
+--- Stretch the child widget over the entire area.
+--
+-- By default, the widget honors the borders.
+--
+--@DOC_wibox_container_border_honor_borders1_EXAMPLE@
+--
+-- @property honor_borders
+-- @tparam[opt=true] boolean honor_borders
+-- @propemits true false
+-- @see ontop
+
+function module:set_honor_borders(value)
+ if self._private.honor_borders == value then return end
+
+ self._private.honor_borders = value
+ self:emit_signal("property::honor_borders", value)
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Draw the child widget below or on top of the border.
+--
+-- `wibox.container.background` supports border clips because it knows the
+-- shape of the border. `wibox.container.border` doesn't have this luxury
+-- because the border is defined in an image and the "inner limit" could
+-- be any pixel.
+--
+-- This proporty helps mitigate that by allowing the border to be drawn on
+-- top of the child widget when `honor_borders` is set to false. When
+-- `honor_borders` is `true`, it does nothing. In the example below, some
+-- `paddings` are also necessary to look decent.
+--
+-- @DOC_wibox_container_border_ontop1_EXAMPLE@
+--
+-- @property ontop
+-- @tparam[opt=true] boolean ontop
+-- @propemits true false
+-- @see honor_borders
+-- @see paddings
+
+function module:set_ontop(value)
+ self._private.ontop = value
+
+ self:emit_signal("property::ontop", value)
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Use the center portion of the `border_image` as a background.
+--
+-- @DOC_wibox_container_border_fill1_EXAMPLE@
+--
+-- @property fill
+-- @tparam[opt=false] boolean fill
+-- @propemits true false
+-- @see filling_fit_policy
+
+function module:set_fill(value)
+ if self._private.fill == value then return end
+
+ self._private.fill = value
+ self:emit_signal("property::fill", value)
+ self:emit_signal("widget::redraw_needed")
+end
+
+--- Add some space between the border and the inner widget.
+--
+-- This is pretty much exactly what `wibox.container.margin` would be
+-- used for, but since this is a very common use case, this container
+-- also has it built-in.
+--
+-- A common use case for this is asymetric borders such as soft shadows.
+--
+-- @DOC_wibox_container_border_paddings1_EXAMPLE@
+--
+-- @property paddings
+-- @tparam[opt=0] number|table paddings
+-- @propemits true false
+-- @negativeallowed false
+-- @see wibox.container.margin
+
+function module:set_paddings(value)
+ if self._private.paddings == value then return end
+
+ if type(value) == "number" then
+ value = {
+ left = value,
+ right = value,
+ top = value,
+ bottom = value
+ }
+ end
+
+ self._private.paddings = value
+ self:emit_signal("property::paddings", value)
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Use individual widgets as a border.
+--
+-- Please note that this is mutually exclusive for each
+-- corner or side with `border_images`. It
+-- has priority over `border_image`.
+--
+-- Please note that if `borders` is not defined, the side widgets
+-- (`left`, `right`, `top` and `bottom`) will be used to compute
+-- the side of the borders. If you only have corner widgets, then
+-- `borders` need to be defined.
+--
+--@DOC_wibox_container_border_border_widgets1_EXAMPLE@
+--
+-- @property border_widgets
+-- @tparam[opt=nil] table|nil border_widgets
+-- @tparam[opt=nil] wibox.widget border_widgets.top_left
+-- @tparam[opt=nil] wibox.widget border_widgets.top
+-- @tparam[opt=nil] wibox.widget border_widgets.top_right
+-- @tparam[opt=nil] wibox.widget border_widgets.right
+-- @tparam[opt=nil] wibox.widget border_widgets.bottom_right
+-- @tparam[opt=nil] wibox.widget border_widgets.bottom
+-- @tparam[opt=nil] wibox.widget border_widgets.bottom_left
+-- @tparam[opt=nil] wibox.widget border_widgets.left
+-- @propemits true false
+
+function module:set_border_widgets(value)
+ local widgets = {}
+
+ if type(value) ~= "table" then
+ for _, component in ipairs(components) do
+ widgets[component] = value
+ and base.make_widget_from_value(value)
+ or nil
+ end
+ else
+ for component, widget in pairs(value) do
+ widgets[component] = widget
+ and base.make_widget_from_value(widget)
+ or nil
+ end
+ end
+
+ self._private.border_widgets = widgets
+
+ self:emit_signal("property::border_widgets", widgets)
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+end
+
+--- Merge the corners widgets into the side widgets.
+--
+-- Rather than have corner widgets/images, let the side
+-- widget span the entire area. This feature is coupled with
+-- `border_widgets` and does nothing when only an image is present.
+--
+-- @DOC_wibox_container_border_border_merging1_EXAMPLE@
+--
+-- @property border_merging
+-- @tparam table|nil border_merging
+-- @tparam[opt=false] boolean border_merging.top Extend the `top` side
+-- widget by replacing the `top_left` and `top_right` widgets.
+-- @tparam[opt=false] boolean border_merging.bottom Extend the `bottom` side
+-- widget by replacing the `bottom_left` and `bottom_right` widgets.
+-- @tparam[opt=false] boolean border_merging.left Extend the `left` side
+-- widget by replacing the `top_left` and `bottom_left` widgets.
+-- @tparam[opt=false] boolean border_merging.right Extend the `right` side
+-- widget by replacing the `top_right` and `bottom_right` widgets.
+-- @propemits true false
+
+function module:set_border_merging(value)
+ self._private.border_merging = value
+
+ self:emit_signal("property::border_widgets", value)
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+end
+
+--- When `border_widgets` is used, allow the border to grow due to corner widgets.
+--
+-- @DOC_wibox_container_border_expand_corners1_EXAMPLE@
+--
+-- @property expand_corners
+-- @tparam[opt=false] boolean expand_corners
+
+function module:set_expand_corners(value)
+ self._private.expand_corners = value
+
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("widget::layout_changed")
+end
+
+local function new(_, args)
+ args = args or {}
+
+ local ret = base.make_widget(nil, nil, {enable_properties = true})
+ ret._private.corners_fit_policy = args.corners_fit_policy or "fit"
+ ret._private.sides_fit_policy = args.slides_fit_policy or "fit"
+ ret._private.filling_fit_policy = args.filling_fit_policy or "fit"
+ ret._private.honor_borders = args.honor_borders ~= false
+ ret._private.fill = args.fill or false
+ ret._private.slice = args.slice == nil and true or args.slice
+
+ gtable.crush(ret, module, true)
+ gtable.crush(ret, args, false)
+
+ return ret
+end
+
+return setmetatable(module, {__call=new})
diff --git a/lib/wibox/container/init.lua b/lib/wibox/container/init.lua
index 69926fb2f..8bc50d6d8 100644
--- a/lib/wibox/container/init.lua
+++ b/lib/wibox/container/init.lua
@@ -18,6 +18,7 @@ return setmetatable({
arcchart = require("wibox.container.arcchart");
place = require("wibox.container.place");
tile = require("wibox.container.tile");
+ border = require("wibox.container.border");
}, {__call = function(_, args) return base.make_widget_declarative(args) end})
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/widget/imagebox.lua b/lib/wibox/widget/imagebox.lua
index 79b6f710b..07b7bed48 100644
--- a/lib/wibox/widget/imagebox.lua
+++ b/lib/wibox/widget/imagebox.lua
@@ -31,12 +31,23 @@ local base = require("wibox.widget.base")
local surface = require("gears.surface")
local gtable = require("gears.table")
local gdebug = require("gears.debug")
+local gfs = require("gears.filesystem")
local setmetatable = setmetatable
local type = type
local math = math
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 = {
+ ["pad"] = cairo.Extend.PAD,
+ ["repeat"] = cairo.Extend.REPEAT,
+ ["reflect"] = cairo.Extend.REFLECT,
+}
+
-- Safe load for optional Rsvg module
local Rsvg = nil
do
@@ -49,18 +60,25 @@ end
local imagebox = { mt = {} }
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.
-- @return Rsvg handle
-- @treturn table A table where cached data can be stored.
-local function 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
- 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
- return cache, rsvg_handle_cache[file]
+ return cache, bucket[style_ref]
end
local handle, err
@@ -72,9 +90,10 @@ local function load_rsvg_handle(file)
end
if not err then
- rsvg_handle_cache[file] = rsvg_handle_cache[file] or {}
- rsvg_handle_cache[file]["handle"] = handle
- return handle, rsvg_handle_cache[file]
+ rsvg_handle_cache[file] = rsvg_handle_cache[file] or setmetatable({}, {__mode = "k"})
+ rsvg_handle_cache[file][style_ref] = rsvg_handle_cache[file][style_ref] or {}
+ rsvg_handle_cache[file][style_ref]["handle"] = handle
+ return handle, rsvg_handle_cache[file][style_ref]
end
end
@@ -111,7 +130,7 @@ end
---@treturn boolean True if image was successfully applied
local function load_and_apply(ib, file, image_loader, image_setter)
local image_applied
- local object, cache = image_loader(file)
+ local object, cache = image_loader(file, ib._private.stylesheet_og)
if object then
image_applied = image_setter(ib, object, cache)
@@ -119,6 +138,38 @@ local function load_and_apply(ib, file, image_loader, image_setter)
return image_applied
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.
--
-- It's necessary because a single RSVG handle can be used by
@@ -182,6 +233,11 @@ function imagebox:draw(ctx, cr, width, height)
local w, h = self._private.default.width, self._private.default.height
+ local policy = {
+ w = self._private.horizontal_fit_policy or "auto",
+ h = self._private.vertical_fit_policy or "auto"
+ }
+
if self._private.resize then
-- That's for the "fit" policy.
local aspects = {
@@ -189,17 +245,12 @@ function imagebox:draw(ctx, cr, width, height)
h = height / h
}
- local policy = {
- w = self._private.horizontal_fit_policy or "auto",
- h = self._private.vertical_fit_policy or "auto"
- }
-
for _, aspect in ipairs {"w", "h"} do
if self._private.upscale == false and (w < width and h < height) then
aspects[aspect] = 1
elseif self._private.downscale == false and (w >= width and h >= height) then
aspects[aspect] = 1
- elseif policy[aspect] == "none" then
+ elseif policy[aspect] == "none" or policies_to_extents[policy[aspect]] then
aspects[aspect] = 1
elseif policy[aspect] == "auto" then
aspects[aspect] = math.min(width / w, height / h)
@@ -258,7 +309,18 @@ function imagebox:draw(ctx, cr, width, height)
if self._private.handle then
self._private.handle:render_cairo(cr)
else
- cr:set_source_surface(self._private.image, 0, 0)
+ -- Yes, it is possible that the vertical or horizontal policies both
+ -- have extends, but Cairo doesn't support this. So be it.
+ local pol = policies_to_extents[policy.w]
+ pol = pol or policies_to_extents[policy.h]
+
+ if pol then
+ local pat = cairo.Pattern.create_for_surface(self._private.image)
+ pat:set_extend(pol)
+ cr:set_source(pat)
+ else
+ cr:set_source_surface(self._private.image, 0, 0)
+ end
local filter = self._private.scaling_quality
@@ -300,6 +362,32 @@ end
-- @tparam[opt=nil] image|nil image
-- @propemits false false
+--- Return the source image width.
+--
+-- For SVG images, this may be affected by the DPI and might not
+-- reflect the size the images will be rendered at. For PNG or
+-- JPG images, this will return the file resolution.
+--
+-- @property source_width
+-- @tparam number source_width
+-- @propertydefault This depends on the source image.
+-- @negativeallowed false
+-- @see image
+-- @see source_height
+
+--- Return the source image height.
+--
+-- For SVG images, this may be affected by the DPI and might not
+-- reflect the size the images will be rendered at. For PNG or
+-- JPG images, this will return the file resolution.
+--
+-- @property source_height
+-- @tparam number source_height
+-- @propertydefault This depends on the source image.
+-- @negativeallowed false
+-- @see image
+-- @see source_width
+
--- Set the `imagebox` image.
--
-- The image can be a file, a cairo image surface, or an rsvg handle object
@@ -326,7 +414,7 @@ function imagebox:set_image(image)
if type(image) == "string" then
-- try to load rsvg handle from file
- setup_succeed = load_and_apply(self, image, load_rsvg_handle, set_handle)
+ setup_succeed = load_and_apply(self, image, imagebox._load_rsvg_handle, set_handle)
if not setup_succeed then
-- rsvg handle failed, try to load cairo surface with pixbuf
@@ -355,6 +443,14 @@ function imagebox:set_image(image)
return true
end
+for _, dim in ipairs { "width", "height" } do
+ imagebox["get_source_"..dim] = function(self)
+ if not self._private.default then return nil end
+
+ return self._private.default[dim]
+ end
+end
+
--- Set a clip shape for this imagebox.
--
-- A clip shape defines an area and dimension to which the content should be
@@ -426,8 +522,7 @@ end
-- 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.
--
--- Note that this property is a string, not a path. If the stylesheet is
--- stored on disk, read the content first.
+-- The value can be either CSS data or a file path.
--
--@DOC_wibox_widget_imagebox_stylesheet_EXAMPLE@
--
@@ -466,18 +561,47 @@ end
-- @propemits true false
-- @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)
+ local old = self._private[prop]
+
-- It will be set in :fit and :draw. The handle is shared
-- by multiple imagebox, so it cannot be set just once.
self._private[prop] = value
self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed")
- self:emit_signal("property::" .. prop)
+ self:emit_signal("property::" .. prop, value, old)
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)
self._private.resize = allowed
@@ -510,6 +634,9 @@ end
--- Set the horizontal fit policy.
--
+-- Note that `repeat`, `reflect` and `pad` cannot be mixed across
+-- the vertical and horizontal axis.
+--
-- Here is the result for a 22x32 image:
--
-- @DOC_wibox_widget_imagebox_horizontal_fit_policy_EXAMPLE@
@@ -519,12 +646,20 @@ end
-- @propertyvalue "auto" Honor the `resize` variable and preserve the aspect ratio.
-- @propertyvalue "none" Do not resize at all.
-- @propertyvalue "fit" Resize to the widget width.
+-- @propertyvalue "repeat" Repeat the image side by side.
+-- @propertyvalue "reflect" Like `repeat`, but alternate the reflection.
+-- @propertyvalue "pad" Take the last column of pixels and repeat them.
-- @propemits true false
-- @see vertical_fit_policy
-- @see resize
--- Set the vertical fit policy.
--
+-- Valid values are:
+--
+-- Note that `repeat`, `reflect` and `pad` cannot be mixed across
+-- the vertical and horizontal axis.
+--
-- Here is the result for a 32x22 image:
--
-- @DOC_wibox_widget_imagebox_vertical_fit_policy_EXAMPLE@
@@ -534,6 +669,10 @@ end
-- @propertyvalue "auto" Honor the `resize` variable and preserve the aspect ratio.
-- @propertyvalue "none" Do not resize at all.
-- @propertyvalue "fit" Resize to the widget height.
+-- @propertyvalue "fit" Resize to the widget width.
+-- @propertyvalue "repeat" Repeat the image side by side.
+-- @propertyvalue "reflect" Like `repeat`, but alternate the reflection.
+-- @propertyvalue "pad" Take the last column of pixels and repeat them.
-- @propemits true false
-- @see horizontal_fit_policy
-- @see resize
diff --git a/tests/examples/shims/beautiful.lua b/tests/examples/shims/beautiful.lua
index 396ace662..dffb924ce 100644
--- a/tests/examples/shims/beautiful.lua
+++ b/tests/examples/shims/beautiful.lua
@@ -3,15 +3,24 @@ local Pango = lgi.Pango
local cairo = lgi.cairo
-- A simple Awesome logo
-local function logo()
+local function logo(fg, bg)
local img = cairo.ImageSurface.create(cairo.Format.ARGB32, 22, 22)
local cr = cairo.Context(img)
-- Awesome default #555555
- cr:set_source_rgb(0.21568627451, 0.21568627451, 0.21568627451)
+ if bg then
+ cr:set_source(bg)
+ else
+ cr:set_source_rgb(0.21568627451, 0.21568627451, 0.21568627451)
+ end
+
cr:paint()
- cr:set_source_rgb(1,1,1)
+ if fg then
+ cr:set_source(fg)
+ else
+ cr:set_source_rgb(1,1,1)
+ end
cr:rectangle(0, 7, 15, 1)
cr:fill()
@@ -48,7 +57,8 @@ local module = {
-- Fake resources handling
xresources = require("beautiful.xresources"),
- awesome_icon = logo()
+ awesome_icon = logo(),
+ _logo = logo,
}
module.graph_bg = module.bg_normal
diff --git a/tests/examples/wibox/container/background/border_color.lua b/tests/examples/wibox/container/background/border_color.lua
new file mode 100644
index 000000000..99a7cd184
--- /dev/null
+++ b/tests/examples/wibox/container/background/border_color.lua
@@ -0,0 +1,52 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+local gears = { shape = require("gears.shape"), color = require("gears.color") }
+local beautiful = require("beautiful")
+
+parent.spacing = 5
+
+--DOC_HIDE_END
+
+local colors = {
+ beautiful.bg_normal,
+ "#00ff00",
+ gears.color {
+ type = "linear",
+ from = { 0 , 20 },
+ to = { 20, 0 },
+ stops = {
+ { 0, "#0000ff" },
+ { 1, "#ff0000" }
+ },
+ },
+}
+
+--DOC_NEWLINE
+
+for _, color in ipairs(colors) do
+ local w = wibox.widget {
+ {
+ {
+ text = " Content ",
+ valign = "center",
+ align = "center",
+ widget = wibox.widget.textbox
+ },
+ margins = 10,
+ widget = wibox.container.margin
+ },
+ border_color = color,
+ border_width = 3,
+ stretch_vertically = true,
+ stretch_horizontally = true,
+ shape = gears.shape.rounded_rect,
+ widget = wibox.container.background
+ }
+
+ parent:add(w) --DOC_HIDE
+end
+
+--DOC_HIDE_START
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/background/border_strategy.lua b/tests/examples/wibox/container/background/border_strategy.lua
new file mode 100644
index 000000000..1952396b9
--- /dev/null
+++ b/tests/examples/wibox/container/background/border_strategy.lua
@@ -0,0 +1,54 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+local gears = { shape = require("gears.shape") }
+local beautiful = require("beautiful")
+
+local l = wibox.layout {
+ forced_width = 640,
+ spacing = 5,
+ forced_num_cols = 2,
+ homogeneous = false,
+ expand = false,
+ layout = wibox.layout.grid.vertical
+}
+
+--DOC_HIDE_END
+
+for k, strategy in ipairs { "none", "inner" } do
+ --DOC_HIDE_START
+ local r = k*2
+
+ l:add_widget_at(wibox.widget {
+ markup = "border_strategy = \"".. strategy .."\"",
+ widget = wibox.widget.textbox,
+ }, r, 1, 1, 2)
+ --DOC_HIDE_END
+
+ for idx, width in ipairs {0, 1, 3, 10 } do
+ local w = wibox.widget {
+ {
+ {
+ text = "border_width = "..width,
+ valign = "center",
+ align = "center",
+ widget = wibox.widget.textbox
+ },
+ border_color = beautiful.bg_normal,
+ border_width = width,
+ border_strategy = strategy,
+ shape = gears.shape.rounded_rect,
+ widget = wibox.container.background
+ },
+ widget = wibox.container.place
+ }
+
+ l:add_widget_at(w, r+1, idx, 1, 1) --DOC_HIDE
+ end
+ --DOC_HIDE_END
+end
+--DOC_HIDE_START
+
+parent:add(l)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/background/border_width.lua b/tests/examples/wibox/container/background/border_width.lua
new file mode 100644
index 000000000..7fc874558
--- /dev/null
+++ b/tests/examples/wibox/container/background/border_width.lua
@@ -0,0 +1,34 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+local gears = { shape = require("gears.shape") }
+local beautiful = require("beautiful")
+
+parent.spacing = 5
+
+--DOC_HIDE_END
+
+for _, width in ipairs {0, 1, 3, 10 } do
+ local w = wibox.widget {
+ {
+ {
+ text = " Content ",
+ valign = "center",
+ align = "center",
+ widget = wibox.widget.textbox
+ },
+ margins = 10,
+ widget = wibox.container.margin
+ },
+ border_color = beautiful.bg_normal,
+ border_width = width,
+ shape = gears.shape.rounded_rect,
+ widget = wibox.container.background
+ }
+
+ parent:add(w) --DOC_HIDE
+end
+
+--DOC_HIDE_START
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/background/stretch_horizontally.lua b/tests/examples/wibox/container/background/stretch_horizontally.lua
new file mode 100644
index 000000000..3cad561b8
--- /dev/null
+++ b/tests/examples/wibox/container/background/stretch_horizontally.lua
@@ -0,0 +1,82 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+local gears = { shape = require("gears.shape"), color = require("gears.color") }
+
+local l = wibox.layout {
+ forced_width = 640,
+ spacing = 5,
+ forced_num_cols = 2,
+ homogeneous = false,
+ expand = false,
+ layout = wibox.layout.grid.vertical
+}
+
+--DOC_HIDE_END
+
+local gradients = {
+ gears.color {
+ type = "linear",
+ from = { 0 , 0 },
+ to = { 100, 0 },
+ stops = {
+ { 0 , "#0000ff" },
+ { 0.8, "#0000ff" },
+ { 1 , "#ff0000" }
+ }
+ },
+ gears.color {
+ type = "radial",
+ from = { 30, 98, 20 },
+ to = { 30, 98, 120 },
+ stops = {
+ { 0 , "#ff0000" },
+ { 0.5, "#00ff00" },
+ { 1 , "#0000ff" },
+ }
+ }
+}
+
+--DOC_NEWLINE
+
+for k, stretch in ipairs { false, true } do
+ --DOC_HIDE_START
+ local r = (k-1)*5 + 1
+
+ l:add_widget_at(wibox.widget {
+ markup = "stretch_horizontally = \"".. (stretch and "true" or "false") .."\"",
+ widget = wibox.widget.textbox,
+ }, r, 1, 1, 2)
+ --DOC_HIDE_END
+
+ for __, grad in ipairs(gradients) do
+ for idx, width in ipairs { 50, 100, 150, 200 } do
+ local w = wibox.widget {
+ {
+ {
+ text = " Width: " .. width .. " ",
+ valign = "center",
+ align = "center",
+ widget = wibox.widget.textbox
+ },
+ bg = grad,
+ stretch_horizontally = stretch,
+ forced_width = width,
+ fg = "#ffffff",
+ shape = gears.shape.rounded_rect,
+ widget = wibox.container.background
+ },
+ forced_width = 200,
+ widget = wibox.container.place
+ }
+
+ l:add_widget_at(w, (k-1)*5 + idx + 1, __, 1, 1) --DOC_HIDE
+ end
+ end
+ --DOC_HIDE_END
+end
+--DOC_HIDE_START
+
+parent:add(l)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/background/stretch_vertically.lua b/tests/examples/wibox/container/background/stretch_vertically.lua
new file mode 100644
index 000000000..35dbb13bf
--- /dev/null
+++ b/tests/examples/wibox/container/background/stretch_vertically.lua
@@ -0,0 +1,83 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+local gears = { shape = require("gears.shape"), color = require("gears.color") }
+
+local l = wibox.layout {
+ forced_width = 640,
+ spacing = 5,
+ forced_num_cols = 2,
+ homogeneous = false,
+ expand = false,
+ layout = wibox.layout.grid.vertical
+}
+
+--DOC_HIDE_END
+
+local gradients = {
+ gears.color {
+ type = "linear",
+ from = { 0, 0 },
+ to = { 0, 100 },
+ stops = {
+ { 0 , "#0000ff" },
+ { 0.8, "#0000ff" },
+ { 1 , "#ff0000" }
+ }
+ },
+ gears.color {
+ type = "radial",
+ from = { 30, 98, 20 },
+ to = { 30, 98, 120 },
+ stops = {
+ { 0 , "#ff0000" },
+ { 0.5, "#00ff00" },
+ { 1 , "#0000ff" },
+ }
+ }
+}
+
+--DOC_NEWLINE
+
+for k, stretch in ipairs { false, true } do
+ --DOC_HIDE_START
+ local r = (k-1) * 3 + 1
+
+ l:add_widget_at(wibox.widget {
+ markup = "stretch_vertically = \"".. (stretch and "true" or "false") .."\"",
+ widget = wibox.widget.textbox,
+ }, r, 1, 1, 2)
+ --DOC_HIDE_END
+
+
+ for _, gradient in ipairs(gradients) do
+ for idx, height in ipairs { 10, 50, 100, 150 } do
+ local w = wibox.widget {
+ {
+ {
+ text = " Height: " .. height .. " ",
+ valign = "center",
+ align = "center",
+ widget = wibox.widget.textbox
+ },
+ bg = gradient,
+ stretch_vertically = stretch,
+ forced_height = height,
+ fg = "#ffffff",
+ shape = gears.shape.rounded_rect,
+ widget = wibox.container.background
+ },
+ forced_height = 150,
+ widget = wibox.container.place
+ }
+
+ l:add_widget_at(w, r + _, idx, 1, 1) --DOC_HIDE
+ end
+ end
+ --DOC_HIDE_END
+end
+--DOC_HIDE_START
+
+parent:add(l)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/border_images1.lua b/tests/examples/wibox/container/border/border_images1.lua
new file mode 100644
index 000000000..69a3ecfba
--- /dev/null
+++ b/tests/examples/wibox/container/border/border_images1.lua
@@ -0,0 +1,59 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+local beautiful = require( "beautiful" )
+local gears = { color = require("gears.color")}
+
+local red_logo = beautiful._logo(nil, gears.color("#ff0000"))
+local green_logo = beautiful._logo(nil, gears.color("#00ff00"))
+local blue_logo = beautiful._logo(nil, gears.color("#0000ff"))
+local yellow_logo = beautiful._logo(nil, gears.color("#ffff00"))
+local orange_logo = beautiful._logo(nil, gears.color("#ffbb00"))
+local purple_logo = beautiful._logo(nil, gears.color("#ff00ff"))
+local cyan_logo = beautiful._logo(nil, gears.color("#00ffff"))
+local black_logo = beautiful._logo(nil, gears.color("#000000"))
+
+--DOC_HIDE_END
+
+local w1 = wibox.widget {
+ {
+ text = "Single image",
+ valign = "center",
+ align = "center",
+ widget = wibox.widget.textbox
+ },
+ borders = 20,
+ sides_fit_policy = "repeat",
+ border_images = blue_logo,
+ widget = wibox.container.border
+}
+
+--DOC_NEWLINE
+
+local w2 = wibox.widget {
+ {
+ text = "Multiple images",
+ valign = "center",
+ align = "center",
+ widget = wibox.widget.textbox
+ },
+ sides_fit_policy = "repeat",
+ border_images = {
+ top_left = red_logo,
+ top = green_logo,
+ top_right = blue_logo,
+ right = yellow_logo,
+ bottom_right = orange_logo,
+ bottom = purple_logo,
+ bottom_left = cyan_logo,
+ left = black_logo,
+ },
+ widget = wibox.container.border
+}
+
+--DOC_HIDE_START
+
+parent.spacing = 20
+parent:add(w1)
+parent:add(w2)
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/border_merging1.lua b/tests/examples/wibox/container/border/border_merging1.lua
new file mode 100644
index 000000000..de40772eb
--- /dev/null
+++ b/tests/examples/wibox/container/border/border_merging1.lua
@@ -0,0 +1,73 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+local beautiful = require( "beautiful" )
+
+local function generic_widget(text, margins)
+ return wibox.widget {
+ {
+ {
+ {
+ id = "text",
+ align = "center",
+ valign = "center",
+ text = text,
+ widget = wibox.widget.textbox
+ },
+ margins = 10,
+ widget = wibox.container.margin,
+ },
+ border_width = 3,
+ border_color = beautiful.border_color,
+ bg = beautiful.bg_normal,
+ widget = wibox.container.background
+ },
+ margins = margins or 5,
+ widget = wibox.container.margin,
+ }
+end
+
+local l = wibox.layout {
+ spacing = 100,
+ forced_num_cols = 2,
+ forced_num_rows = 2,
+ homogeneous = true,
+ expand = true,
+ layout = wibox.layout.grid.vertical
+}
+
+--DOC_HIDE_END
+
+for _, side in ipairs { "top", "bottom", "left", "right" } do
+ l:add(wibox.widget {
+ {
+ text = side .. " = true",
+ valign = "center",
+ align = "center",
+ widget = wibox.widget.textbox
+ },
+ border_merging = {
+ -- This is the equaivalent "side = true,". "side" is the loop
+ -- variable.
+ [side] = true
+ },
+ border_widgets = {
+ top_left = generic_widget("top_left"),
+ top = generic_widget("top"),
+ top_right = generic_widget("top_right"),
+ right = generic_widget("right"),
+ bottom_right = generic_widget("bottom_right"),
+ bottom = generic_widget("bottom"),
+ bottom_left = generic_widget("bottom_left"),
+ left = generic_widget("left"),
+ },
+ widget = wibox.container.border
+ })
+end
+
+
+--DOC_HIDE_START
+
+parent:add(l)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/border_widgets1.lua b/tests/examples/wibox/container/border/border_widgets1.lua
new file mode 100644
index 000000000..c2ce21734
--- /dev/null
+++ b/tests/examples/wibox/container/border/border_widgets1.lua
@@ -0,0 +1,55 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+local beautiful = require( "beautiful" )
+
+local function generic_widget(text, margins)
+ return wibox.widget {
+ {
+ {
+ {
+ id = "text",
+ align = "center",
+ valign = "center",
+ text = text,
+ widget = wibox.widget.textbox
+ },
+ margins = 10,
+ widget = wibox.container.margin,
+ },
+ border_width = 3,
+ border_color = beautiful.border_color,
+ bg = beautiful.bg_normal,
+ widget = wibox.container.background
+ },
+ margins = margins or 5,
+ widget = wibox.container.margin,
+ }
+end
+
+--DOC_HIDE_END
+
+local w = wibox.widget {
+ {
+ text = "Central widget",
+ valign = "center",
+ align = "center",
+ widget = wibox.widget.textbox
+ },
+ border_widgets = {
+ top_left = generic_widget("top_left"),
+ top = generic_widget("top"),
+ top_right = generic_widget("top_right"),
+ right = generic_widget("right"),
+ bottom_right = generic_widget("bottom_right"),
+ bottom = generic_widget("bottom"),
+ bottom_left = generic_widget("bottom_left"),
+ left = generic_widget("left"),
+ },
+ widget = wibox.container.border
+}
+
+--DOC_HIDE_START
+
+parent:add(w)
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/borders1.lua b/tests/examples/wibox/container/border/borders1.lua
new file mode 100644
index 000000000..bd5d07cde
--- /dev/null
+++ b/tests/examples/wibox/container/border/borders1.lua
@@ -0,0 +1,104 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+local cairo = require("lgi").cairo
+
+-- luacheck: push no max string line length
+
+local svg_image_path = [[
+
+
+]]
+
+--luacheck: pop
+
+-- There is no path there, but that's just for the doc.
+local handle = wibox.widget.imagebox._load_rsvg_handle(svg_image_path)
+local png_image_path = cairo.ImageSurface(cairo.Format.ARGB32, 48, 48)
+local cr = cairo.Context(png_image_path)
+handle:render_cairo(cr)
+
+local l = wibox.layout {
+ forced_width = 440,
+ spacing = 5,
+ forced_num_cols = 2,
+ homogeneous = false,
+ expand = false,
+ layout = wibox.layout.grid.vertical
+}
+
+l:add_widget_at(wibox.widget {
+ markup = "SVG image (34x34 pt):",
+ widget = wibox.widget.textbox,
+},1,1)
+
+l:add_widget_at(wibox.widget {
+ markup = "PNG image (48x48 px):",
+ widget = wibox.widget.textbox,
+},1,2)
+
+l:add_widget_at(wibox.widget {
+ image = svg_image_path,
+ forced_height = 48,
+ forced_width = 200,
+ halign = "center",
+ widget = wibox.widget.imagebox,
+}, 2, 1, 1, 1)
+
+l:add_widget_at(wibox.widget {
+ image = png_image_path,
+ forced_height = 48,
+ forced_width = 200,
+ halign = "center",
+ widget = wibox.widget.imagebox,
+}, 2, 2, 1, 1)
+
+--DOC_HIDE_END
+for k, borders in ipairs {0, 10, 30, 64} do
+ --DOC_HIDE_START
+ local r = 1 + k*2
+
+ l:add_widget_at(wibox.widget {
+ markup = "borders = ".. borders .."",
+ widget = wibox.widget.textbox,
+ }, r, 1, 1, 2)
+ --DOC_HIDE_END
+
+ for idx, image in ipairs {svg_image_path, png_image_path} do
+ local w = wibox.widget {
+ {
+ text = "Central widget",
+ valign = "center",
+ align = "center",
+ forced_height = 30,
+ forced_width = 30,
+ widget = wibox.widget.textbox
+ },
+ fill = false,
+ borders = borders,
+ border_image = image,
+ forced_width = 200, --DOC_HIDE
+ forced_height = 100,
+ widget = wibox.container.border
+ }
+ l:add_widget_at(w, r+1, idx, 1, 1)
+ end
+end
+--DOC_HIDE_START
+
+parent:add(l)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/corners_fit_policy1.lua b/tests/examples/wibox/container/border/corners_fit_policy1.lua
new file mode 100644
index 000000000..d97686596
--- /dev/null
+++ b/tests/examples/wibox/container/border/corners_fit_policy1.lua
@@ -0,0 +1,122 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+
+-- luacheck: push no max string line length
+
+local image_path1 = [[
+
+
+]]
+
+local image_path2 = [[
+
+
+]]
+
+--luacheck: pop
+
+local l = wibox.layout {
+ forced_width = 440,
+ spacing = 5,
+ forced_num_cols = 2,
+ homogeneous = false,
+ expand = false,
+ layout = wibox.layout.grid.vertical
+}
+
+l:add(wibox.widget {
+ markup = "Original image:",
+ widget = wibox.widget.textbox,
+})
+
+l:add_widget_at(wibox.widget {
+ image = image_path1,
+ forced_height = 48,
+ forced_width = 200,
+ halign = "center",
+ widget = wibox.widget.imagebox,
+}, 2, 1, 1, 1)
+
+l:add_widget_at(wibox.widget {
+ image = image_path2,
+ forced_height = 48,
+ forced_width = 200,
+ halign = "center",
+ widget = wibox.widget.imagebox,
+}, 2, 2, 1, 1)
+
+--DOC_HIDE_END
+for k, mode in ipairs {"fit", "repeat", "reflect", "pad"} do
+ --DOC_HIDE_START
+ local r = 1 + k*2
+
+ l:add_widget_at(wibox.widget {
+ markup = "corners_fit_policy = \"".. mode .."\"",
+ widget = wibox.widget.textbox,
+ }, r, 1, 1, 2)
+ --DOC_HIDE_END
+
+
+ for idx, image in ipairs { image_path1, image_path2 } do
+ local w = wibox.widget {
+ {
+ text = "Central widget",
+ valign = "center",
+ align = "center",
+ widget = wibox.widget.textbox
+ },
+ fill = false,
+ borders = {
+ left = 10,
+ right = 50,
+ top = 10,
+ bottom = 50,
+ },
+ border_image = image,
+ corners_fit_policy = mode,
+ forced_width = 200, --DOC_HIDE
+ widget = wibox.container.border
+ }
+
+ l:add_widget_at(w, r+1, idx, 1, 1) --DOC_HIDE
+ end
+ --DOC_HIDE_END
+end
+--DOC_HIDE_START
+
+parent:add(l)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/custom_draw1.lua b/tests/examples/wibox/container/border/custom_draw1.lua
new file mode 100644
index 000000000..9eac39e4a
--- /dev/null
+++ b/tests/examples/wibox/container/border/custom_draw1.lua
@@ -0,0 +1,29 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START --DOC_NO_USAGE
+local parent = ...
+local wibox = require("wibox")
+
+--DOC_HIDE_END
+
+ local w = wibox.widget {
+ {
+ text = "Center widget",
+ valign = "center",
+ align = "center",
+ widget = wibox.widget.textbox
+ },
+ after_draw_children = function(_, _, cr, width, height)
+ cr:set_source_rgba(1,0,0,1)
+ cr:set_dash({1,1},1)
+ cr:rectangle(1, 1, width-2, height-2)
+ cr:rectangle(5, 5, width-10, height-10)
+ cr:stroke()
+ end,
+ borders = 20,
+ honor_borders = false,
+ forced_width = 100, --DOC_HIDE
+ forced_height = 50, --DOC_HIDE
+ widget = wibox.container.border
+ }
+
+parent:add(w) --DOC_HIDE
+--DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/dpi1.lua b/tests/examples/wibox/container/border/dpi1.lua
new file mode 100644
index 000000000..81935e677
--- /dev/null
+++ b/tests/examples/wibox/container/border/dpi1.lua
@@ -0,0 +1,104 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+local cairo = require("lgi").cairo
+
+-- luacheck: push no max string line length
+
+local svg_image_path = [[
+
+
+]]
+
+--luacheck: pop
+
+-- There is no path there, but that's just for the doc.
+local handle = wibox.widget.imagebox._load_rsvg_handle(svg_image_path)
+local png_image_path = cairo.ImageSurface(cairo.Format.ARGB32, 48, 48)
+local cr = cairo.Context(png_image_path)
+handle:render_cairo(cr)
+
+local l = wibox.layout {
+ forced_width = 440,
+ spacing = 5,
+ forced_num_cols = 2,
+ homogeneous = false,
+ expand = false,
+ layout = wibox.layout.grid.vertical
+}
+
+l:add_widget_at(wibox.widget {
+ markup = "SVG image (34x34 pt):",
+ widget = wibox.widget.textbox,
+},1,1)
+
+l:add_widget_at(wibox.widget {
+ markup = "PNG image (48x48 px):",
+ widget = wibox.widget.textbox,
+},1,2)
+
+l:add_widget_at(wibox.widget {
+ image = svg_image_path,
+ forced_height = 48,
+ forced_width = 200,
+ halign = "center",
+ widget = wibox.widget.imagebox,
+}, 2, 1, 1, 1)
+
+l:add_widget_at(wibox.widget {
+ image = png_image_path,
+ forced_height = 48,
+ forced_width = 200,
+ halign = "center",
+ widget = wibox.widget.imagebox,
+}, 2, 2, 1, 1)
+
+--DOC_HIDE_END
+for k, dpi in ipairs {72, 96, 220} do
+ --DOC_HIDE_START
+ local r = 1 + k*2
+
+ l:add_widget_at(wibox.widget {
+ markup = "border_image_dpi = ".. dpi .."",
+ widget = wibox.widget.textbox,
+ }, r, 1, 1, 2)
+ --DOC_HIDE_END
+
+ for idx, image in ipairs {svg_image_path, png_image_path} do
+ local w = wibox.widget {
+ {
+ text = "Central widget",
+ valign = "center",
+ align = "center",
+ forced_height = 30,
+ forced_width = 30,
+ widget = wibox.widget.textbox
+ },
+ fill = false,
+ border_image_dpi = dpi,
+ borders = 10,
+ border_image = image,
+ forced_width = 200, --DOC_HIDE
+ widget = wibox.container.border
+ }
+ l:add_widget_at(w, r+1, idx, 1, 1)
+ end
+end
+--DOC_HIDE_START
+
+parent:add(l)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/dual1.lua b/tests/examples/wibox/container/border/dual1.lua
new file mode 100644
index 000000000..0a49b9b3b
--- /dev/null
+++ b/tests/examples/wibox/container/border/dual1.lua
@@ -0,0 +1,90 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START --DOC_NO_USAGE
+local parent = ...
+local wibox = require("wibox")
+local beautiful = require( "beautiful" )
+
+-- luacheck: push no max line length
+
+local image_path = ''..
+ ''
+
+--luacheck: pop
+
+local function generic_widget(text)
+ return wibox.widget {
+ {
+ {
+ {
+ id = "text",
+ align = "center",
+ valign = "center",
+ text = text,
+ widget = wibox.widget.textbox
+ },
+ margins = 10,
+ widget = wibox.container.margin,
+ },
+ border_width = 3,
+ border_color = "transparent",
+ bg = beautiful.bg_normal,
+ widget = wibox.container.background
+ },
+ opacity = 0.5,
+ widget = wibox.container.margin,
+ }
+end
+
+--DOC_HIDE_END
+ local w = wibox.widget {
+ -- This is the background border.
+ {
+ paddings = {
+ left = 5,
+ top = 3,
+ right = 10,
+ bottom = 10,
+ },
+ borders = 20,
+ border_image = image_path,
+ honor_borders = false,
+ widget = wibox.container.border
+ },
+ -- This border container is used top place widgets.
+ {
+ {
+ text = "Center widget",
+ valign = "center",
+ align = "center",
+ widget = wibox.widget.textbox
+ },
+ border_widgets = {
+ top_left = generic_widget(""),
+ top = generic_widget("top"),
+ top_right = generic_widget(""),
+ right = generic_widget("right"),
+ bottom_right = generic_widget(""),
+ bottom = generic_widget("bottom"),
+ bottom_left = generic_widget(""),
+ left = generic_widget("left"),
+ },
+ widget = wibox.container.border
+ },
+ forced_width = 200, --DOC_HIDE
+ forced_height = 200, --DOC_HIDE
+ layout = wibox.layout.stack
+ }
+
+--DOC_HIDE_START
+parent:add(w)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/expand_corners1.lua b/tests/examples/wibox/container/border/expand_corners1.lua
new file mode 100644
index 000000000..55ef69093
--- /dev/null
+++ b/tests/examples/wibox/container/border/expand_corners1.lua
@@ -0,0 +1,58 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+local beautiful = require( "beautiful" )
+
+parent.spacing = 50
+
+local function generic_widget(text, margins)
+ return wibox.widget {
+ {
+ {
+ {
+ id = "text",
+ align = "center",
+ valign = "center",
+ text = text,
+ widget = wibox.widget.textbox
+ },
+ margins = 10,
+ widget = wibox.container.margin,
+ },
+ border_width = 3,
+ border_color = beautiful.border_color,
+ bg = beautiful.bg_normal,
+ widget = wibox.container.background
+ },
+ margins = margins or 5,
+ widget = wibox.container.margin,
+ }
+end
+
+--DOC_HIDE_END
+
+for _, expand in ipairs { false, true } do
+ local w = wibox.widget {
+ {
+ text = "expand_corners = " .. (expand and "true" or "false"),
+ valign = "center",
+ align = "center",
+ widget = wibox.widget.textbox
+ },
+ border_widgets = {
+ top_left = generic_widget("top_left"),
+ top = generic_widget("top"),
+ top_right = generic_widget("top_right"),
+ right = generic_widget("right"),
+ bottom_right = generic_widget("bottom_right"),
+ bottom = generic_widget("bottom"),
+ bottom_left = generic_widget("bottom_left"),
+ left = generic_widget("left"),
+ },
+ expand_corners = expand,
+ widget = wibox.container.border
+ }
+ parent:add(wibox.container.place(w)) --DOC_HIDE
+end
+
+----DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/fill1.lua b/tests/examples/wibox/container/border/fill1.lua
new file mode 100644
index 000000000..c13804184
--- /dev/null
+++ b/tests/examples/wibox/container/border/fill1.lua
@@ -0,0 +1,75 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+
+-- luacheck: push no max string line length
+
+local image_path = [[
+
+
+]]
+
+--luacheck: pop
+
+local l = wibox.layout {
+ forced_width = 240,
+ spacing = 5,
+ layout = wibox.layout.fixed.vertical
+}
+
+l:add(wibox.widget {
+ markup = "Original image:",
+ widget = wibox.widget.textbox,
+})
+
+l:add(wibox.widget {
+ image = image_path,
+ forced_height = 48,
+ forced_width = 48,
+ widget = wibox.widget.imagebox,
+})
+
+for _, fill in ipairs {true, false} do
+ --DOC_HIDE_END
+ local w = wibox.widget {
+ {
+ text = "Central widget",
+ valign = "center",
+ align = "center",
+ forced_height = 30,
+ forced_width = 30,
+ widget = wibox.widget.textbox
+ },
+ borders = 10,
+ border_image = image_path,
+ fill = fill,
+ widget = wibox.container.border
+ }
+
+ --DOC_HIDE_START
+ l:add(wibox.widget {
+ {
+ markup = "`fill` = "..(fill and "true" or "false").."",
+ widget = wibox.widget.textbox,
+ },
+ w,
+ layout = wibox.layout.fixed.vertical,
+ })
+end
+
+parent:add(l)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/filling_fit_policy1.lua b/tests/examples/wibox/container/border/filling_fit_policy1.lua
new file mode 100644
index 000000000..a20da39f3
--- /dev/null
+++ b/tests/examples/wibox/container/border/filling_fit_policy1.lua
@@ -0,0 +1,117 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+
+-- luacheck: push no max line length
+local image_path1 = [[
+
+
+]]
+
+local image_path2 = [[
+
+
+]]
+
+--luacheck: pop
+
+local l = wibox.layout {
+ forced_width = 440,
+ spacing = 5,
+ forced_num_cols = 2,
+ homogeneous = false,
+ expand = false,
+ layout = wibox.layout.grid.vertical
+}
+
+l:add(wibox.widget {
+ markup = "Original image:",
+ widget = wibox.widget.textbox,
+})
+
+l:add_widget_at(wibox.widget {
+ image = image_path1,
+ forced_height = 48,
+ forced_width = 200,
+ halign = "center",
+ widget = wibox.widget.imagebox,
+}, 2, 1, 1, 1)
+
+l:add_widget_at(wibox.widget {
+ image = image_path2,
+ forced_height = 48,
+ forced_width = 200,
+ halign = "center",
+ widget = wibox.widget.imagebox,
+}, 2, 2, 1, 1)
+
+--DOC_HIDE_END
+for k, mode in ipairs {"fit", "repeat", "reflect", "pad"} do
+ --DOC_HIDE_START
+ local r = 1 + k*2
+
+ l:add_widget_at(wibox.widget {
+ markup = "filling_fit_policy = \"".. mode .."\"",
+ widget = wibox.widget.textbox,
+ }, r, 1, 1, 2)
+ --DOC_HIDE_END
+
+
+ for idx, image in ipairs { image_path1, image_path2 } do
+ local w = wibox.widget {
+ {
+ text = "Central widget",
+ valign = "center",
+ align = "center",
+ forced_height = 50, --DOC_HIDE
+ widget = wibox.widget.textbox
+ },
+ fill = true,
+ borders = idx == 1 and 10 or 30,
+ border_image = image,
+ filling_fit_policy = mode,
+ forced_width = 200, --DOC_HIDE
+ widget = wibox.container.border
+ }
+
+ l:add_widget_at(w, r+1, idx, 1, 1) --DOC_HIDE
+ end
+ --DOC_HIDE_END
+end
+--DOC_HIDE_START
+
+parent:add(l)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/honor_borders1.lua b/tests/examples/wibox/container/border/honor_borders1.lua
new file mode 100644
index 000000000..24a959cee
--- /dev/null
+++ b/tests/examples/wibox/container/border/honor_borders1.lua
@@ -0,0 +1,121 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+local color = require("gears.color")
+local cairo = require("lgi").cairo
+
+-- luacheck: push no max string line length
+
+local image_path = [[
+
+
+]]
+
+--luacheck: pop
+
+local function sur_to_pat(img2)
+ local pat = cairo.Pattern.create_for_surface(img2)
+ pat:set_extend(cairo.Extend.REPEAT)
+ return pat
+end
+
+-- Imported for elv13/blind/pattern.lua
+local function stripe_pat(col, angle, line_width, spacing)
+ col = color(col)
+ angle = angle or math.pi/4
+ line_width = line_width or 2
+ spacing = spacing or 2
+
+ local hy = line_width + 2*spacing
+
+ -- Get the necessary width and height so the line repeat itself correctly
+ local a, o = math.cos(angle)*hy, math.sin(angle)*hy
+
+ local w, h = math.ceil(a + (line_width - 1)), math.ceil(o + (line_width - 1))
+
+ -- Create the pattern
+ local img2 = cairo.SvgSurface.create(nil, w, h)
+ local cr2 = cairo.Context(img2)
+ cr2:set_antialias(cairo.ANTIALIAS_NONE)
+
+ -- Avoid artefacts caused by anti-aliasing
+ local offset = line_width
+
+ -- Setup
+ cr2:set_source(color(col))
+ cr2:set_line_width(line_width)
+
+ -- The central line
+ cr2:move_to(-offset, -offset)
+ cr2:line_to(w+offset, h+offset)
+ cr2:stroke()
+
+ -- Top right
+ cr2:move_to(-offset + w - spacing/2+line_width, -offset)
+ cr2:line_to(2*w+offset - spacing/2+line_width, h+offset)
+ cr2:stroke()
+
+ -- Bottom left
+ cr2:move_to(-offset + spacing/2-line_width, -offset + h)
+ cr2:line_to(w+offset + spacing/2-line_width, 2*h+offset)
+ cr2:stroke()
+
+ return sur_to_pat(img2)
+end
+
+local stripe_pattern = stripe_pat("#ff0000")
+
+local l = wibox.layout {
+ forced_width = 240,
+ spacing = 5,
+ layout = wibox.layout.fixed.vertical
+}
+
+for _, honor in ipairs {true, false} do
+ --DOC_HIDE_END
+ local w = wibox.widget {
+ {
+ {
+ markup = "Central widget",
+ valign = "center",
+ align = "center",
+ forced_height = 30,
+ forced_width = 30,
+ widget = wibox.widget.textbox
+ },
+ bg = stripe_pattern,
+ widget = wibox.container.background
+ },
+ borders = 10,
+ border_image = image_path,
+ honor_borders = honor,
+ widget = wibox.container.border
+ }
+
+ --DOC_HIDE_START
+ l:add(wibox.widget {
+ {
+ markup = "honor_borders = "..(honor and "true" or "false").."",
+ widget = wibox.widget.textbox,
+ },
+ w,
+ layout = wibox.layout.fixed.vertical,
+ })
+end
+
+parent:add(l)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/ontop1.lua b/tests/examples/wibox/container/border/ontop1.lua
new file mode 100644
index 000000000..0b5cf4ce1
--- /dev/null
+++ b/tests/examples/wibox/container/border/ontop1.lua
@@ -0,0 +1,132 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+local color = require("gears.color")
+local beautiful = require( "beautiful" )
+local cairo = require("lgi").cairo
+
+-- luacheck: push no max string line length
+local image_path = [[
+
+
+]]
+--luacheck: pop
+
+local function sur_to_pat(img2)
+ local pat = cairo.Pattern.create_for_surface(img2)
+ pat:set_extend(cairo.Extend.REPEAT)
+ return pat
+end
+
+-- Imported for elv13/blind/pattern.lua
+local function stripe_pat(col, angle, line_width, spacing)
+ col = color(col)
+ angle = angle or math.pi/4
+ line_width = line_width or 2
+ spacing = spacing or 2
+
+ local hy = line_width + 2*spacing
+
+ -- Get the necessary width and height so the line repeat itself correctly
+ local a, o = math.cos(angle)*hy, math.sin(angle)*hy
+
+ local w, h = math.ceil(a + (line_width - 1)), math.ceil(o + (line_width - 1))
+
+ -- Create the pattern
+ local img2 = cairo.SvgSurface.create(nil, w, h)
+ local cr2 = cairo.Context(img2)
+ cr2:set_antialias(cairo.ANTIALIAS_NONE)
+
+ -- Avoid artefacts caused by anti-aliasing
+ local offset = line_width
+
+ -- Setup
+ cr2:set_source(color(col))
+ cr2:set_line_width(line_width)
+
+ -- The central line
+ cr2:move_to(-offset, -offset)
+ cr2:line_to(w+offset, h+offset)
+ cr2:stroke()
+
+ -- Top right
+ cr2:move_to(-offset + w - spacing/2+line_width, -offset)
+ cr2:line_to(2*w+offset - spacing/2+line_width, h+offset)
+ cr2:stroke()
+
+ -- Bottom left
+ cr2:move_to(-offset + spacing/2-line_width, -offset + h)
+ cr2:line_to(w+offset + spacing/2-line_width, 2*h+offset)
+ cr2:stroke()
+
+ return sur_to_pat(img2)
+end
+
+local stripe_pattern = stripe_pat(beautiful.bg_normal)
+
+local l = wibox.layout {
+ forced_width = 240,
+ spacing = 5,
+ layout = wibox.layout.fixed.vertical
+}
+
+for _, ontop in ipairs {true, false} do
+ --DOC_HIDE_END
+ local w = wibox.widget {
+ {
+ {
+ markup = "Central widget",
+ valign = "center",
+ align = "center",
+ forced_height = 30,
+ forced_width = 30,
+ widget = wibox.widget.textbox
+ },
+ bg = stripe_pattern,
+ widget = wibox.container.background
+ },
+ paddings = {
+ left = 5,
+ top = 3,
+ right = 10,
+ bottom = 10,
+ },
+ borders = 20,
+ border_image = image_path,
+ ontop = ontop,
+ honor_borders = false,
+ widget = wibox.container.border
+ }
+
+ --DOC_HIDE_START
+ l:add(wibox.widget {
+ {
+ markup = "ontop = "..(ontop and "true" or "false").."",
+ widget = wibox.widget.textbox,
+ },
+ w,
+ layout = wibox.layout.fixed.vertical,
+ })
+end
+
+parent:add(l)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/paddings1.lua b/tests/examples/wibox/container/border/paddings1.lua
new file mode 100644
index 000000000..477707e70
--- /dev/null
+++ b/tests/examples/wibox/container/border/paddings1.lua
@@ -0,0 +1,142 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+local color = require("gears.color")
+local cairo = require("lgi").cairo
+
+-- luacheck: push no max string line length
+
+local image_path = [[
+
+
+]]
+--luacheck: pop
+
+local l = wibox.layout {
+ forced_width = 260,
+ spacing = 5,
+ layout = wibox.layout.fixed.vertical
+}
+
+local function sur_to_pat(img2)
+ local pat = cairo.Pattern.create_for_surface(img2)
+ pat:set_extend(cairo.Extend.REPEAT)
+ return pat
+end
+
+-- Imported for elv13/blind/pattern.lua
+local function stripe_pat(col, angle, line_width, spacing)
+ col = color(col)
+ angle = angle or math.pi/4
+ line_width = line_width or 2
+ spacing = spacing or 2
+
+ local hy = line_width + 2*spacing
+
+ -- Get the necessary width and height so the line repeat itself correctly
+ local a, o = math.cos(angle)*hy, math.sin(angle)*hy
+
+ local w, h = math.ceil(a + (line_width - 1)), math.ceil(o + (line_width - 1))
+
+ -- Create the pattern
+ local img2 = cairo.SvgSurface.create(nil, w, h)
+ local cr2 = cairo.Context(img2)
+ cr2:set_antialias(cairo.ANTIALIAS_NONE)
+
+ -- Avoid artefacts caused by anti-aliasing
+ local offset = line_width
+
+ -- Setup
+ cr2:set_source(color(col))
+ cr2:set_line_width(line_width)
+
+ -- The central line
+ cr2:move_to(-offset, -offset)
+ cr2:line_to(w+offset, h+offset)
+ cr2:stroke()
+
+ -- Top right
+ cr2:move_to(-offset + w - spacing/2+line_width, -offset)
+ cr2:line_to(2*w+offset - spacing/2+line_width, h+offset)
+ cr2:stroke()
+
+ -- Bottom left
+ cr2:move_to(-offset + spacing/2-line_width, -offset + h)
+ cr2:line_to(w+offset + spacing/2-line_width, 2*h+offset)
+ cr2:stroke()
+
+ return sur_to_pat(img2)
+end
+
+local stripe_pattern = stripe_pat("#ff0000")
+
+
+--DOC_HIDE_END
+local paddings = {
+ 0,
+ 5,
+ 10,
+ {
+ left = 5,
+ top = 5,
+ bottom = 10,
+ right = 10,
+ }
+}
+
+--DOC_NEWLINE
+
+for _, padding in ipairs(paddings) do
+ local w = wibox.widget {
+ {
+ {
+ markup = "Central widget",
+ valign = "center",
+ align = "center",
+ forced_height = 30,
+ forced_width = 30,
+ widget = wibox.widget.textbox
+ },
+ bg = stripe_pattern,
+ widget = wibox.container.background
+ },
+
+ borders = 10,
+ paddings = padding,
+ border_image = image_path,
+ widget = wibox.container.border
+ }
+
+ --DOC_HIDE_START
+
+ if type(padding) == "table" then
+ padding = "{left=5, top=5, bottom=10, right=10}"
+ end
+
+ l:add(wibox.widget {
+ {
+ markup = "paddings = "..padding.."",
+ widget = wibox.widget.textbox,
+ },
+ w,
+ layout = wibox.layout.fixed.vertical,
+ })
+ --DOC_HIDE_END
+end
+
+parent:add(l) --DOC_HIDE
+
+--DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/sides_fit_policy1.lua b/tests/examples/wibox/container/border/sides_fit_policy1.lua
new file mode 100644
index 000000000..51b976c84
--- /dev/null
+++ b/tests/examples/wibox/container/border/sides_fit_policy1.lua
@@ -0,0 +1,157 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+
+-- luacheck: push no max string line length
+
+local image_path1 = [[
+
+
+]]
+
+local image_path2 = [[
+
+
+]]
+
+local image_path3 = [[
+
+
+]]
+
+--luacheck: pop
+
+local l = wibox.layout {
+ forced_width = 640,
+ spacing = 5,
+ forced_num_cols = 2,
+ homogeneous = false,
+ expand = false,
+ layout = wibox.layout.grid.vertical
+}
+
+l:add(wibox.widget {
+ markup = "Original image:",
+ widget = wibox.widget.textbox,
+})
+
+for idx, original in ipairs {image_path1, image_path2, image_path3 } do
+ l:add_widget_at(wibox.widget {
+ image = original,
+ forced_height = 64,
+ forced_width = 200,
+ halign = "center",
+ widget = wibox.widget.imagebox,
+ }, 2, idx, 1, 1)
+end
+
+--DOC_HIDE_END
+
+local images = {
+ {
+ path = image_path1,
+ borders = 10,
+ },
+ {
+ path = image_path2,
+ borders = 30,
+ },
+ {
+ path = image_path3,
+ borders = {
+ top = 20,
+ left = 20,
+ bottom = 20,
+ right = 20,
+ },
+ },
+}
+
+--DOC_NEWLINE
+
+for k, mode in ipairs {"fit", "repeat", "reflect", "pad"} do
+ --DOC_HIDE_START
+ local r = 1 + k*2
+
+ l:add_widget_at(wibox.widget {
+ markup = "sides_fit_policy = \"".. mode .."\"",
+ widget = wibox.widget.textbox,
+ }, r, 1, 1, 2)
+ --DOC_HIDE_END
+
+
+ for idx, image in ipairs(images) do
+ local w = wibox.widget {
+ {
+ text = "Central widget",
+ valign = "center",
+ align = "center",
+ widget = wibox.widget.textbox
+ },
+ fill = false,
+ borders = image.borders,
+ border_image = image.path,
+ sides_fit_policy = mode,
+ forced_width = 200, --DOC_HIDE
+ widget = wibox.container.border
+ }
+
+ l:add_widget_at(w, r+1, idx, 1, 1) --DOC_HIDE
+ end
+ --DOC_HIDE_END
+end
+--DOC_HIDE_START
+
+parent:add(l)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/slice1.lua b/tests/examples/wibox/container/border/slice1.lua
new file mode 100644
index 000000000..4ddbf6b97
--- /dev/null
+++ b/tests/examples/wibox/container/border/slice1.lua
@@ -0,0 +1,76 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+
+-- luacheck: push no max string line length
+
+local image_path = [[
+
+
+]]
+
+--luacheck: pop
+
+local l = wibox.layout {
+ forced_width = 240,
+ spacing = 5,
+ layout = wibox.layout.fixed.vertical
+}
+
+l:add(wibox.widget {
+ markup = "Original image:",
+ widget = wibox.widget.textbox,
+})
+
+l:add(wibox.widget {
+ image = image_path,
+ forced_height = 48,
+ forced_width = 48,
+ widget = wibox.widget.imagebox,
+})
+
+for _, i in ipairs {true, false} do
+ --DOC_HIDE_END
+ local w = wibox.widget {
+ {
+ text = "Central widget",
+ valign = "center",
+ align = "center",
+ forced_height = 30,
+ forced_width = 30,
+ widget = wibox.widget.textbox
+ },
+ fill = true,
+ borders = 10,
+ border_image = image_path,
+ slice = i,
+ widget = wibox.container.border
+ }
+
+ --DOC_HIDE_START
+ l:add(wibox.widget {
+ {
+ markup = "`slice` = "..(i and "true" or "false").."",
+ widget = wibox.widget.textbox,
+ },
+ w,
+ layout = wibox.layout.fixed.vertical,
+ })
+end
+
+parent:add(l)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/border/stylesheet1.lua b/tests/examples/wibox/container/border/stylesheet1.lua
new file mode 100644
index 000000000..c1e792a00
--- /dev/null
+++ b/tests/examples/wibox/container/border/stylesheet1.lua
@@ -0,0 +1,51 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START
+local parent = ...
+local wibox = require("wibox")
+
+--luacheck: push no max line length
+
+--DOC_HIDE_END
+
+local image_path = ''..
+ ''
+
+--DOC_NEWLINE
+
+local style = ""..
+ "#first {stop-color: magenta;}" ..
+ "#second {stop-color: cyan;}" ..
+ "#third {stop-color: yellow;}"
+
+--DOC_NEWLINE
+
+local w = wibox.widget {
+ {
+ text = "Center widget",
+ valign = "center",
+ align = "center",
+ widget = wibox.widget.textbox
+ },
+ borders = 50,
+ border_image_stylesheet = style,
+ border_image = image_path,
+ honor_borders = false,
+ forced_width = 100, --DOC_HIDE
+ forced_height = 100, --DOC_HIDE
+ widget = wibox.container.border
+}
+
+--DOC_HIDE_START
+
+--luacheck: pop
+
+parent:add(w)
diff --git a/tests/examples/wibox/container/border/titlebar1.lua b/tests/examples/wibox/container/border/titlebar1.lua
new file mode 100644
index 000000000..c50e7bc90
--- /dev/null
+++ b/tests/examples/wibox/container/border/titlebar1.lua
@@ -0,0 +1,172 @@
+--DOC_GEN_IMAGE --DOC_HIDE_START --DOC_NO_USAGE
+local parent = ...
+local wibox = require("wibox")
+local gears = { shape = require("gears.shape")}
+local beautiful = require( "beautiful" )
+
+-- luacheck: push no max line length
+local bg = [[
+
+
+]]
+
+local btn = [[
+
+
+]]
+
+--luacheck: pop
+
+local function generic_widget(text, margins)
+ return wibox.widget {
+ {
+ {
+ {
+ id = "text",
+ align = "center",
+ valign = "center",
+ text = text,
+ widget = wibox.widget.textbox
+ },
+ margins = 10,
+ widget = wibox.container.margin,
+ },
+ border_width = 3,
+ border_color = beautiful.border_color,
+ bg = beautiful.bg_normal,
+ widget = wibox.container.background
+ },
+ margins = margins or 5,
+ widget = wibox.container.margin,
+ }
+end
+
+--DOC_HIDE_END
+
+ local w = wibox.widget {
+ {
+ {
+ text = "Content",
+ align = "center",
+ valign = "center",
+ widget = wibox.widget.textbox
+ },
+ border_widgets = {
+ top = {
+ {
+ {
+ {
+ stylesheet = "#bg1 {stop-color:#ca2b2b;} #bg2 {stop-color:#f8b9b9;}",
+ image = btn,
+ widget = wibox.widget.imagebox
+ },
+ {
+ stylesheet = "#bg1 {stop-color:#ec9527;} #bg2 {stop-color:#ffff9c;}",
+ image = btn,
+ widget = wibox.widget.imagebox
+ },
+ {
+ stylesheet = "#bg1 {stop-color:#75b525;} #bg2 {stop-color:#e0fda9;}",
+ image = btn,
+ widget = wibox.widget.imagebox
+ },
+ spacing = 3,
+ layout = wibox.layout.fixed.horizontal
+ },
+ {
+ align = "center",
+ text = "Shameless macOS ripoff",
+ widget = wibox.widget.textbox
+ },
+ layout = wibox.layout.align.horizontal
+ },
+ paddings = 6,
+ borders = 14,
+ border_image = bg,
+ honor_borders = false,
+ fill = true,
+ forced_height = 28, --DOC_HIDE
+ widget = wibox.container.border
+ },
+ right = generic_widget(""),
+ bottom_right = generic_widget(""),
+ bottom = generic_widget(""),
+ bottom_left = generic_widget(""),
+ left = generic_widget(""),
+ },
+ borders = {
+ top = 28,
+ left = 22,
+ right = 22,
+ bottom = 22,
+ },
+ border_merging = {
+ top = true
+ },
+ forced_width = 300, --DOC_HIDE
+ forced_height = 100, --DOC_HIDE
+ widget = wibox.container.border
+ },
+ bg = "#d9d9d9",
+ shape = gears.shape.rounded_rect,
+ widget = wibox.container.background
+ }
+
+--DOC_HIDE_START
+parent:add(w)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/container/defaults/border.lua b/tests/examples/wibox/container/defaults/border.lua
new file mode 100644
index 000000000..40c337612
--- /dev/null
+++ b/tests/examples/wibox/container/defaults/border.lua
@@ -0,0 +1,47 @@
+--DOC_HIDE_ALL
+--DOC_GEN_IMAGE
+local wibox = require("wibox")
+
+-- luacheck: push no max string line length
+local data = [[
+
+
+]]
+--luacheck: pop
+
+return {
+ text = "Before",
+ align = "center",
+ valign = "center",
+ widget = wibox.widget.textbox,
+},
+{
+ {
+ {
+ text = "After",
+ align = "center",
+ valign = "center",
+ widget = wibox.widget.textbox,
+ },
+ border_image = data,
+ slice = true,
+ fill = true,
+ borders = 10,
+ widget = wibox.container.border
+ },
+ margins = 5,
+ layout = wibox.container.margin
+}
diff --git a/tests/examples/wibox/widget/imagebox/horizontal_fit_policy.lua b/tests/examples/wibox/widget/imagebox/horizontal_fit_policy.lua
index 6bfcbc326..d069b49b8 100644
--- a/tests/examples/wibox/widget/imagebox/horizontal_fit_policy.lua
+++ b/tests/examples/wibox/widget/imagebox/horizontal_fit_policy.lua
@@ -24,7 +24,14 @@ local function demo()
cr:set_source_rgb(0,1,0)
cr:arc(11, 16, 8, 0, 2*math.pi)
- cr:fill()
+ cr:fill_preserve()
+
+ cr:clip()
+
+ cr:move_to(0 ,0 )
+ cr:line_to(22,32)
+ cr:set_source_rgb(1,1,0)
+ cr:stroke()
return img
end
@@ -69,13 +76,19 @@ parent:add(l)
l:add_widget_at(wibox.widget.textbox('horizontal_fit_policy = "auto"'), 1, 1)
l:add_widget_at(wibox.widget.textbox('horizontal_fit_policy = "none"'), 2, 1)
l:add_widget_at(wibox.widget.textbox('horizontal_fit_policy = "fit"'), 3, 1)
+l:add_widget_at(wibox.widget.textbox('horizontal_fit_policy = "repeat"'), 4, 1)
+l:add_widget_at(wibox.widget.textbox('horizontal_fit_policy = "reflect"'), 5, 1)
+l:add_widget_at(wibox.widget.textbox('horizontal_fit_policy = "pad"'), 6, 1)
l:add_widget_at(wibox.widget.textbox('imagebox size'), 4, 1)
for i,size in ipairs({16, 32, 64}) do
- l:add_widget_at(build_ib(size, "auto"), 1, i + 1)
- l:add_widget_at(build_ib(size, "none"), 2, i + 1)
- l:add_widget_at(build_ib(size, "fit" ), 3, i + 1)
- l:add_widget_at(cell_centered_widget(wibox.widget.textbox(size..'x'..size)), 4, i + 1)
+ l:add_widget_at(build_ib(size, "auto" ), 1, i + 1)
+ l:add_widget_at(build_ib(size, "none" ), 2, i + 1)
+ l:add_widget_at(build_ib(size, "fit" ), 3, i + 1)
+ l:add_widget_at(build_ib(size, "repeat" ), 4, i + 1)
+ l:add_widget_at(build_ib(size, "reflect" ), 5, i + 1)
+ l:add_widget_at(build_ib(size, "pad" ), 6, i + 1)
+ l:add_widget_at(cell_centered_widget(wibox.widget.textbox(size..'x'..size)), 7, i + 1)
end
--DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/tests/examples/wibox/widget/imagebox/vertical_fit_policy.lua b/tests/examples/wibox/widget/imagebox/vertical_fit_policy.lua
index 24744f960..29fb8363b 100644
--- a/tests/examples/wibox/widget/imagebox/vertical_fit_policy.lua
+++ b/tests/examples/wibox/widget/imagebox/vertical_fit_policy.lua
@@ -24,7 +24,14 @@ local function demo()
cr:set_source_rgb(0,1,0)
cr:arc(16, 11, 8, 0, 2*math.pi)
- cr:fill()
+ cr:fill_preserve()
+
+ cr:clip()
+
+ cr:move_to(0 ,0 )
+ cr:line_to(32,22)
+ cr:set_source_rgb(1,1,0)
+ cr:stroke()
return img
end
@@ -66,16 +73,22 @@ local l = wibox.widget {
}
parent:add(l)
-l:add_widget_at(wibox.widget.textbox('vertical_fit_policy = "auto"'), 1, 1)
-l:add_widget_at(wibox.widget.textbox('versical_fit_policy = "none"'), 2, 1)
-l:add_widget_at(wibox.widget.textbox('vertical_fit_policy = "fit"'), 3, 1)
+l:add_widget_at(wibox.widget.textbox('vertical_fit_policy = "auto"' ), 1, 1)
+l:add_widget_at(wibox.widget.textbox('versical_fit_policy = "none"' ), 2, 1)
+l:add_widget_at(wibox.widget.textbox('vertical_fit_policy = "fit"' ), 3, 1)
+l:add_widget_at(wibox.widget.textbox('vertical_fit_policy = "repeat"' ), 4, 1)
+l:add_widget_at(wibox.widget.textbox('vertical_fit_policy = "reflect"'), 5, 1)
+l:add_widget_at(wibox.widget.textbox('vertical_fit_policy = "pad"' ), 6, 1)
l:add_widget_at(wibox.widget.textbox('imagebox size'), 4, 1)
for i,size in ipairs({16, 32, 64}) do
- l:add_widget_at(build_ib(size, "auto"), 1, i + 1)
- l:add_widget_at(build_ib(size, "none"), 2, i + 1)
- l:add_widget_at(build_ib(size, "fit" ), 3, i + 1)
- l:add_widget_at(cell_centered_widget(wibox.widget.textbox(size..'x'..size)), 4, i + 1)
+ l:add_widget_at(build_ib(size, "auto" ), 1, i + 1)
+ l:add_widget_at(build_ib(size, "none" ), 2, i + 1)
+ l:add_widget_at(build_ib(size, "fit" ), 3, i + 1)
+ l:add_widget_at(build_ib(size, "repeat" ), 4, i + 1)
+ l:add_widget_at(build_ib(size, "reflect" ), 5, i + 1)
+ l:add_widget_at(build_ib(size, "pad" ), 6, i + 1)
+ l:add_widget_at(cell_centered_widget(wibox.widget.textbox(size..'x'..size)), 7, i + 1)
end
--DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80