diff --git a/lib/wibox/container/init.lua b/lib/wibox/container/init.lua index 05ac0346c..69926fb2f 100644 --- a/lib/wibox/container/init.lua +++ b/lib/wibox/container/init.lua @@ -17,6 +17,7 @@ return setmetatable({ radialprogressbar = require("wibox.container.radialprogressbar"); arcchart = require("wibox.container.arcchart"); place = require("wibox.container.place"); + tile = require("wibox.container.tile"); }, {__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/container/tile.lua b/lib/wibox/container/tile.lua new file mode 100644 index 000000000..e2175cd4f --- /dev/null +++ b/lib/wibox/container/tile.lua @@ -0,0 +1,211 @@ +--------------------------------------------------------------------------- +-- Replicate the content of the widget over and over. +-- +-- This contained is intended to be used for wallpapers. It currently doesn't +-- support mouse input in the replicated tiles. +-- +--@DOC_wibox_container_defaults_tile_EXAMPLE@ +-- @author Emmanuel Lepage-Vallee +-- @copyright 2021 Emmanuel Lepage-Vallee +-- @containermod wibox.container.tile +-- @supermodule wibox.container.place +local place = require("wibox.container.place") +local cairo = require("lgi").cairo +local widget = require("wibox.widget") +local gtable = require("gears.table") + +local module = {mt = {}} + +function module:draw(context, cr, width, height) + if not self._private.tiled then return end + if not self._private.widget then return end + + local x, y, w, h = self:_layout(context, width, height) + + local vspace, hspace = self.vertical_spacing, self.horizontal_spacing + local vcrop, hcrop = self.vertical_crop, self.horizontal_crop + + -- In theory we could avoid a few repaints by tracking the child widget + -- redraw independently from the container redraw. However it is nearly a + -- 1:1 march, so there's little reasons to do it. + if not self._private.surface then + self._private.surface = cairo.ImageSurface(cairo.Format.ARGB32, w+hspace, h+vspace) + self._private.cr = cairo.Context(self._private.surface) + self._private.pattern = cairo.Pattern.create_for_surface(self._private.surface) + self._private.pattern.extend = cairo.Extend.REPEAT + self._private.cr:translate(math.ceil(hspace), math.ceil(vspace)) + else + self._private.cr:set_operator(cairo.Operator.CLEAR) + self._private.cr:set_source_rgba(0,0,0,1) + self._private.cr:paint() + self._private.cr:set_operator(cairo.Operator.SOURCE) + end + + widget.draw_to_cairo_context(self._private.widget, self._private.cr, w, h, context) + + cr:save() + + -- We do our own clip. + cr:reset_clip() + + local x0, y0 = 0, 0 + + -- Avoid painting incomplete tiles + if hcrop and x ~= 0 then + x0 = x - math.floor(x/(w+hspace))*(w+hspace) + end + + if hcrop then + width = x + w + hspace + math.floor((width - (x + w + hspace))/(w+hspace))*(w+hspace) + end + + if vcrop and y ~= 0 then + y0 = y - math.floor(y/(h+vspace))*(h+vspace) + end + + if vcrop then + height = (y+h+vspace) + math.floor((height - (y+h+vspace))/(h+vspace))*(h+vspace) + end + + -- Create a clip around the "real" widget in case there is some transparency. + cr:rectangle(x0, y0, width-x0, y-y0) + cr:rectangle(x0, y0, x-hspace-x0, height-y0) + cr:rectangle(x+hspace+w, y0, width - (x+w+hspace), height-y0) + cr:rectangle(x, y+vspace+h, w+hspace, height - (y+h+vspace)) + cr:clip() + + -- Make sure the tiles are aligned with the child widget. + cr:translate(x - hspace, y - vspace) + + + -- Use OVER rather than SOURCE to preserve the alpha. + cr.operator = cairo.Operator.OVER + cr.source = self._private.pattern + cr:paint() + + cr:restore() +end + +--- The horizontal spacing between the tiled. +-- +--@DOC_wibox_container_tile_horizontal_spacing_EXAMPLE@ +-- +-- @property horizontal_spacing +-- @tparam number horizontal_spacing +-- @propemits true false +-- @see vertical_spacing + +--- The vertical spacing between the tiled. +-- +--@DOC_wibox_container_tile_vertical_spacing_EXAMPLE@ +-- +-- @property vertical_spacing +-- @tparam number vertical_spacing +-- @propemits true false +-- @see horizontal_spacing + +--- Avoid painting incomplete horizontal tiles. +-- +--@DOC_wibox_container_tile_horizontal_crop_EXAMPLE@ +-- +-- @property horizontal_crop +-- @tparam[opt=false] boolean tiled +-- @see vertical_crop + +--- Avoid painting incomplete vertical tiles. +-- +--@DOC_wibox_container_tile_vertical_crop_EXAMPLE@ +-- +-- @property vertical_crop +-- @tparam[opt=false] boolean tiled +-- @see horizontal_crop + +--- Enable or disable the tiling. +-- +-- When set to `false`, this container behaves exactly like +-- `wibox.container.place`. +-- +--@DOC_wibox_container_tile_tiled_EXAMPLE@ +-- +-- @property tiled +-- @tparam[opt=true] boolean tiled + +local defaults = { + horizontal_spacing = 0, + vertical_spacing = 0, + tiled = true, + horizontal_crop = false, + vertical_crop = false, +} + +for prop in pairs(defaults) do + + module["set_"..prop] = function(self, value) + self._private[prop] = value + self:emit_signal("widget::redraw_needed", value) + end + + module["get_"..prop] = function(self) + if self._private[prop] == nil then + return defaults[prop] + end + + return self._private[prop] + end +end + +local function new(_, args) + args = args or {} + local ret = place(args.widget, args.halign, args.valign) + gtable.crush(ret, module, true) + ret._private.tiled = true + + local function redraw() + ret:emit_signal("widget::redraw_needed") + end + + -- Resize the pattern as needed. + local function reset() + if ret._private.surface then + ret._private.surface:finish() + end + + ret._private.cr = nil + ret._private.surface = nil + ret._private.pattern = nil + end + + local w = nil + + ret:connect_signal("property::widget", function() + reset() + + if w then + w:disconnect_signal("widget::redraw_needed", redraw) + w:disconnect_signal("widget::layout_changed", reset) + end + + w = ret._private.widget + + if w then + w:connect_signal("widget::redraw_needed", redraw) + w:connect_signal("widget::layout_changed", reset) + end + end) + + return ret +end + +--- Create a new tile container. +-- @tparam table args +-- @tparam wibox.widget widget args.widget The widget to tile. +-- @tparam string args.halign Either `left`, `right` or `center`. +-- @tparam string args.valign Either `top`, `bottom` or `center`. +-- @constructorfct wibox.container.tile +function module.mt:__call(...) + return new(...) +end + +return setmetatable(module, module.mt) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80