2016-05-16 05:44:54 +02:00
|
|
|
---------------------------------------------------------------------------
|
|
|
|
--
|
|
|
|
-- 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
|
2019-06-06 09:40:17 +02:00
|
|
|
-- @utillib gears.geometry
|
2016-05-16 05:44:54 +02:00
|
|
|
---------------------------------------------------------------------------
|
|
|
|
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
|
2019-06-08 01:08:05 +02:00
|
|
|
-- @treturn number The squared distance of the rectangle to the provided point.
|
|
|
|
-- @staticfct gears.geometry.rectangle.get_square_distance
|
2016-05-16 05:44:54 +02:00
|
|
|
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.
|
2019-06-08 01:08:05 +02:00
|
|
|
-- @staticfct gears.geometry.rectangle.get_closest_by_coord
|
2016-05-16 05:44:54 +02:00
|
|
|
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
|
|
|
|
|
2016-05-16 05:59:59 +02:00
|
|
|
--- 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.
|
2019-06-08 01:08:05 +02:00
|
|
|
-- @staticfct gears.geometry.rectangle.get_by_coord
|
2016-05-16 05:59:59 +02:00
|
|
|
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
|
|
|
|
|
2016-05-16 06:57:15 +02:00
|
|
|
--- Return true whether rectangle B is in the right direction
|
|
|
|
-- compared to rectangle A.
|
2020-02-10 02:15:29 +01:00
|
|
|
--
|
|
|
|
-- 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.
|
2016-05-16 06:57:15 +02:00
|
|
|
-- @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.
|
2020-02-10 02:29:41 +01:00
|
|
|
--
|
|
|
|
-- The valid `dir` are:
|
|
|
|
--
|
|
|
|
-- * up
|
|
|
|
-- * down
|
|
|
|
-- * left
|
|
|
|
-- * right
|
|
|
|
--
|
|
|
|
-- @tparam string dir The direction.
|
2022-07-03 08:10:31 +02:00
|
|
|
-- @tparam table gA The first rectangle.
|
|
|
|
-- @tparam table gB The second rectangle.
|
2016-05-16 06:57:15 +02:00
|
|
|
-- @return The distance between the screens.
|
2022-07-03 08:10:31 +02:00
|
|
|
local function calculate_distance(dir, gA, gB)
|
|
|
|
local gAx = gA.x
|
|
|
|
local gAy = gA.y
|
|
|
|
local gBx = gB.x
|
|
|
|
local gBy = gB.y
|
2016-05-16 06:57:15 +02:00
|
|
|
|
|
|
|
if dir == "up" then
|
2022-07-03 08:10:31 +02:00
|
|
|
gBy = gB.y + gB.height
|
2016-05-16 06:57:15 +02:00
|
|
|
elseif dir == "down" then
|
2022-07-03 08:10:31 +02:00
|
|
|
gAy = gA.y + gA.height
|
2016-05-16 06:57:15 +02:00
|
|
|
elseif dir == "left" then
|
2022-07-03 08:10:31 +02:00
|
|
|
gBx = gB.x + gB.width
|
2016-05-16 06:57:15 +02:00
|
|
|
elseif dir == "right" then
|
2022-07-03 08:10:31 +02:00
|
|
|
gAx = gA.x + gA.width
|
2016-05-16 06:57:15 +02:00
|
|
|
end
|
|
|
|
|
2017-03-05 04:16:15 +01:00
|
|
|
return math.sqrt((gBx - gAx) ^ 2 + (gBy - gAy) ^ 2)
|
2016-05-16 06:57:15 +02:00
|
|
|
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.
|
2019-06-08 01:08:05 +02:00
|
|
|
-- @staticfct gears.geometry.rectangle.get_in_direction
|
2016-05-16 06:57:15 +02:00
|
|
|
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
|
|
|
|
|
2019-06-24 23:10:50 +02:00
|
|
|
--- 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.
|
2020-02-10 02:15:29 +01:00
|
|
|
-- @staticfct gears.geometry.rectangle.are_equal
|
2019-06-24 23:10:50 +02:00
|
|
|
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
|
|
|
|
|
2020-02-10 02:29:41 +01:00
|
|
|
--- 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
|
|
|
|
|
2016-05-16 07:07:58 +02:00
|
|
|
--- Check if an area intersect another area.
|
2020-02-10 02:15:29 +01:00
|
|
|
-- @tparam table a The area.
|
|
|
|
-- @tparam table b The other area.
|
2016-05-16 07:07:58 +02:00
|
|
|
-- @return True if they intersect, false otherwise.
|
2019-06-08 01:08:05 +02:00
|
|
|
-- @staticfct gears.geometry.rectangle.area_intersect_area
|
2018-05-18 22:44:35 +02:00
|
|
|
function gears.geometry.rectangle.area_intersect_area(a, b)
|
2016-05-16 07:07:58 +02:00
|
|
|
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.
|
2019-06-08 01:08:05 +02:00
|
|
|
-- @staticfct gears.geometry.rectangle.get_intersection
|
2020-02-10 02:29:41 +01:00
|
|
|
-- @see gears.geometry.rectangle.is_inside
|
2016-05-16 07:07:58 +02:00
|
|
|
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
|
2017-03-05 20:00:43 +01:00
|
|
|
if g.width <= 0 or g.height <= 0 then
|
|
|
|
g.width, g.height = 0, 0
|
|
|
|
end
|
2016-05-16 07:07:58 +02:00
|
|
|
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.
|
2019-06-08 01:08:05 +02:00
|
|
|
-- @staticfct gears.geometry.rectangle.area_remove
|
2016-05-16 07:07:58 +02:00
|
|
|
function gears.geometry.rectangle.area_remove(areas, elem)
|
|
|
|
for i = #areas, 1, -1 do
|
|
|
|
-- Check if the 'elem' intersect
|
2018-05-18 22:44:35 +02:00
|
|
|
if gears.geometry.rectangle.area_intersect_area(areas[i], elem) then
|
2016-05-16 07:07:58 +02:00
|
|
|
-- 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
|
|
|
|
|
2016-05-16 05:44:54 +02:00
|
|
|
return gears.geometry
|
2016-12-31 14:07:13 +01:00
|
|
|
|
|
|
|
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|