local M = require "quarrel.ui.consts" local awful = require "awful" local gshape = require "gears.shape" local gtable = require "gears.table" local qbind = require "quarrel.bind" local qcolor = require "quarrel.color" local qdelegate = require "quarrel.delegate" local wibox = require "wibox" local cairo = require("lgi").cairo --- Clip Cairo context ---@param cr cairo_surface Cairo surface ---@param w integer Widget width ---@param h integer Widget height ---@return nil function M.shape(cr, w, h) -- gears.shape.rounded_rect(cr, w, h, dpi(4)) gshape.rounded_rect(cr, w, h, M.BORDER_RADIUS) -- gshape.rectangle(cr, w, h) end --- Returns qvars.text_font with size scaled by factor ---@param factor number? ---@return string function M.font(factor) return M.TEXT_FONT .. " " .. M.FONT_SIZE * (factor or 1) end --- Injects background widget styling into target ---@param target table ---@return table function M.styled(target) return gtable.crush({ bg = qcolor.palette.bg(), border_color = qcolor.palette.border(), border_width = M.BORDER_WIDTH, shape = M.shape, }, target) end --- Wraps a widget inside of a margin widget ---@param target table ---@return table function M.padded(target) return { widget = wibox.container.margin, margins = M.PADDING, target, } end --- Wraps a widget inside of a margin widget ---@param target table ---@return table function M.padded_big(target) return { widget = wibox.container.margin, margins = M.BIG_PADDING, target, } end --- Generates a styled popup ---@param target table ---@return table function M.popup(target, debug) target.widget = { widget = wibox.container.margin, margins = M.BIG_PADDING, target.widget, } return awful.popup(M.styled(target)) end --- Generates svg recolor string ---@param color string ---@return string function M.recolor(color) return "svg{fill:" .. color .. "}" end --- Generates icon widget ---@param args? table ---@return table function M.separator(args) args = args or {} return wibox.widget(gtable.crush({ widget = wibox.container.background, bg = qcolor.palette.border(), forced_height = not args.vertical and args.size, forced_width = args.vertical and args.size, }, args.widget or {})) end --- Generates icon widget ---@param args table ---@return table function M.icon(args) return wibox.widget(gtable.crush({ widget = wibox.widget.imagebox, image = args.icon, forced_width = (not args.unsized) and M.CHAR_HEIGHT, forced_height = (not args.unsized) and M.CHAR_HEIGHT, stylesheet = M.recolor(args.color or qcolor.palette.fg()), }, args.widget or {})) end --- Generates button widget ---@param args table ---@return table function M.button(args) args.press = args.press or function(_) end local widget = wibox.widget(gtable.crush({ widget = wibox.widget.imagebox, image = args.icon, forced_height = M.CHAR_HEIGHT, forced_width = M.CHAR_HEIGHT, stylesheet = M.recolor(qcolor.palette.fg()), press = args.press, }, args.widget or {})) widget.buttons = { qbind { triggers = qbind.btns.left, press = function() widget:press() end, hidden = true, }, } return widget end --- Generates toggle widget ---@param args table ---@return table function M.toggle(args) args.press = args.press or function(_) end local widget = M.button { widget = gtable.crush({ toggled = false, silent_press = function(self, state) if state then self.toggled = state else self.toggled = not self.toggled end if self.toggled then self.image = args.on else self.image = args.off end end, }, args.widget or {}), icon = args.off, press = function(self) if not args.manual then self:silent_press() end args.press(self) end, } return widget end ---@param widget wibox.widget.base ---@param cursor string ---@return wibox.widget.base function M.hoverable(widget, cursor) local last_wibox widget:connect_signal("mouse::enter", function() local w = mouse.current_wibox last_wibox = w if w then w.cursor = cursor end if type(widget.hover) == "function" then -- on enter bind widget.hover() elseif type(widget.hover) == "table" and widget.hover.enter then -- { enter: fun(), leave: fun() } widget.hover.enter() end end) widget:connect_signal("mouse::leave", function() local w = last_wibox or mouse.current_wibox if w then w.cursor = "left_ptr" end if type(widget.hover) == "table" and widget.hover.leave then -- { enter: fun(), leave: fun() } widget.hover.leave() end end) return widget end ---@param wibox wibox function M.animateable_shape(wibox) local old_apply_shape = wibox._apply_shape wibox:disconnect_signal("property::geometry", old_apply_shape) wibox:disconnect_signal("property::border_width", old_apply_shape) function wibox:set_shape_width(value) self._shape_width = value self:emit_signal("property::shape_width") end function wibox:get_shape_width(value) return self._shape_width end function wibox:set_shape_height(value) self._shape_height = value self:emit_signal("property::shape_height") end function wibox:get_shape_height(value) return self._shape_height end -- override to allow us to override shape wibox._apply_shape = qdelegate(function(env, self) local shape = self._shape if not shape then self.shape_bounding = nil self.shape_clip = nil return end local geo = self:geometry() local bw = self.border_width if self.shape_width then geo.width = self.shape_width end if self.shape_height then geo.height = self.shape_height end -- First handle the bounding shape (things including the border) local img = cairo.ImageSurface(cairo.Format.A1, geo.width + 2*bw, geo.height + 2*bw) local cr = cairo.Context(img) -- We just draw the shape in its full size shape(cr, geo.width + 2*bw, geo.height + 2*bw) cr:set_operator(cairo.Operator.SOURCE) cr:fill() self.shape_bounding = img._native img:finish() -- Now handle the clip shape (things excluding the border) img = cairo.ImageSurface(cairo.Format.A1, geo.width, geo.height) cr = cairo.Context(img) -- We give the shape the same arguments as for the bounding shape and draw -- it in its full size (the translate is to compensate for the smaller -- surface) cr:translate(-bw, -bw) shape(cr, geo.width + 2*bw, geo.height + 2*bw) cr:set_operator(cairo.Operator.SOURCE) cr:fill_preserve() -- Now we remove an area of width 'bw' again around the shape (We use 2*bw -- since half of that is on the outside and only half on the inside) cr:set_source_rgba(0, 0, 0, 0) cr:set_line_width(2*bw) cr:stroke() self.shape_clip = img._native img:finish() end, old_apply_shape) wibox:connect_signal("property::geometry", wibox._apply_shape) wibox:connect_signal("property::border_width", wibox._apply_shape) wibox:connect_signal("property::shape_width", wibox._apply_shape) wibox:connect_signal("property::shape_height", wibox._apply_shape) return wibox end return M