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 = {} }
-- Draw this widget
function background:draw(context, cr, width, height)
if not self._private.widget or not self._private.widget:get_visible() then
return
-- Make sure a surface pattern is freed *now*
local function dispose_pattern(pattern)
local status, s = pattern:get_surface()
if status == "SUCCESS" then
s:finish()
end
end
-- Keep the shape path in case there is a border
self._private.path = nil
-- Prepare drawing the children of this widget
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
-- Only add the offset if there is something to draw
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()
cr:push_group_with_content(cairo.Content.COLOR_ALPHA)
end
-- Draw the background
if self._private.background then
cr:set_source(self._private.background)
cr:paint()
cr:rectangle(0, 0, width, height)
cr:fill()
end
if self._private.bgimage then
if type(self._private.bgimage) == "function" then
@ -51,36 +49,74 @@ function background:draw(context, cr, width, height)
else
local pattern = cairo.Pattern.create_for_surface(self._private.bgimage)
cr:set_source(pattern)
cr:paint()
cr:rectangle(0, 0, width, height)
cr:fill()
end
end
cr:set_source(source)
end
-- Draw the border
function background:after_draw_children(_, cr)
-- Draw the border
if self._private.path and self._private.shape_border_width and self._private.shape_border_width > 0 then
cr:append_path(self._private.path)
function background:after_draw_children(_, cr, width, height)
if not self._private.shape then
return
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_operator(cairo.Operator.SOURCE)
cr:mask(mask)
cr:set_line_width(self._private.shape_border_width)
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)
dispose_pattern(mask)
end
-- Clip the shape
if self._private.path and self._private.shape_clip then
cr:append_path(self._private.path)
cr:clip()
end
-- We now have the right content in a temporary surface. Copy it to the
-- target surface. For this, we need another mask
cr:push_group_with_content(cairo.Content.ALPHA)
-- 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
-- Layout this widget