diff --git a/lib/gears/matrix.lua b/lib/gears/matrix.lua index 5e6bf7566..687a4f967 100644 --- a/lib/gears/matrix.lua +++ b/lib/gears/matrix.lua @@ -52,6 +52,22 @@ function matrix.create_rotate(angle) return matrix.create(c, s, -s, c, 0, 0) end +--- Create a matrix copy +-- @return A new matrix identical to `other` +function matrix:copy() + local m = matrix.create( + self.xx, self.yx, self.xy, + self.yy, self.x0, self.y0 + ) + + -- Used internally + if rawget(self, "_call") then + rawset(m, "_call", self._call) + end + + return m +end + --- Translate this matrix -- @tparam number x The translation in x direction. -- @tparam number y The translation in y direction. @@ -75,6 +91,17 @@ function matrix:rotate(angle) return matrix.create_rotate(angle):multiply(self) end +--- Rotate a shape from a custom point +-- @tparam number x The horizontal rotation point +-- @tparam number y The vertical rotation point +-- @tparam number angle The angle (in radiant: -2*math.pi to 2*math.pi) +-- @return A transformation object +function matrix:rotate_at(x, y, angle) + return self * matrix.create_translate( -x, -y ) + * matrix.create_rotate ( angle ) + * matrix.create_translate( x, y ) +end + --- Invert this matrix -- @return A new matrix describing the inverse transformation. function matrix:invert() @@ -94,12 +121,19 @@ end -- @tparam gears.matrix|cairo.Matrix other The other matrix to multiply with. -- @return The multiplication result. function matrix:multiply(other) - return matrix.create(self.xx * other.xx + self.yx * other.xy, - self.xx * other.yx + self.yx * other.yy, - self.xy * other.xx + self.yy * other.xy, - self.xy * other.yx + self.yy * other.yy, - self.x0 * other.xx + self.y0 * other.xy + other.x0, - self.x0 * other.yx + self.y0 * other.yy + other.y0) + local ret = matrix.create(self.xx * other.xx + self.yx * other.xy, + self.xx * other.yx + self.yx * other.yy, + self.xy * other.xx + self.yy * other.xy, + self.xy * other.yx + self.yy * other.yy, + self.x0 * other.xx + self.y0 * other.xy + other.x0, + self.x0 * other.yx + self.y0 * other.yy + other.y0) + + -- Used internally + if rawget(self, "_call") or rawget(other, "_call") then + rawset(ret, "_call", self._call or other._call) + end + + return ret end --- Check if two matrices are equal. @@ -189,6 +223,7 @@ matrix_mt.__newindex = error matrix_mt.__eq = matrix.equals matrix_mt.__mul = matrix.multiply matrix_mt.__tostring = matrix.tostring +matrix_mt.__call = function(self, ...) return self._call(self, ...) end --- A constant for the identity matrix. matrix.identity = matrix.create(1, 0, 0, 1, 0, 0) diff --git a/lib/gears/shape.lua b/lib/gears/shape.lua index d57cbfdb1..76b16f87c 100644 --- a/lib/gears/shape.lua +++ b/lib/gears/shape.lua @@ -21,6 +21,7 @@ -- @release @AWESOME_VERSION@ -- @module gears.shape --------------------------------------------------------------------------- +local g_matrix = require( "gears.matrix" ) local module = {} @@ -55,4 +56,142 @@ function module.rounded_bar(cr, width, height) module.rounded_rect(cr, width, height, height / 2) end +--- A rounded rectangle with a triangle at the top +-- @param cr A cairo context +-- @tparam number width The shape with +-- @tparam number height The shape height +-- @tparam[opt=5] number corner_radius The corner radius +-- @tparam[opt=10] number arrow_size The width and height of the arrow +-- @tparam[opt=width/2 - arrow_size/2] number arrow_position The position of the arrow +function module.infobubble(cr, width, height, corner_radius, arrow_size, arrow_position) + local corner_radius = corner_radius or 5 + local arrow_size = arrow_size or 10 + local arrow_position = arrow_position or width/2 - arrow_size/2 + + cr:move_to(0 ,corner_radius) + + -- Top left corner + cr:arc(corner_radius, corner_radius+arrow_size, (corner_radius), math.pi, 3*(math.pi/2)) + + -- The arrow triangle (still at the top) + cr:line_to(arrow_position , arrow_size ) + cr:line_to(arrow_position + arrow_size , 0 ) + cr:line_to(arrow_position + 2*arrow_size , arrow_size ) + + -- Complete the rounded rounded rectangle + cr:arc(width-corner_radius, corner_radius+arrow_size , (corner_radius) , 3*(math.pi/2) , math.pi*2 ) + cr:arc(width-corner_radius, height-(corner_radius) , (corner_radius) , math.pi*2 , math.pi/2 ) + cr:arc(corner_radius , height-(corner_radius) , (corner_radius) , math.pi/2 , math.pi ) + + -- Close path + cr:close_path() +end + +--- A rectangle terminated by an arrow +-- @param cr A cairo context +-- @tparam number width The shape with +-- @tparam number height The shape height +function module.rectangular_tag(cr, width, height) + cr:move_to(0 , height/2) + cr:line_to(height/2 , 0 ) + cr:line_to(width , 0 ) + cr:line_to(width , height ) + cr:line_to(height/2 , height ) + + cr:close_path() +end + +--- A simple arrow shape +-- @param cr A cairo context +-- @tparam number width The shape with +-- @tparam number height The shape height +-- @tparam[opt=head_width] number head_width The width of the head (/\) of the arrow +-- @tparam[opt=width /2] number shaft_width The width of the shaft of the arrow +-- @tparam[opt=height/2] number shaft_length The head_length of the shaft (the rest is the head) +function module.arrow(cr, width, height, head_width, shaft_width, shaft_length) + local shaft_length = shaft_length or height / 2 + local shaft_width = shaft_width or width / 2 + local head_width = head_width or width + local head_length = height - shaft_length + + cr:move_to ( width/2 , 0 ) + cr:rel_line_to( head_width/2 , head_length ) + cr:rel_line_to( -(head_width-shaft_width)/2 , 0 ) + cr:rel_line_to( 0 , shaft_length ) + cr:rel_line_to( -shaft_width , 0 ) + cr:rel_line_to( 0 , -shaft_length ) + cr:rel_line_to( -(head_width-shaft_width)/2 , 0 ) + + cr:close_path() +end + +--- A squeezed hexagon filling the rectangle +-- @param cr A cairo context +-- @tparam number width The shape with +-- @tparam number height The shape height +function module.hexagon(cr, width, height) + cr:move_to(height/2,0) + cr:line_to(width-height/2,0) + cr:line_to(width,height/2) + cr:line_to(width-height/2,height) + cr:line_to(height/2,height) + cr:line_to(0,height/2) + cr:line_to(height/2,0) + cr:close_path() +end + +--- Double arrow popularized by the vim-powerline module +-- @param cr A cairo context +-- @tparam number width The shape with +-- @tparam number height The shape height +-- @tparam[opt=height/2] number arrow_depth The width of the arrow part of the shape +function module.powerline(cr, width, height, arrow_depth) + local arrow_depth = arrow_depth or height/2 + cr:move_to(0 , 0 ) + cr:line_to(width - arrow_depth , 0 ) + cr:line_to(width , height/2 ) + cr:line_to(width - arrow_depth , height ) + cr:line_to(0 , height ) + cr:line_to(arrow_depth , height/2 ) + + cr:close_path() +end + +--- An isosceles triangle +-- @param cr A cairo context +-- @tparam number width The shape with +-- @tparam number height The shape height +function module.isosceles_triangle(cr, width, height) + cr:move_to( width/2, 0 ) + cr:line_to( width , height ) + cr:line_to( 0 , height ) + cr:close_path() +end + +--- Ajust the shape using a transformation object +-- +-- Apply various transformations to the shape +-- +-- @usage gears.shape.transform(gears.shape.rounded_bar) +-- : rotate(math.pi/2) +-- : translate(10, 10) +-- +-- @param shape A shape function +-- @return A transformation handle, also act as a shape function +function module.transform(shape) + + -- Apply the transformation matrix and apply the shape, then restore + local function apply(self, cr, width, height, ...) + cr:save() + cr:transform(self:to_cairo_matrix()) + shape(cr, width, height, ...) + cr:restore() + end + + local matrix = g_matrix.identity:copy() + rawset(matrix, "_call", apply) + + return matrix +end + return module diff --git a/lib/gears/surface.lua b/lib/gears/surface.lua index 404f38cfa..64afabea3 100644 --- a/lib/gears/surface.lua +++ b/lib/gears/surface.lua @@ -9,6 +9,7 @@ local setmetatable = setmetatable local type = type local capi = { awesome = awesome } local cairo = require("lgi").cairo +local color = nil local gdebug = require("gears.debug") -- Keep this in sync with build-utils/lgi-check.sh! @@ -152,6 +153,32 @@ function surface.duplicate_surface(s) return result end +--- Create a surface from a `gears.shape` +-- Any additional parameters will be passed to the shape function +-- @tparam number width The surface width +-- @tparam number height The surface height +-- @param shape A `gears.shape` compatible function +-- @param[opt=white] shape_color The shape color or pattern +-- @param[opt=transparent] bg_color The surface background color +-- @treturn cairo.surface the new surface +function surface.load_from_shape(width, height, shape, shape_color, bg_color, ...) + color = color or require("gears.color") + + local img = cairo.ImageSurface(cairo.Format.ARGB32, width, height) + local cr = cairo.Context(img) + + cr:set_source(color(bg_color or "#00000000")) + cr:paint() + + cr:set_source(color(shape_color or "#000000")) + + shape(cr, width, height, ...) + + cr:fill() + + return img +end + --- Apply a shape to a client or a wibox. -- -- If the wibox or client size change, this function need to be called diff --git a/lib/wibox/widget/imagebox.lua b/lib/wibox/widget/imagebox.lua index 40e4a6443..d30fe5e8d 100644 --- a/lib/wibox/widget/imagebox.lua +++ b/lib/wibox/widget/imagebox.lua @@ -12,6 +12,7 @@ local pairs = pairs local type = type local pcall = pcall local print = print +local unpack = unpack or table.unpack local imagebox = { mt = {} } @@ -30,6 +31,12 @@ function imagebox:draw(context, cr, width, height) cr:scale(aspect, aspect) end + + -- Set the clip + if self._clip_shape then + cr:clip(self._clip_shape(cr, width, height, unpack(self._clip_args))) + end + cr:set_source_surface(self._image, 0, 0) cr:paint() end @@ -107,6 +114,19 @@ function imagebox:set_image(image) return true end +--- Set a clip shape for this imagebox +-- A clip shape define an area where the content is displayed and one where it +-- is trimmed. +-- +-- Any other parameters will be passed to the clip shape function +-- +-- @param clip_shape A `gears_shape` compatible shape function +function imagebox:set_clip_shape(clip_shape, ...) + self._clip_shape = clip_shape + self._clip_args = {...} + self:emit_signal("widget::redraw_needed") +end + --- Should the image be resized to fit into the available space? -- @param allowed If false, the image will be clipped, else it will be resized -- to fit into the available space. @@ -117,10 +137,12 @@ function imagebox:set_resize(allowed) end --- Returns a new imagebox +-- Any other arguments will be passed to the clip shape function -- @param image the image to display, may be nil -- @param resize_allowed If false, the image will be clipped, else it will be resized -- to fit into the available space. -local function new(image, resize_allowed) +-- @param clip_shape A `gears.shape` compatible function +local function new(image, resize_allowed, clip_shape) local ret = base.make_widget() for k, v in pairs(imagebox) do @@ -136,6 +158,9 @@ local function new(image, resize_allowed) ret:set_resize(resize_allowed) end + ret._clip_shape = clip_shape + ret._clip_args = {} + return ret end