radical/placement.lua

206 lines
7.1 KiB
Lua

local capi = {screen=screen, mouse = mouse}
local unpack = unpack or table.unpack
local mouse = require( "awful.mouse" )
local screen = require( "awful.screen" )
local cairo = require( "lgi" ).cairo
local module = {}
-- Compute the new `x` and `y`.
-- The workarea position need to be applied by the caller
local map = {
-- Corners
top_left = function(sw, sh, dw, dh) return {x=0 , y=0 } end,
top_right = function(sw, sh, dw, dh) return {x=sw-dw , y=0 } end,
bottom_left = function(sw, sh, dw, dh) return {x=0 , y=sh-dh } end,
bottom_right = function(sw, sh, dw, dh) return {x=sw-dw , y=sh-dh } end,
left = function(sw, sh, dw, dh) return {x=0 , y=sh/2-dh/2} end,
right = function(sw, sh, dw, dh) return {x=sw-dw , y=sh/2-dh/2} end,
top = function(sw, sh, dw, dh) return {x=sw/2-dw/2, y=0 } end,
bottom = function(sw, sh, dw, dh) return {x=sw/2-dw/2, y=sh-dh } end,
}
-- Create the geometry rectangle 1=best case, 2=fallback
local positions = {
left1 = function(x, y, w, h) return {x = x - w, y = y , width = w, height = h} end,
left2 = function(x, y, w, h) return {x = x - w, y = y - h , width = w, height = h} end,
right1 = function(x, y, w, h) return {x = x , y = y , width = w, height = h} end,
right2 = function(x, y, w, h) return {x = x , y = y - h , width = w, height = h} end,
top1 = function(x, y, w, h) return {x = x , y = y - h , width = w, height = h} end,
top2 = function(x, y, w, h) return {x = x - w, y = y - h , width = w, height = h} end,
bottom1 = function(x, y, w, h) return {x = x , y = y , width = w, height = h} end,
bottom2 = function(x, y, w, h) return {x = x - w, y = y , width = w, height = h} end,
}
-- Check if the proposed geometry fit in the screen
local function fit_in_screen(s, geo)
local sgeo = capi.screen[s].geometry
local region = cairo.Region.create_rectangle(cairo.RectangleInt(sgeo))
region:intersect(cairo.Region.create_rectangle(
cairo.RectangleInt(geo)
))
local geo2 = region:get_rectangle(0)
-- If the geometry is the same, then it fit, else, it will be cropped
--TODO in case all directions are cropped, keep the least cropped one
return geo2.width == geo.width and geo2.height == geo.height
end
--- Move the drawable (client or wibox) `d` to a screen corner or side.
function module.corner(d, corner, s, honor_wa, update_wa)
local sgeo = capi.screen[s][honor_wa and "workarea" or "geometry"]
local dgeo = d:geometry()
local pos = map[corner](sgeo.width, sgeo.height, dgeo.width, dgeo.height)
d : geometry {
x = math.ceil(sgeo.x + pos.x) ,
y = math.ceil(sgeo.y + pos.y) ,
width = math.ceil(dgeo.width ) ,
height = math.ceil(dgeo.height ) ,
}
--TODO update_wa
end
--- Pin a drawable to a placement function.
-- Auto update the position when the size change
function module.pin(d, f, ...)
--TODO memory leak
local args = {...}
local function tracker()
f(d, unpack(args))
end
d:connect_signal("property::width" , tracker)
d:connect_signal("property::height", tracker)
tracker()
end
--- Get the possible 2D anchor points around a widget geometry.
-- This take into account the widget drawable (wibox) and try to avoid
-- overlapping.
function module.get_relative_points(geo, mode)
local use_mouse = true --TODO support modes
-- The closest points around the geometry
local dps = {}
-- Use the mouse position and the wibox/client under it
if not geo then
local draw = mouse.drawin_under_pointer()
geo = draw and draw:geometry() or capi.mouse.coords()
geo.drawable = draw
elseif geo.x and geo.width then
local coords = capi.mouse.coords()
-- Check id the mouse is in the rect
if coords.x > geo.x and coords.x < geo.x+geo.width and
coords.y > geo.y and coords.y < geo.y+geo.height then
geo.drawable = mouse.drawin_under_pointer()
end
--TODO add drawin_at(x,y) in the C core
end
if geo.drawable then
-- Case 1: A widget
local dgeo = geo.drawable.drawable:geometry()
-- Compute the absolute widget geometry
local abs_widget_geo = {
x = dgeo.x + geo.x,
y = dgeo.y + geo.y,
width = geo.width ,
height = geo.height ,
drawable = geo.drawable ,
}
-- Get the comparaison point
local center_point = use_mouse and capi.mouse.coords() or {
x = abs_widget_geo.x + abs_widget_geo.width / 2,
y = abs_widget_geo.y + abs_widget_geo.height / 2,
}
-- Get the 4 cloest points from `center_point` around the wibox
local points = {
left = {x = dgeo.x , y = center_point.y },
right = {x = dgeo.x + dgeo.width , y = center_point.y },
top = {x = center_point.x , y = dgeo.y },
bottom = {x = center_point.x , y = dgeo.y + dgeo.height },
}
local s = geo.drawable.screen or screen.getbycoord(
center_point.x,
center_point.y
)
-- Compute the distance (dp) between the `center_point` and the sides
for k, v in pairs(points) do
local dx, dy = v.x - center_point.x, v.y - center_point.y
dps[k] = {
distance = math.sqrt(dx*dx + dy*dy),
x = v.x,
y = v.y,
screen = s
}
end
else
-- Case 2: A random geometry
--TODO
end
return dps
end
-- @tparam drawable d A wibox or client
-- @tparam table points A table with position as key and points (x,y) as value
-- @tparam[opt={}] table preferred_positions The preferred positions (position as key,
-- and index as value)
-- @treturn string The choosen position
function module.move_relative(d, points, preferred_positions)
local w,h = d.width, d.height
local pref_idx, pref_name = 99, nil
local does_fit = {}
for k,v in pairs(points) do
local geo = positions[k..1](v.x, v.y, w, h)
local fit = fit_in_screen(v.screen, geo)
-- Try the other compatible geometry
if not fit then
geo = positions[k..2](v.x, v.y, w, h)
fit = fit_in_screen(v.screen, geo)
end
does_fit[k] = fit and geo or nil
if fit and preferred_positions[k] and preferred_positions[k] < pref_idx then
pref_idx = preferred_positions[k]
pref_name = k
end
-- No need to continue
if fit and preferred_positions[k] == 1 then break end
end
local pos_name = pref_name or next(does_fit)
local pos = does_fit[pos_name]
if pos then
d.x = math.ceil(pos.x)
d.y = math.ceil(pos.y)
end
return pos_name
end
return module