awesome/lib/gears/geometry.lua

304 lines
10 KiB
Lua

---------------------------------------------------------------------------
--
-- Helper functions used to compute geometries.
--
-- When this module refer to a geometry table, this assume a table with at least
-- an *x*, *y*, *width* and *height* keys and numeric values.
--
-- @author Julien Danjou <julien@danjou.info>
-- @copyright 2008 Julien Danjou
-- @utillib gears.geometry
---------------------------------------------------------------------------
local math = math
local gears = {geometry = {rectangle = {} } }
--- Get the square distance between a rectangle and a point.
-- @tparam table geom A rectangle
-- @tparam number geom.x The horizontal coordinate
-- @tparam number geom.y The vertical coordinate
-- @tparam number geom.width The rectangle width
-- @tparam number geom.height The rectangle height
-- @tparam number x X coordinate of point
-- @tparam number y Y coordinate of point
-- @treturn number The squared distance of the rectangle to the provided point.
-- @staticfct gears.geometry.rectangle.get_square_distance
function gears.geometry.rectangle.get_square_distance(geom, x, y)
local dist_x, dist_y = 0, 0
if x < geom.x then
dist_x = geom.x - x
elseif x >= geom.x + geom.width then
dist_x = x - geom.x - geom.width + 1
end
if y < geom.y then
dist_y = geom.y - y
elseif y >= geom.y + geom.height then
dist_y = y - geom.y - geom.height + 1
end
return dist_x * dist_x + dist_y * dist_y
end
--- Return the closest rectangle from `list` for a given point.
-- @tparam table list A list of geometry tables.
-- @tparam number x The x coordinate
-- @tparam number y The y coordinate
-- @return The key from the closest geometry.
-- @staticfct gears.geometry.rectangle.get_closest_by_coord
function gears.geometry.rectangle.get_closest_by_coord(list, x, y)
local dist = math.huge
local ret = nil
for k, v in pairs(list) do
local d = gears.geometry.rectangle.get_square_distance(v, x, y)
if d < dist then
ret, dist = k, d
end
end
return ret
end
--- Return the rectangle containing the [x, y] point.
--
-- Note that if multiple element from the geometry list contains the point, the
-- returned result is nondeterministic.
--
-- @tparam table list A list of geometry tables.
-- @tparam number x The x coordinate
-- @tparam number y The y coordinate
-- @return The key from the closest geometry. In case no result is found, *nil*
-- is returned.
-- @staticfct gears.geometry.rectangle.get_by_coord
function gears.geometry.rectangle.get_by_coord(list, x, y)
for k, geometry in pairs(list) do
if x >= geometry.x and x < geometry.x + geometry.width
and y >= geometry.y and y < geometry.y + geometry.height then
return k
end
end
end
--- Return true whether rectangle B is in the right direction
-- compared to rectangle A.
--
-- The valid `dir` are:
--
-- * up
-- * down
-- * left
-- * right
--
-- @tparam string dir The direction.
-- @tparam table gA The geometric specification for rectangle A.
-- @tparam table gB The geometric specification for rectangle B.
-- @return True if B is in the direction of A.
local function is_in_direction(dir, gA, gB)
if dir == "up" then
return gA.y > gB.y
elseif dir == "down" then
return gA.y < gB.y
elseif dir == "left" then
return gA.x > gB.x
elseif dir == "right" then
return gA.x < gB.x
end
return false
end
--- Calculate distance between two points.
-- i.e: if we want to move to the right, we will take the right border
-- of the currently focused screen and the left side of the checked screen.
--
-- The valid `dir` are:
--
-- * up
-- * down
-- * left
-- * right
--
-- @tparam string dir The direction.
-- @tparam table gA The first rectangle.
-- @tparam table gB The second rectangle.
-- @return The distance between the screens.
local function calculate_distance(dir, gA, gB)
local gAx = gA.x
local gAy = gA.y
local gBx = gB.x
local gBy = gB.y
if dir == "up" then
gBy = gB.y + gB.height
elseif dir == "down" then
gAy = gA.y + gA.height
elseif dir == "left" then
gBx = gB.x + gB.width
elseif dir == "right" then
gAx = gA.x + gA.width
end
return math.sqrt((gBx - gAx) ^ 2 + (gBy - gAy) ^ 2)
end
--- Get the nearest rectangle in the given direction. Every rectangle is specified as a table
-- with *x*, *y*, *width*, *height* keys, the same as client or screen geometries.
-- @tparam string dir The direction, can be either *up*, *down*, *left* or *right*.
-- @tparam table recttbl A table of rectangle specifications.
-- @tparam table cur The current rectangle.
-- @return The index for the rectangle in recttbl closer to cur in the given direction. nil if none found.
-- @staticfct gears.geometry.rectangle.get_in_direction
function gears.geometry.rectangle.get_in_direction(dir, recttbl, cur)
local dist, dist_min
local target = nil
-- We check each object
for i, rect in pairs(recttbl) do
-- Check geometry to see if object is located in the right direction.
if is_in_direction(dir, cur, rect) then
-- Calculate distance between current and checked object.
dist = calculate_distance(dir, cur, rect)
-- If distance is shorter then keep the object.
if not target or dist < dist_min then
target = i
dist_min = dist
end
end
end
return target
end
--- Return true if the area are exactly identical.
--
-- The areas are table with a `x`, `y`, `width` and `height` keys.
--
-- @tparam table a The area.
-- @tparam table b The other area.
-- @treturn boolean If the areas are identical.
-- @staticfct gears.geometry.rectangle.are_equal
function gears.geometry.rectangle.are_equal(a, b)
for _, v in ipairs {"x", "y", "width", "height"} do
if a[v] ~= b[v] then return false end
end
return true
end
--- Return if rectangle `a` is within rectangle `b`.
--
-- This includes the edges. 100% of `a` area has to be within `b` for this
-- function to return true. If you wish to know if any part of `a` intersect
-- with `b`, use `gears.geometry.rectangle.get_intersection`.
--
-- @tparam table a The smaller area.
-- @tparam table b The larger area.
-- @treturn boolean If the areas are identical.
-- @staticfct gears.geometry.rectangle.is_inside
-- @see gears.geometry.rectangle.get_intersection
function gears.geometry.rectangle.is_inside(a, b)
return (a.x >= b.x
and a.y >= b.y
and a.x+a.width <= b.x + b.width
and a.y+a.height <= b.y + b.height
)
end
--- Check if an area intersect another area.
-- @tparam table a The area.
-- @tparam table b The other area.
-- @return True if they intersect, false otherwise.
-- @staticfct gears.geometry.rectangle.area_intersect_area
function gears.geometry.rectangle.area_intersect_area(a, b)
return (b.x < a.x + a.width
and b.x + b.width > a.x
and b.y < a.y + a.height
and b.y + b.height > a.y)
end
--- Get the intersect area between a and b.
-- @tparam table a The area.
-- @tparam number a.x The horizontal coordinate
-- @tparam number a.y The vertical coordinate
-- @tparam number a.width The rectangle width
-- @tparam number a.height The rectangle height
-- @tparam table b The other area.
-- @tparam number b.x The horizontal coordinate
-- @tparam number b.y The vertical coordinate
-- @tparam number b.width The rectangle width
-- @tparam number b.height The rectangle height
-- @treturn table The intersect area.
-- @staticfct gears.geometry.rectangle.get_intersection
-- @see gears.geometry.rectangle.is_inside
function gears.geometry.rectangle.get_intersection(a, b)
local g = {}
g.x = math.max(a.x, b.x)
g.y = math.max(a.y, b.y)
g.width = math.min(a.x + a.width, b.x + b.width) - g.x
g.height = math.min(a.y + a.height, b.y + b.height) - g.y
if g.width <= 0 or g.height <= 0 then
g.width, g.height = 0, 0
end
return g
end
--- Remove an area from a list, splitting the space between several area that
-- can overlap.
-- @tparam table areas Table of areas.
-- @tparam table elem Area to remove.
-- @tparam number elem.x The horizontal coordinate
-- @tparam number elem.y The vertical coordinate
-- @tparam number elem.width The rectangle width
-- @tparam number elem.height The rectangle height
-- @return The new area list.
-- @staticfct gears.geometry.rectangle.area_remove
function gears.geometry.rectangle.area_remove(areas, elem)
for i = #areas, 1, -1 do
-- Check if the 'elem' intersect
if gears.geometry.rectangle.area_intersect_area(areas[i], elem) then
-- It does? remove it
local r = table.remove(areas, i)
local inter = gears.geometry.rectangle.get_intersection(r, elem)
if inter.x > r.x then
table.insert(areas, {
x = r.x,
y = r.y,
width = inter.x - r.x,
height = r.height
})
end
if inter.y > r.y then
table.insert(areas, {
x = r.x,
y = r.y,
width = r.width,
height = inter.y - r.y
})
end
if inter.x + inter.width < r.x + r.width then
table.insert(areas, {
x = inter.x + inter.width,
y = r.y,
width = (r.x + r.width) - (inter.x + inter.width),
height = r.height
})
end
if inter.y + inter.height < r.y + r.height then
table.insert(areas, {
x = r.x,
y = inter.y + inter.height,
width = r.width,
height = (r.y + r.height) - (inter.y + inter.height)
})
end
end
end
return areas
end
return gears.geometry
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80