242 lines
8.1 KiB
Lua
242 lines
8.1 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
|
|
-- @release @AWESOME_VERSION@
|
|
-- @module 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
|
|
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.
|
|
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.
|
|
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.
|
|
-- @param dir The direction.
|
|
-- @param gA The geometric specification for rectangle A.
|
|
-- @param 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.
|
|
-- @param dir The direction.
|
|
-- @param _gA The first rectangle.
|
|
-- @param _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(math.pow(gBx - gAx, 2) + math.pow(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.
|
|
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
|
|
|
|
--- Check if an area intersect another area.
|
|
-- @param a The area.
|
|
-- @param b The other area.
|
|
-- @return True if they intersect, false otherwise.
|
|
local function 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.
|
|
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
|
|
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.
|
|
function gears.geometry.rectangle.area_remove(areas, elem)
|
|
for i = #areas, 1, -1 do
|
|
-- Check if the 'elem' intersect
|
|
if 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
|