From 22d656903563f75678f3634964731ccf93355dfd Mon Sep 17 00:00:00 2001 From: ache Date: Mon, 13 Mar 2017 23:17:19 +0100 Subject: Init commit --- lib/gears/geometry.lua | 240 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 lib/gears/geometry.lua (limited to 'lib/gears/geometry.lua') diff --git a/lib/gears/geometry.lua b/lib/gears/geometry.lua new file mode 100644 index 0000000..a429abd --- /dev/null +++ b/lib/gears/geometry.lua @@ -0,0 +1,240 @@ +--------------------------------------------------------------------------- +-- +-- 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 +-- @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 -- cgit v1.2.3-54-g00ecf