From ecc20dd0b850fc3311cde5eb34dc5126f90077a3 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 26 Dec 2021 22:46:34 -0800 Subject: [PATCH 01/11] imagebox: Add 3 new fit policies. This will be used soon by a nice CSS inspired image slicer container/layout. They have a limitation inherited from Cairo of only working on a single axis. This isn't important for 99% of the use case. --- lib/wibox/widget/imagebox.lua | 46 +++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/lib/wibox/widget/imagebox.lua b/lib/wibox/widget/imagebox.lua index 79b6f710b..efd7b010a 100644 --- a/lib/wibox/widget/imagebox.lua +++ b/lib/wibox/widget/imagebox.lua @@ -37,6 +37,12 @@ local math = math local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1) +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 @@ -182,6 +188,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 +200,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 +264,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 @@ -510,6 +527,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 +539,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 +562,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 From 47855e41646fa03f092716776eb7ab03fb8fcb6f Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 26 Dec 2021 22:48:10 -0800 Subject: [PATCH 02/11] tests: Test the 3 new imagebox extents. --- .../widget/imagebox/horizontal_fit_policy.lua | 23 +++++++++++---- .../widget/imagebox/vertical_fit_policy.lua | 29 ++++++++++++++----- 2 files changed, 39 insertions(+), 13 deletions(-) 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 From 5c9ffb8ef30e364730475c1484e3c5b20a512651 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Tue, 28 Dec 2021 14:08:37 -0800 Subject: [PATCH 03/11] imagebox: Expose the private SVG handling function. To be used by `wibox.container.border`, which is a specialized imagebox. --- lib/wibox/widget/imagebox.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/wibox/widget/imagebox.lua b/lib/wibox/widget/imagebox.lua index efd7b010a..631c32a56 100644 --- a/lib/wibox/widget/imagebox.lua +++ b/lib/wibox/widget/imagebox.lua @@ -56,11 +56,11 @@ local imagebox = { mt = {} } local rsvg_handle_cache = setmetatable({}, { __mode = 'k' }) ----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) if not Rsvg then return end local cache = (rsvg_handle_cache[file] or {})["handle"] @@ -343,7 +343,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 From ab121e9ac06f8e96cf545ab6f1af7bb8e611f447 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Mon, 3 Jan 2022 22:29:33 -0800 Subject: [PATCH 04/11] imagebox: Support a CSS file. Before, the CSS had to be inline. Now that the border container uses this code, it makes sense to make it more flexible. --- lib/wibox/widget/imagebox.lua | 95 +++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 11 deletions(-) diff --git a/lib/wibox/widget/imagebox.lua b/lib/wibox/widget/imagebox.lua index 631c32a56..76ca28604 100644 --- a/lib/wibox/widget/imagebox.lua +++ b/lib/wibox/widget/imagebox.lua @@ -31,12 +31,17 @@ 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, @@ -55,18 +60,25 @@ end local imagebox = { mt = {} } local rsvg_handle_cache = setmetatable({}, { __mode = 'k' }) +local stylesheet_cache = {} --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. -function imagebox._load_rsvg_handle(file) +function imagebox._load_rsvg_handle(file, style) + -- Make sure this is called in the right order. + assert((not style) or (style and stylesheet_cache[style])) + + local style_ref = style and stylesheet_cache[style] or empty_stylesheet + if not Rsvg then return end - 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 @@ -78,9 +90,10 @@ function imagebox._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 @@ -117,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) @@ -125,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 @@ -443,8 +488,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@ -- @@ -483,18 +527,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 From e23940cc6a1c685530ec64030e03642c7b6a193f Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Wed, 5 Jan 2022 11:40:28 -0800 Subject: [PATCH 05/11] imagebox: Expose the "optimal size" properties. The `textbox` already has the equivalent. This will soon be used by the new `wibox.container.border` widget. --- lib/wibox/widget/imagebox.lua | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lib/wibox/widget/imagebox.lua b/lib/wibox/widget/imagebox.lua index 76ca28604..bed1887fd 100644 --- a/lib/wibox/widget/imagebox.lua +++ b/lib/wibox/widget/imagebox.lua @@ -362,6 +362,28 @@ 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 +-- @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 +-- @see image +-- @see source_width + --- Set the `imagebox` image. -- -- The image can be a file, a cairo image surface, or an rsvg handle object @@ -417,6 +439,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 From dc207d5b491442747720b9682332895c1e662516 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 5 Feb 2022 15:04:50 -0800 Subject: [PATCH 06/11] container: Add a broder container. Rather than bloat the `background` container with another 1k lines, a new container is added. The goal is to implement CSS-style slicing for the background images. The container also allows to place generic widgets on each sides and corners of the container. The main use case for this container is to replace the old `awful.titlebar` API with `awful.decoration`. That new module will allow a central client widget to be surrounded by widgets rather than have 4 separate drawing areas. The border container is designed to make complex border+titlebar setup trivial. --- lib/wibox/container/border.lua | 1050 ++++++++++++++++++++++++++++++++ lib/wibox/container/init.lua | 1 + 2 files changed, 1051 insertions(+) create mode 100644 lib/wibox/container/border.lua diff --git a/lib/wibox/container/border.lua b/lib/wibox/container/border.lua new file mode 100644 index 000000000..b9ee6b5fe --- /dev/null +++ b/lib/wibox/container/border.lua @@ -0,0 +1,1050 @@ +--------------------------------------------------------------------------- +-- 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, +-- 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 (self._private.border_widgets or self._private.border_image_widgets) == nil +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 widget 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 +-- @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 string|gears.surface 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 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. +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +-- +--
ValueDescription
fastA high-performance filter
goodA reasonable-performance filter
bestThe highest-quality available
nearestNearest-neighbor filtering (blocky)
bilinearLinear interpolation in two dimensions
+-- +-- +-- +-- @property image_scaling_quality +-- @tparam[opt="nearest"] string image_scaling_quality + +--- 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|gears.surface|nil border_images +-- @tparam[opt=nil] string|gears.surface border_images.top_left +-- @tparam[opt=nil] string|gears.surface border_images.top +-- @tparam[opt=nil] string|gears.surface border_images.top_right +-- @tparam[opt=nil] string|gears.surface border_images.right +-- @tparam[opt=nil] string|gears.surface border_images.bottom_right +-- @tparam[opt=nil] string|gears.surface border_images.bottom +-- @tparam[opt=nil] string|gears.surface border_images.bottom_left +-- @tparam[opt=nil] string|gears.surface 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 + +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. +-- +-- * "fit" (default) +-- * "repeat" +-- * "reflect" +-- * "pad" +-- +-- 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 +-- @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. +-- +-- * "fit" (default) +-- * "repeat" +-- * "reflect" +-- * "pad" +-- +-- @DOC_wibox_container_border_filling_fit_policy1_EXAMPLE@ +-- +-- @property filling_fit_policy +-- @tparam[opt="fit"] string filling_fit_policy +-- @propemits true false +-- @see fill + +--- How the sliced image is resized for the border corners. +-- +-- * "fit" (default) +-- * "repeat" +-- * "reflect" +-- * "pad" +-- +-- @DOC_wibox_container_border_corners_fit_policy1_EXAMPLE@ +-- +-- @property corners_fit_policy +-- @tparam[opt="fit"] string corners_fit_policy +-- @propemits true false + +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 +-- @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 From 57b766ccf94a9087f6c834562211d758c32760f7 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 5 Feb 2022 15:10:31 -0800 Subject: [PATCH 07/11] tests: Test the border container. --- .../wibox/container/border/border_images1.lua | 59 ++++++ .../container/border/border_merging1.lua | 73 ++++++++ .../container/border/border_widgets1.lua | 55 ++++++ .../wibox/container/border/borders1.lua | 104 +++++++++++ .../container/border/corners_fit_policy1.lua | 122 +++++++++++++ .../wibox/container/border/custom_draw1.lua | 29 +++ .../examples/wibox/container/border/dpi1.lua | 104 +++++++++++ .../examples/wibox/container/border/dual1.lua | 90 +++++++++ .../container/border/expand_corners1.lua | 58 ++++++ .../examples/wibox/container/border/fill1.lua | 75 ++++++++ .../container/border/filling_fit_policy1.lua | 117 ++++++++++++ .../wibox/container/border/honor_borders1.lua | 121 ++++++++++++ .../wibox/container/border/ontop1.lua | 132 ++++++++++++++ .../wibox/container/border/paddings1.lua | 142 +++++++++++++++ .../container/border/sides_fit_policy1.lua | 157 ++++++++++++++++ .../wibox/container/border/slice1.lua | 76 ++++++++ .../wibox/container/border/stylesheet1.lua | 51 ++++++ .../wibox/container/border/titlebar1.lua | 172 ++++++++++++++++++ .../wibox/container/defaults/border.lua | 47 +++++ 19 files changed, 1784 insertions(+) create mode 100644 tests/examples/wibox/container/border/border_images1.lua create mode 100644 tests/examples/wibox/container/border/border_merging1.lua create mode 100644 tests/examples/wibox/container/border/border_widgets1.lua create mode 100644 tests/examples/wibox/container/border/borders1.lua create mode 100644 tests/examples/wibox/container/border/corners_fit_policy1.lua create mode 100644 tests/examples/wibox/container/border/custom_draw1.lua create mode 100644 tests/examples/wibox/container/border/dpi1.lua create mode 100644 tests/examples/wibox/container/border/dual1.lua create mode 100644 tests/examples/wibox/container/border/expand_corners1.lua create mode 100644 tests/examples/wibox/container/border/fill1.lua create mode 100644 tests/examples/wibox/container/border/filling_fit_policy1.lua create mode 100644 tests/examples/wibox/container/border/honor_borders1.lua create mode 100644 tests/examples/wibox/container/border/ontop1.lua create mode 100644 tests/examples/wibox/container/border/paddings1.lua create mode 100644 tests/examples/wibox/container/border/sides_fit_policy1.lua create mode 100644 tests/examples/wibox/container/border/slice1.lua create mode 100644 tests/examples/wibox/container/border/stylesheet1.lua create mode 100644 tests/examples/wibox/container/border/titlebar1.lua create mode 100644 tests/examples/wibox/container/defaults/border.lua 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..e9b9f1c9b --- /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 "left = 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 +} From e82de8edb4761f28ffb8e003dad76a180af4b49b Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 5 Feb 2022 15:15:57 -0800 Subject: [PATCH 08/11] background: Deprecate `bgimage`. It is now handled by the `wibox.container.border`. The current implementation had many unhandled corner case and is less flexible than the new API. Some might argue deprecating the background from a background container is... strange. This is true. However, keeing all use case within the same codebase would make it too large. The `gears.shape` support is incompatible with the border widgets (which are used to implement the background images). --- lib/wibox/container/background.lua | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/wibox/container/background.lua b/lib/wibox/container/background.lua index 8262d6a7e..2e6b5b838 100644 --- a/lib/wibox/container/background.lua +++ b/lib/wibox/container/background.lua @@ -450,10 +450,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 +466,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) From 5ee926d5f1143aee7dd22011703e72488ffd8c53 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 5 Feb 2022 15:16:21 -0800 Subject: [PATCH 09/11] background: Add a way to scale a background gradient. This is a feature request from the chat. Right now, only deprecated hacks allow this to be implemented. This is a valid use case and must be supported for HiDPI use cases. --- lib/wibox/container/background.lua | 193 ++++++++++++++++++++++++++++- 1 file changed, 187 insertions(+), 6 deletions(-) diff --git a/lib/wibox/container/background.lua b/lib/wibox/container/background.lua index 2e6b5b838..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 @@ -513,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) From 1291a346f3a684f939647ef4accb19ab3a652ce9 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 5 Feb 2022 15:18:04 -0800 Subject: [PATCH 10/11] doc: Add more `wibox.container.background` examples. --- tests/examples/shims/beautiful.lua | 18 +++- .../container/background/border_color.lua | 52 ++++++++++++ .../container/background/border_strategy.lua | 54 ++++++++++++ .../container/background/border_width.lua | 34 ++++++++ .../background/stretch_horizontally.lua | 82 ++++++++++++++++++ .../background/stretch_vertically.lua | 83 +++++++++++++++++++ 6 files changed, 319 insertions(+), 4 deletions(-) create mode 100644 tests/examples/wibox/container/background/border_color.lua create mode 100644 tests/examples/wibox/container/background/border_strategy.lua create mode 100644 tests/examples/wibox/container/background/border_width.lua create mode 100644 tests/examples/wibox/container/background/stretch_horizontally.lua create mode 100644 tests/examples/wibox/container/background/stretch_vertically.lua 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 From 2eab55e53681418eba6a87cd72f9198ea4f9bb9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Lepage=20Vall=C3=A9e?= Date: Sun, 31 Dec 2023 14:38:05 -0800 Subject: [PATCH 11/11] Update lib/wibox/container/border.lua Co-authored-by: Aire-One --- lib/wibox/container/border.lua | 71 +++++++++++-------- lib/wibox/widget/imagebox.lua | 16 +++-- .../container/border/border_merging1.lua | 2 +- 3 files changed, 51 insertions(+), 38 deletions(-) diff --git a/lib/wibox/container/border.lua b/lib/wibox/container/border.lua index b9ee6b5fe..75d2fb475 100644 --- a/lib/wibox/container/border.lua +++ b/lib/wibox/container/border.lua @@ -8,8 +8,8 @@ -- 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, --- the `background` container is a much better choice. This module is better +-- 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 @@ -23,7 +23,7 @@ -- -- 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 +-- `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. @@ -82,7 +82,7 @@ local function fit_common(widget, ctx, max_w, max_h) end local function uses_slice(self) - return (self._private.border_widgets or self._private.border_image_widgets) == nil + return not (self._private.border_widgets or self._private.border_image_widgets) end local function get_widget(self, ctx, component) @@ -577,7 +577,7 @@ end --- The widget to display inside of the border. -- -- @property widget --- @tparam widget widget +-- @tparam[opt=nil] widget|nil widget module.set_widget = base.set_widget_common @@ -595,6 +595,7 @@ 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) @@ -605,7 +606,7 @@ end -- When using this property, the `borders` also **needs** to be specified. -- -- @property border_image --- @tparam string|gears.surface border_image +-- @tparam[opt=nil] string|image|nil border_image -- @see borders -- @see border_images @@ -671,7 +672,7 @@ end -- @DOC_wibox_container_border_stylesheet1_EXAMPLE@ -- -- @property border_image_stylesheet --- @tparam string border_image_stylesheet CSS data or file path. +-- @tparam[opt=""] string border_image_stylesheet CSS data or file path. -- @see wibox.widget.imagebox.stylesheet function module:set_border_image_stylesheet(value) @@ -701,6 +702,11 @@ end -- -- @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. -- @@ -713,15 +719,15 @@ end -- @DOC_wibox_container_border_border_images1_EXAMPLE@ -- -- @property border_images --- @tparam[opt=nil] table|gears.surface|nil border_images --- @tparam[opt=nil] string|gears.surface border_images.top_left --- @tparam[opt=nil] string|gears.surface border_images.top --- @tparam[opt=nil] string|gears.surface border_images.top_right --- @tparam[opt=nil] string|gears.surface border_images.right --- @tparam[opt=nil] string|gears.surface border_images.bottom_right --- @tparam[opt=nil] string|gears.surface border_images.bottom --- @tparam[opt=nil] string|gears.surface border_images.bottom_left --- @tparam[opt=nil] string|gears.surface border_images.left +-- @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 @@ -754,6 +760,7 @@ end -- @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) @@ -773,11 +780,6 @@ end --- How the sliced image is resized for the border sides. -- --- * "fit" (default) --- * "repeat" --- * "reflect" --- * "pad" --- -- 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 @@ -787,6 +789,10 @@ end -- -- @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 @@ -796,30 +802,32 @@ end -- Also note that if `slice` is set to `false`, this will be used for -- the entire background. -- --- * "fit" (default) --- * "repeat" --- * "reflect" --- * "pad" --- -- @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. -- --- * "fit" (default) --- * "repeat" --- * "reflect" --- * "pad" --- -- @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) @@ -920,6 +928,7 @@ end -- @property paddings -- @tparam[opt=0] number|table paddings -- @propemits true false +-- @negativeallowed false -- @see wibox.container.margin function module:set_paddings(value) diff --git a/lib/wibox/widget/imagebox.lua b/lib/wibox/widget/imagebox.lua index bed1887fd..07b7bed48 100644 --- a/lib/wibox/widget/imagebox.lua +++ b/lib/wibox/widget/imagebox.lua @@ -370,6 +370,8 @@ end -- -- @property source_width -- @tparam number source_width +-- @propertydefault This depends on the source image. +-- @negativeallowed false -- @see image -- @see source_height @@ -381,6 +383,8 @@ end -- -- @property source_height -- @tparam number source_height +-- @propertydefault This depends on the source image. +-- @negativeallowed false -- @see image -- @see source_width @@ -642,9 +646,9 @@ 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. +-- @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 @@ -666,9 +670,9 @@ end -- @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. +-- @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/wibox/container/border/border_merging1.lua b/tests/examples/wibox/container/border/border_merging1.lua index e9b9f1c9b..de40772eb 100644 --- a/tests/examples/wibox/container/border/border_merging1.lua +++ b/tests/examples/wibox/container/border/border_merging1.lua @@ -47,7 +47,7 @@ for _, side in ipairs { "top", "bottom", "left", "right" } do widget = wibox.widget.textbox }, border_merging = { - -- This is the equaivalent "left = true,". "side" is the loop + -- This is the equaivalent "side = true,". "side" is the loop -- variable. [side] = true },