From 1a5f6b7ad208e1aee5faff16c898e73b05ed7bdc Mon Sep 17 00:00:00 2001 From: copycat-killer Date: Thu, 2 Feb 2017 21:12:47 +0100 Subject: [PATCH] client: Allow clients to have shapes Also fixes awful.client.shape docs Closes #1507 --- lib/awful/client.lua | 10 ++++ lib/awful/client/shape.lua | 108 ++++++++++++++++++++++++++++++------- tests/test-resize.lua | 4 +- 3 files changed, 103 insertions(+), 19 deletions(-) diff --git a/lib/awful/client.lua b/lib/awful/client.lua index 55bb694c..73caf2cf 100644 --- a/lib/awful/client.lua +++ b/lib/awful/client.lua @@ -9,6 +9,7 @@ -- Grab environment we need local util = require("awful.util") local spawn = require("awful.spawn") +local set_shape = require("awful.client.shape").update.all local object = require("gears.object") local grect = require("gears.geometry").rectangle local pairs = pairs @@ -1155,6 +1156,15 @@ function client.object.is_transient_for(self, c2) return nil end +--- Set the client shape. +-- @property shape +-- @tparam gears.shape A gears.shape compatible function. +-- @see gears.shape +function client.object.set_shape(self, shape) + client.property.set(self, "_shape", shape) + set_shape(self) +end + -- Register standards signals --- The last geometry when client was floating. diff --git a/lib/awful/client/shape.lua b/lib/awful/client/shape.lua index f688b8c5..bc0c545f 100644 --- a/lib/awful/client/shape.lua +++ b/lib/awful/client/shape.lua @@ -18,13 +18,14 @@ local shape = {} shape.update = {} --- Get one of a client's shapes and transform it to include window decorations. --- @function awful.shape.get_transformed +-- @function awful.client.shape.get_transformed -- @client c The client whose shape should be retrieved -- @tparam string shape_name Either "bounding" or "clip" function shape.get_transformed(c, shape_name) local border = shape_name == "bounding" and c.border_width or 0 local shape_img = surface.load_silently(c["client_shape_" .. shape_name], false) - if not shape_img then return end + local _shape = c._shape + if not (shape_img or _shape) then return end -- Get information about various sizes on the client local geom = c:geometry() @@ -39,20 +40,99 @@ function shape.get_transformed(c, shape_name) local result = cairo.ImageSurface(cairo.Format.A1, img_width, img_height) local cr = cairo.Context(result) - -- Fill everything (this paints the titlebars and border) + -- Fill everything (this paints the titlebars and border). + -- The `cr:paint()` below will have painted the whole surface, so + -- everything inside the client is currently meant to be visible cr:paint() - -- Draw the client's shape in the middle - cr:set_operator(cairo.Operator.SOURCE) - cr:set_source_surface(shape_img, border + l, border + t) - cr:rectangle(border + l, border + t, geom.width - l - r, geom.height - t - b) - cr:fill() + if shape_img then + -- Draw the client's shape in the middle + cr:set_operator(cairo.Operator.SOURCE) + cr:set_source_surface(shape_img, border + l, border + t) + cr:rectangle(border + l, border + t, geom.width - l - r, geom.height - t - b) + cr:fill() + end + + if _shape then + -- Draw the shape to an intermediate surface + cr:push_group() + -- Intersect what is drawn so far with the shape set by Lua. + if shape_name == "clip" then + -- Correct for the border offset + cr:translate(-c.border_width, -c.border_width) + end + -- Always call the shape with the size of the bounding shape + _shape(cr, geom.width + 2*c.border_width, geom.height + 2*c.border_width) + -- Now fill the "selected" part + cr:set_operator(cairo.Operator.SOURCE) + cr:set_source_rgba(1, 1, 1, 1) + cr:fill_preserve() + if shape_name == "clip" then + -- Remove an area of size c.border_width again (We use 2*bw since + -- half of that is on the outside) + cr:set_source_rgba(0, 0, 0, 0) + cr:set_line_width(2*c.border_width) + cr:stroke() + end + -- Combine the result with what we already have + cr:pop_group_to_source() + cr:set_operator(cairo.Operator.IN) + cr:paint() + end return result end +--- Update all of a client's shapes from the shapes the client set itself. +-- @function awful.client.shape.update.all +-- @client c The client to act on +function shape.update.all(c) + local shape_fun = c._shape + + if not shape_fun then + c.shape_bounding = nil + c.shape_clip = nil + shape.update.bounding(c) + shape.update.clip(c) + return + end + + local geo = c:geometry() + local bw = c.border_width + + -- 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_fun(cr, geo.width + 2*bw, geo.height + 2*bw) + cr:set_operator(cairo.Operator.SOURCE) + cr:fill() + c.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_fun(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() + c.shape_clip = img._native + img:finish() +end + --- Update a client's bounding shape from the shape the client set itself. --- @function awful.shape.update.bounding +-- @function awful.client.shape.update.bounding -- @client c The client to act on function shape.update.bounding(c) local res = shape.get_transformed(c, "bounding") @@ -64,7 +144,7 @@ function shape.update.bounding(c) end --- Update a client's clip shape from the shape the client set itself. --- @function awful.shape.update.clip +-- @function awful.client.shape.update.clip -- @client c The client to act on function shape.update.clip(c) local res = shape.get_transformed(c, "clip") @@ -75,14 +155,6 @@ function shape.update.clip(c) end end ---- Update all of a client's shapes from the shapes the client set itself. --- @function awful.shape.update.all --- @client c The client to act on -function shape.update.all(c) - shape.update.bounding(c) - shape.update.clip(c) -end - capi.client.connect_signal("property::shape_client_bounding", shape.update.bounding) capi.client.connect_signal("property::shape_client_clip", shape.update.clip) capi.client.connect_signal("property::size", shape.update.all) diff --git a/tests/test-resize.lua b/tests/test-resize.lua index 67a75d9c..df76b69a 100644 --- a/tests/test-resize.lua +++ b/tests/test-resize.lua @@ -1,6 +1,7 @@ local test_client = require("_client") local placement = require("awful.placement") local amouse = require("awful.mouse") +local rounded_rect = require("gears.shape").rounded_rect local steps = {} @@ -8,7 +9,6 @@ table.insert(steps, function(count) if count == 1 then -- Setup. test_client("foobar", "foobar") elseif #client.get() > 0 then - client.get()[1] : geometry { x = 200, y = 200, @@ -16,6 +16,8 @@ table.insert(steps, function(count) height = 300, } + client.get()[1].shape = rounded_rect + return true end end)