213 lines
7.0 KiB
Lua
213 lines
7.0 KiB
Lua
-- => Shapes
|
|
-- Provides utility functions for handling cairo shapes and geometry
|
|
-- ============================================================
|
|
--
|
|
local lgi = require("lgi")
|
|
local colors = require("nice.colors")
|
|
local hex2rgb = colors.hex2rgb
|
|
local darken = colors.darken
|
|
local cairo = lgi.cairo
|
|
local math = math
|
|
local rad = math.rad
|
|
local floor = math.floor
|
|
|
|
-- Returns a shape function for a rounded rectangle with independently configurable corner radii
|
|
local function rounded_rect(args)
|
|
local r1 = args.tl or 0
|
|
local r2 = args.bl or 0
|
|
local r3 = args.br or 0
|
|
local r4 = args.tr or 0
|
|
return function(cr, width, height)
|
|
cr:new_sub_path()
|
|
cr:arc(width - r1, r1, r1, rad(-90), rad(0))
|
|
cr:arc(width - r2, height - r2, r2, rad(0), rad(90))
|
|
cr:arc(r3, height - r3, r3, rad(90), rad(180))
|
|
cr:arc(r4, r4, r4, rad(180), rad(270))
|
|
cr:close_path()
|
|
end
|
|
end
|
|
|
|
-- Returns a circle of the specified size filled with the specified color
|
|
local function circle_filled(color, size)
|
|
color = color or "#fefefa"
|
|
local surface = cairo.ImageSurface.create("ARGB32", size, size)
|
|
local cr = cairo.Context.create(surface)
|
|
cr:arc(size / 2, size / 2, size / 2, rad(0), rad(360))
|
|
cr:set_source_rgba(hex2rgb(color))
|
|
cr.antialias = cairo.Antialias.BEST
|
|
cr:fill()
|
|
-- cr:arc(
|
|
-- size / 2, size / 2, size / 2 - 0.5, rad(135), rad(270))
|
|
-- cr:set_source_rgba(hex2rgb(darken(color, 25)))
|
|
-- cr.line_width = 1
|
|
-- cr:stroke()
|
|
collectgarbage()
|
|
collectgarbage()
|
|
return surface
|
|
end
|
|
|
|
-- Returns a vertical gradient pattern going from cololr_1 -> color_2
|
|
local function duotone_gradient_vertical(color_1, color_2, height, offset_1,
|
|
offset_2)
|
|
local fill_pattern = cairo.Pattern.create_linear(0, 0, 0, height)
|
|
local r, g, b, a
|
|
r, g, b, a = hex2rgb(color_1)
|
|
fill_pattern:add_color_stop_rgba(offset_1 or 0, r, g, b, a)
|
|
r, g, b, a = hex2rgb(color_2)
|
|
fill_pattern:add_color_stop_rgba(offset_2 or 1, r, g, b, a)
|
|
return fill_pattern
|
|
end
|
|
|
|
-- Flips the given surface around the specified axis
|
|
local function flip(surface, axis)
|
|
local width = surface:get_width()
|
|
local height = surface:get_height()
|
|
local flipped = cairo.ImageSurface.create("ARGB32", width, height)
|
|
local cr = cairo.Context.create(flipped)
|
|
local source_pattern = cairo.Pattern.create_for_surface(surface)
|
|
if axis == "horizontal" then
|
|
source_pattern.matrix = cairo.Matrix {xx = -1, yy = 1, x0 = width}
|
|
elseif axis == "vertical" then
|
|
source_pattern.matrix = cairo.Matrix {xx = 1, yy = -1, y0 = height}
|
|
elseif axis == "both" then
|
|
source_pattern.matrix = cairo.Matrix {
|
|
xx = -1,
|
|
yy = -1,
|
|
x0 = width,
|
|
y0 = height,
|
|
}
|
|
end
|
|
cr.source = source_pattern
|
|
cr:rectangle(0, 0, width, height)
|
|
cr:paint()
|
|
collectgarbage()
|
|
collectgarbage()
|
|
return flipped
|
|
end
|
|
|
|
-- Draws the left corner of the titlebar
|
|
local function create_corner_top_left(args)
|
|
local radius = args.radius
|
|
local height = args.height
|
|
local surface = cairo.ImageSurface.create("ARGB32", radius, height)
|
|
local cr = cairo.Context.create(surface)
|
|
-- Create the corner shape and fill it with a gradient
|
|
local radius_offset = 1 -- To soften the corner
|
|
cr:move_to(0, height)
|
|
cr:line_to(0, radius - radius_offset)
|
|
cr:arc(
|
|
radius + radius_offset, radius + radius_offset, radius, rad(180),
|
|
rad(270))
|
|
cr:line_to(radius, height)
|
|
cr:close_path()
|
|
cr.source = args.background_source
|
|
cr.antialias = cairo.Antialias.BEST
|
|
cr:fill()
|
|
-- Next add the subtle 3D look
|
|
local function add_stroke(nargs)
|
|
local arc_radius = nargs.radius
|
|
local offset_x = nargs.offset_x
|
|
local offset_y = nargs.offset_y
|
|
cr:new_sub_path()
|
|
cr:move_to(offset_x, height)
|
|
cr:line_to(offset_x, arc_radius + offset_y)
|
|
cr:arc(
|
|
arc_radius + offset_x, arc_radius + offset_y, arc_radius, rad(180),
|
|
rad(270))
|
|
cr.source = nargs.source
|
|
cr.line_width = nargs.width
|
|
cr.antialias = cairo.Antialias.BEST
|
|
cr:stroke()
|
|
end
|
|
-- Outer dark stroke
|
|
add_stroke {
|
|
offset_x = args.stroke_offset_outer,
|
|
offset_y = args.stroke_offset_outer,
|
|
radius = radius + 0.5,
|
|
source = args.stroke_source_outer,
|
|
width = args.stroke_width_outer,
|
|
}
|
|
-- Inner light stroke
|
|
add_stroke {
|
|
offset_x = args.stroke_offset_inner,
|
|
offset_y = args.stroke_offset_inner,
|
|
radius = radius,
|
|
width = args.stroke_width_inner,
|
|
source = args.stroke_source_inner,
|
|
}
|
|
collectgarbage()
|
|
collectgarbage()
|
|
return surface
|
|
end
|
|
|
|
-- Draws the middle of the titlebar
|
|
local function create_edge_top_middle(args)
|
|
local client_color = args.color
|
|
local height = args.height
|
|
local width = args.width
|
|
local surface = cairo.ImageSurface.create("ARGB32", width, height)
|
|
local cr = cairo.Context.create(surface)
|
|
-- Create the background shape and fill it with a gradient
|
|
cr:rectangle(0, 0, width, height)
|
|
cr.source = args.background_source
|
|
cr:fill()
|
|
-- Then add the light and dark strokes for that 3D look
|
|
local function add_stroke(stroke_width, stroke_offset, stroke_color)
|
|
cr:new_sub_path()
|
|
cr:move_to(0, stroke_offset)
|
|
cr:line_to(width, stroke_offset)
|
|
cr.line_width = stroke_width
|
|
cr:set_source_rgb(hex2rgb(stroke_color))
|
|
cr:stroke()
|
|
end
|
|
-- Inner light stroke
|
|
add_stroke(
|
|
args.stroke_width_inner, args.stroke_offset_inner,
|
|
args.stroke_color_inner)
|
|
-- Outer dark stroke
|
|
add_stroke(
|
|
args.stroke_width_outer, args.stroke_offset_outer,
|
|
args.stroke_color_outer)
|
|
collectgarbage()
|
|
collectgarbage()
|
|
return surface
|
|
end
|
|
|
|
local function create_edge_left(args)
|
|
local height = args.height
|
|
local width = 2
|
|
-- height = height or 1080
|
|
local surface = cairo.ImageSurface.create("ARGB32", width, height)
|
|
local cr = cairo.Context.create(surface)
|
|
cr:rectangle(0, 0, 2, args.height)
|
|
cr:set_source_rgb(hex2rgb(args.client_color))
|
|
cr:fill()
|
|
-- Inner light stroke
|
|
cr:new_sub_path()
|
|
cr:move_to(args.stroke_offset_inner, 0) -- 1/5
|
|
cr:line_to(args.stroke_offset_inner, height)
|
|
cr.line_width = args.stroke_width_inner -- 1.5
|
|
cr:set_source_rgb(hex2rgb(args.inner_stroke_color))
|
|
cr:stroke()
|
|
-- Outer dark stroke
|
|
cr:new_sub_path()
|
|
cr:move_to(args.stroke_offset_outer, 0)
|
|
cr:line_to(args.stroke_offset_outer, height)
|
|
cr.line_width = args.stroke_width_outer -- 1
|
|
cr:set_source_rgb(hex2rgb(args.stroke_color_outer))
|
|
cr:stroke()
|
|
collectgarbage()
|
|
collectgarbage()
|
|
return surface
|
|
end
|
|
|
|
return {
|
|
rounded_rect = rounded_rect,
|
|
circle_filled = circle_filled,
|
|
duotone_gradient_vertical = duotone_gradient_vertical,
|
|
flip = flip,
|
|
create_corner_top_left = create_corner_top_left,
|
|
create_edge_top_middle = create_edge_top_middle,
|
|
create_edge_left = create_edge_left,
|
|
}
|