awesome/lib/wibox/container/background.lua

510 lines
15 KiB
Lua
Raw Normal View History

---------------------------------------------------------------------------
-- A container capable of changing the background color, foreground color and
-- widget shape.
--
--@DOC_wibox_container_defaults_background_EXAMPLE@
-- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter
-- @containermod wibox.container.background
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local base = require("wibox.widget.base")
local color = require("gears.color")
local surface = require("gears.surface")
local beautiful = require("beautiful")
local cairo = require("lgi").cairo
local gtable = require("gears.table")
local gshape = require("gears.shape")
local gdebug = require("gears.debug")
local setmetatable = setmetatable
local type = type
local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
local background = { mt = {} }
-- 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
-- more widely compatible version.
function background._use_fallback_algorithm()
background.before_draw_children = function(self, _, cr, width, height)
local bw = self._private.shape_border_width or 0
local shape = self._private.shape or gshape.rectangle
if bw > 0 then
cr:translate(bw, bw)
width, height = width - 2*bw, height - 2*bw
end
shape(cr, width, height)
if self._private.background then
cr:save() --Save to avoid messing with the original source
cr:set_source(self._private.background)
cr:fill_preserve()
cr:restore()
end
cr:translate(-bw, -bw)
cr:clip()
if self._private.foreground then
cr:set_source(self._private.foreground)
end
end
background.after_draw_children = function(self, _, cr, width, height)
local bw = self._private.shape_border_width or 0
local shape = self._private.shape or gshape.rectangle
if bw > 0 then
cr:save()
cr:reset_clip()
local mat = cr:get_matrix()
-- Prevent the inner part of the border from being written.
local mask = cairo.RecordingSurface(cairo.Content.COLOR_ALPHA,
cairo.Rectangle { x = 0, y = 0, width = mat.x0 + width, height = mat.y0 + height })
local mask_cr = cairo.Context(mask)
mask_cr:translate(mat.x0, mat.y0)
-- Clear the surface.
mask_cr:set_operator(cairo.Operator.CLEAR)
mask_cr:set_source_rgba(0, 1, 0, 0)
mask_cr:paint()
-- Paint the inner and outer borders.
mask_cr:set_operator(cairo.Operator.SOURCE)
mask_cr:translate(bw, bw)
mask_cr:set_source_rgba(1, 0, 0, 1)
mask_cr:set_line_width(2*bw)
shape(mask_cr, width - 2*bw, height - 2*bw)
mask_cr:stroke_preserve()
-- Remove the inner part.
mask_cr:set_source_rgba(0, 1, 0, 0)
mask_cr:set_operator(cairo.Operator.CLEAR)
mask_cr:fill()
mask:flush()
cr:set_source(color(self._private.shape_border_color or self._private.foreground or beautiful.fg_normal))
cr:mask_surface(mask, 0,0)
cr:restore()
end
end
end
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>
2019-01-25 15:16:17 +01:00
-- 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
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>
2019-01-25 15:16:17 +01:00
end
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>
2019-01-25 15:16:17 +01:00
-- Prepare drawing the children of this widget
function background:before_draw_children(context, cr, width, height)
local bw = self._private.shape_border_width or 0
local shape = self._private.shape or (bw > 0 and gshape.rectangle or nil)
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>
2019-01-25 15:16:17 +01:00
-- Redirect drawing to a temporary surface if there is a shape
if shape then
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>
2019-01-25 15:16:17 +01:00
cr:push_group_with_content(cairo.Content.COLOR_ALPHA)
end
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>
2019-01-25 15:16:17 +01:00
-- Draw the background
if self._private.background then
cr:save()
cr:set_source(self._private.background)
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>
2019-01-25 15:16:17 +01:00
cr:rectangle(0, 0, width, height)
cr:fill()
cr:restore()
end
if self._private.bgimage then
cr:save()
if type(self._private.bgimage) == "function" then
self._private.bgimage(context, cr, width, height,unpack(self._private.bgimage_args))
else
local pattern = cairo.Pattern.create_for_surface(self._private.bgimage)
cr:set_source(pattern)
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>
2019-01-25 15:16:17 +01:00
cr:rectangle(0, 0, width, height)
cr:fill()
end
cr:restore()
end
if self._private.foreground then
cr:set_source(self._private.foreground)
end
end
-- Draw the border
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>
2019-01-25 15:16:17 +01:00
function background:after_draw_children(_, cr, width, height)
local bw = self._private.shape_border_width or 0
local shape = self._private.shape or (bw > 0 and gshape.rectangle or nil)
if not shape then
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>
2019-01-25 15:16:17 +01:00
return
end
-- Okay, there is a shape. Get it as a path.
cr:translate(bw, bw)
shape(cr, width - 2*bw, height - 2*bw, unpack(self._private.shape_args or {}))
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>
2019-01-25 15:16:17 +01:00
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))
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>
2019-01-25 15:16:17 +01:00
cr:set_operator(cairo.Operator.SOURCE)
cr:mask(mask)
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>
2019-01-25 15:16:17 +01:00
dispose_pattern(mask)
end
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>
2019-01-25 15:16:17 +01:00
-- 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)
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>
2019-01-25 15:16:17 +01:00
-- 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
function background:layout(_, width, height)
if self._private.widget then
local bw = self._private.border_strategy == "inner" and
self._private.shape_border_width or 0
return { base.place_widget_at(
self._private.widget, bw, bw, width-2*bw, height-2*bw
) }
end
end
-- Fit this widget into the given area
function background:fit(context, width, height)
if not self._private.widget then
return 0, 0
end
local bw = self._private.border_strategy == "inner" and
self._private.shape_border_width or 0
local w, h = base.fit_widget(
self, context, self._private.widget, width - 2*bw, height - 2*bw
)
return w+2*bw, h+2*bw
end
2016-05-26 08:09:18 +02:00
--- The widget displayed in the background widget.
-- @property widget
-- @tparam widget widget The widget to be disaplayed inside of the background
-- area.
-- @interface container
2016-05-26 08:09:18 +02:00
background.set_widget = base.set_widget_common
2016-05-26 08:09:18 +02:00
function background:get_widget()
return self._private.widget
end
function background:get_children()
return {self._private.widget}
end
function background:set_children(children)
self:set_widget(children[1])
end
2016-05-26 08:09:18 +02:00
--- The background color/pattern/gradient to use.
--
--@DOC_wibox_container_background_bg_EXAMPLE@
--
2016-05-26 08:09:18 +02:00
-- @property bg
-- @tparam color bg A color string, pattern or gradient
2016-05-26 08:09:18 +02:00
-- @see gears.color
-- @propemits true false
2016-05-26 08:09:18 +02:00
function background:set_bg(bg)
if bg then
self._private.background = color(bg)
else
self._private.background = nil
end
self:emit_signal("widget::redraw_needed")
self:emit_signal("property::bg", bg)
end
2016-05-26 08:09:18 +02:00
function background:get_bg()
return self._private.background
end
--- The foreground (text) color/pattern/gradient to use.
--
--@DOC_wibox_container_background_fg_EXAMPLE@
--
2016-05-26 08:09:18 +02:00
-- @property fg
-- @tparam color fg A color string, pattern or gradient
-- @propemits true false
2016-05-26 08:09:18 +02:00
-- @see gears.color
function background:set_fg(fg)
if fg then
self._private.foreground = color(fg)
else
self._private.foreground = nil
end
self:emit_signal("widget::redraw_needed")
self:emit_signal("property::fg", fg)
end
2016-05-26 08:09:18 +02:00
function background:get_fg()
return self._private.foreground
end
--- The background shape.
2016-05-26 08:09:18 +02:00
--
-- Use `set_shape` to set additional shape paramaters.
--
--@DOC_wibox_container_background_shape_EXAMPLE@
--
2016-05-26 08:09:18 +02:00
-- @property shape
-- @tparam gears.shape|function shape A function taking a context, width and height as arguments
2016-05-26 08:09:18 +02:00
-- @see gears.shape
-- @see set_shape
--- Set the background shape.
--
-- Any other arguments will be passed to the shape function.
--
-- @method set_shape
-- @tparam gears.shape|function shape A function taking a context, width and height as arguments
-- @propemits true false
2016-05-26 08:09:18 +02:00
-- @see gears.shape
-- @see shape
function background:set_shape(shape, ...)
2016-08-05 07:13:20 +02:00
local args = {...}
if shape == self._private.shape and #args == 0 then return end
self._private.shape = shape
self._private.shape_args = {...}
self:emit_signal("widget::redraw_needed")
self:emit_signal("property::shape", shape)
end
2016-05-26 08:09:18 +02:00
function background:get_shape()
return self._private.shape
end
--- When a `shape` is set, also draw a border.
--
2016-05-26 08:09:18 +02:00
-- See `wibox.container.background.shape` for an usage example.
--
-- @deprecatedproperty shape_border_width
-- @tparam number width The border width
-- @renamedin 4.4 border_width
-- @see border_width
--- Add a border of a specific width.
--
-- If the shape is set, the border will also be shaped.
--
-- See `wibox.container.background.shape` for an usage example.
-- @property border_width
-- @tparam[opt=0] number width The border width.
-- @propemits true false
-- @introducedin 4.4
-- @see border_color
2016-05-26 08:09:18 +02:00
function background:set_border_width(width)
2016-08-05 07:13:20 +02:00
if self._private.shape_border_width == width then return end
self._private.shape_border_width = width
self:emit_signal("widget::redraw_needed")
self:emit_signal("property::border_width", width)
end
function background:get_border_width()
2016-05-26 08:09:18 +02:00
return self._private.shape_border_width
end
function background.get_shape_border_width(...)
gdebug.deprecate("Use `border_width` instead of `shape_border_width`",
{deprecated_in=5})
return background.get_border_width(...)
end
function background.set_shape_border_width(...)
gdebug.deprecate("Use `border_width` instead of `shape_border_width`",
{deprecated_in=5})
background.set_border_width(...)
end
--- When a `shape` is set, also draw a border.
--
2016-05-26 08:09:18 +02:00
-- See `wibox.container.background.shape` for an usage example.
--
-- @deprecatedproperty shape_border_color
-- @usebeautiful beautiful.fg_normal Fallback when 'fg' and `border_color` aren't set.
-- @tparam[opt=self._private.foreground] color fg The border color, pattern or gradient
-- @renamedin 4.4 border_color
2016-05-26 08:09:18 +02:00
-- @see gears.color
-- @see border_color
2016-05-26 08:09:18 +02:00
--- Set the color for the border.
--
-- See `wibox.container.background.shape` for an usage example.
-- @property border_color
-- @tparam[opt=self._private.foreground] color fg The border color, pattern or gradient
-- @propemits true false
-- @usebeautiful beautiful.fg_normal Fallback when 'fg' and `border_color` aren't set.
-- @introducedin 4.4
-- @see gears.color
-- @see border_width
function background:set_border_color(fg)
2016-08-05 07:13:20 +02:00
if self._private.shape_border_color == fg then return end
self._private.shape_border_color = fg
self:emit_signal("widget::redraw_needed")
self:emit_signal("property::border_color", fg)
end
function background:get_border_color()
2016-05-26 08:09:18 +02:00
return self._private.shape_border_color
end
function background.get_shape_border_color(...)
gdebug.deprecate("Use `border_color` instead of `shape_border_color`",
{deprecated_in=5})
return background.get_border_color(...)
end
function background.set_shape_border_color(...)
gdebug.deprecate("Use `border_color` instead of `shape_border_color`",
{deprecated_in=5})
background.set_border_color(...)
end
function background:set_shape_clip(value)
if value then return end
require("gears.debug").print_warning("shape_clip property of background container was removed."
.. " Use wibox.layout.stack instead if you want shape_clip=false.")
end
2016-05-26 08:09:18 +02:00
function background:get_shape_clip()
require("gears.debug").print_warning("shape_clip property of background container was removed."
.. " Use wibox.layout.stack instead if you want shape_clip=false.")
return true
2016-05-26 08:09:18 +02:00
end
--- How the border width affects the contained widget.
--
-- The valid values are:
--
-- * *none*: Just apply the border, do not affect the content size (default).
-- * *inner*: Squeeze the size of the content by the border width.
--
-- @property border_strategy
-- @tparam[opt="none"] string border_strategy
function background:set_border_strategy(value)
self._private.border_strategy = value
self:emit_signal("widget::layout_changed")
self:emit_signal("property::border_strategy", value)
end
--- The background image to use.
--
-- 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.
--
2016-05-26 08:09:18 +02:00
-- @property bgimage
-- @tparam string|surface|function image A background image or a function
2016-05-26 08:09:18 +02:00
-- @see gears.surface
function background:set_bgimage(image, ...)
self._private.bgimage = type(image) == "function" and image or surface.load(image)
self._private.bgimage_args = {...}
self:emit_signal("widget::redraw_needed")
self:emit_signal("property::bgimage", image)
end
2016-05-26 08:09:18 +02:00
function background:get_bgimage()
return self._private.bgimage
end
--- Returns a new background container.
--
-- A background container applies a background and foreground color
-- to another widget.
--
-- @tparam[opt] widget widget The widget to display.
-- @tparam[opt] color bg The background to use for that widget.
-- @tparam[opt] gears.shape|function shape A `gears.shape` compatible shape function
-- @constructorfct wibox.container.background
local function new(widget, bg, shape)
2016-05-26 08:09:18 +02:00
local ret = base.make_widget(nil, nil, {
enable_properties = true,
})
gtable.crush(ret, background, true)
ret._private.shape = shape
ret:set_widget(widget)
ret:set_bg(bg)
return ret
end
function background.mt:__call(...)
return new(...)
end
return setmetatable(background, background.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80