Change the way the shape is done in the background container

Previously, the background container "just" used the shape and drew a
line around it. This means that half the line will be inside of the
shape and half of it will be outside. Thus, this hides the actual shape
that is used.

This commit changes that so that the line is added outside of the shape.
It does this via some tricks:

- In :before_draw_children(), :push_group() is used to redirect drawing
  of the child widget to a temporary surface.
- In :after_draw_children(), the border is added to this group.
  + For this, another temporary surface is created. It will be used as a
    mask.
  + The inside of the shape on this mask is cleared, everything else is
    filled. Thus, the mask now contains everything "not content".
  + Everything inside the mask is filled with the background color.
- Also in :after_draw_children(), the group is drawn to the actual
  target surface.
  + Again, this needs a mask.
  + This time, we draw the shape to the mask with twice the border width.
    Thus, half of this line will be outside of the shape.
  + Then, the shape itself is also filled so that the mask contains the
    shape and the border.
  + This mask is then used to copy the right parts of the temporary
    surface were the child widget and border was drawn to the actual
    target surface that will be visible on screen.

This approach has some upsides. Because we no longer have "half the
border" above content, colors with some transparency work fine for the
border. Also, this should avoid issues with anti-aliasing, because e.g.
the border is not just drawn with the border width, but also further out
to everything else so that the background cannot "bleed through".

Fixes: https://github.com/awesomeWM/awesome/issues/2516
Signed-off-by: Uli Schlachter <psychon@znc.in>
This commit is contained in:
Uli Schlachter 2019-01-25 15:16:17 +01:00
parent e8bf75ef3c
commit 67cf1469f0
1 changed files with 72 additions and 36 deletions

View File

@ -20,30 +20,28 @@ local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility
local background = { mt = {} } local background = { mt = {} }
-- Draw this widget -- Make sure a surface pattern is freed *now*
function background:draw(context, cr, width, height) local function dispose_pattern(pattern)
if not self._private.widget or not self._private.widget:get_visible() then local status, s = pattern:get_surface()
return if status == "SUCCESS" then
s:finish()
end end
end
-- Keep the shape path in case there is a border -- Prepare drawing the children of this widget
self._private.path = nil function background:before_draw_children(context, cr, width, height)
local source = self._private.foreground or cr:get_source()
-- Redirect drawing to a temporary surface if there is a shape
if self._private.shape then if self._private.shape then
-- Only add the offset if there is something to draw cr:push_group_with_content(cairo.Content.COLOR_ALPHA)
local offset = ((self._private.shape_border_width and self._private.shape_border_color)
and self._private.shape_border_width or 0) / 2
cr:translate(offset, offset)
self._private.shape(cr, width - 2*offset, height - 2*offset, unpack(self._private.shape_args or {}))
cr:translate(-offset, -offset)
self._private.path = cr:copy_path()
cr:clip()
end end
-- Draw the background
if self._private.background then if self._private.background then
cr:set_source(self._private.background) cr:set_source(self._private.background)
cr:paint() cr:rectangle(0, 0, width, height)
cr:fill()
end end
if self._private.bgimage then if self._private.bgimage then
if type(self._private.bgimage) == "function" then if type(self._private.bgimage) == "function" then
@ -51,36 +49,74 @@ function background:draw(context, cr, width, height)
else else
local pattern = cairo.Pattern.create_for_surface(self._private.bgimage) local pattern = cairo.Pattern.create_for_surface(self._private.bgimage)
cr:set_source(pattern) cr:set_source(pattern)
cr:paint() cr:rectangle(0, 0, width, height)
cr:fill()
end end
end end
cr:set_source(source)
end end
-- Draw the border -- Draw the border
function background:after_draw_children(_, cr) function background:after_draw_children(_, cr, width, height)
-- Draw the border if not self._private.shape then
if self._private.path and self._private.shape_border_width and self._private.shape_border_width > 0 then return
cr:append_path(self._private.path) end
-- Okay, there is a shape. Get it as a path.
local bw = self._private.shape_border_width or 0
cr:translate(bw, bw)
self._private.shape(cr, width - 2*bw, height - 2*bw, unpack(self._private.shape_args or {}))
cr:translate(-bw, -bw)
if bw > 0 then
-- Now we need to do a border, somehow. We begin with another
-- temporary surface.
cr:push_group_with_content(cairo.Content.ALPHA)
-- Mark everything as "this is border"
cr:set_source_rgba(0, 0, 0, 1)
cr:paint()
-- Now remove the inside of the shape to get just the border
cr:set_operator(cairo.Operator.SOURCE)
cr:set_source_rgba(0, 0, 0, 0)
cr:fill_preserve()
local mask = cr:pop_group()
-- Now actually draw the border via the mask we just created.
cr:set_source(color(self._private.shape_border_color or self._private.foreground or beautiful.fg_normal)) cr:set_source(color(self._private.shape_border_color or self._private.foreground or beautiful.fg_normal))
cr:set_operator(cairo.Operator.SOURCE)
cr:mask(mask)
cr:set_line_width(self._private.shape_border_width) dispose_pattern(mask)
cr:stroke()
self._private.path = nil
end
end
-- Prepare drawing the children of this widget
function background:before_draw_children(_, cr)
if self._private.foreground then
cr:set_source(self._private.foreground)
end end
-- Clip the shape -- We now have the right content in a temporary surface. Copy it to the
if self._private.path and self._private.shape_clip then -- target surface. For this, we need another mask
cr:append_path(self._private.path) cr:push_group_with_content(cairo.Content.ALPHA)
cr:clip()
end -- Draw the border with 2 * border width (this draws both
-- inside and outside, only half of it is outside)
cr.line_width = 2 * bw
cr:set_source_rgba(0, 0, 0, 1)
cr:stroke_preserve()
-- Now fill the whole inside so that it is also include in the mask
cr:fill()
local mask = cr:pop_group()
local source = cr:pop_group() -- This pops what was pushed in before_draw_children
-- This now draws the content of the background widget to the actual
-- target, but only the part that is inside the mask
cr:set_operator(cairo.Operator.OVER)
cr:set_source(source)
cr:mask(mask)
dispose_pattern(mask)
dispose_pattern(source)
end end
-- Layout this widget -- Layout this widget