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