summaryrefslogtreecommitdiff
path: root/awesome/lib/awful/placement.lua
diff options
context:
space:
mode:
Diffstat (limited to 'awesome/lib/awful/placement.lua')
-rw-r--r--awesome/lib/awful/placement.lua1694
1 files changed, 0 insertions, 1694 deletions
diff --git a/awesome/lib/awful/placement.lua b/awesome/lib/awful/placement.lua
deleted file mode 100644
index d288f49..0000000
--- a/awesome/lib/awful/placement.lua
+++ /dev/null
@@ -1,1694 +0,0 @@
----------------------------------------------------------------------------
---- Algorithms used to place various drawables.
---
--- The functions provided by this module all follow the same arguments
--- conventions. This allow:
---
--- * To use them in various other module as
--- [visitor objects](https://en.wikipedia.org/wiki/Visitor_pattern)
--- * Turn each function into an API with various common customization parameters.
--- * Re-use the same functions for the `mouse`, `client`s, `screen`s and `wibox`es
---
---
--- <h3>Compositing</h3>
---
--- It is possible to compose placement function using the `+` or `*` operator:
---
---
---
---![Usage example](../images/AUTOGEN_awful_placement_compose.svg)
---
---
--- -- 'right' will be replaced by 'left'
--- local f = (awful.placement.right + awful.placement.left)
--- f(client.focus)
---
---
---
---![Usage example](../images/AUTOGEN_awful_placement_compose2.svg)
---
---
--- -- Simulate Windows 7 'edge snap' (also called aero snap) feature
--- local axis = 'vertically'
--- local f = awful.placement.scale
--- + awful.placement.left
--- + (axis and awful.placement['maximize_'..axis] or nil)
--- local geo = f(client.focus, {honor_workarea=true, to_percent = 0.5})
---
--- <h3>Common arguments</h3>
---
--- **pretend** (*boolean*):
---
--- Do not apply the new geometry. This is useful if only the return values is
--- necessary.
---
--- **honor_workarea** (*boolean*):
---
--- Take workarea into account when placing the drawable (default: false)
---
--- **honor_padding** (*boolean*):
---
--- Take the screen padding into account (see `screen.padding`)
---
--- **tag** (*tag*):
---
--- Use a tag geometry
---
--- **margins** (*number* or *table*):
---
--- A table with left, right, top, bottom keys or a number
---
--- **parent** (client, wibox, mouse or screen):
---
--- A parent drawable to use a base geometry
---
--- **bounding_rect** (table):
---
--- A bounding rectangle
---
--- **attach** (*boolean*):
---
--- When the parent geometry (like the screen) changes, re-apply the placement
--- function. This will add a `detach_callback` function to the drawable. Call
--- this to detach the function. This will be called automatically when a new
--- attached function is set.
---
--- **offset** (*table or number*):
---
--- The offset(s) to apply to the new geometry.
---
--- **store_geometry** (*boolean*):
---
--- Keep a single history of each type of placement. It can be restored using
--- `awful.placement.restore` by setting the right `context` argument.
---
--- When either the parent or the screen geometry change, call the placement
--- function again.
---
--- **update_workarea** (*boolean*):
---
--- If *attach* is true, also update the screen workarea.
---
--- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
--- @author Julien Danjou &lt;julien@danjou.info&gt;
--- @copyright 2008 Julien Danjou, Emmanuel Lepage Vallee 2016
--- @module awful.placement
----------------------------------------------------------------------------
-
--- Grab environment we need
-local ipairs = ipairs
-local pairs = pairs
-local math = math
-local table = table
-local capi =
-{
- screen = screen,
- mouse = mouse,
- client = client
-}
-local client = require("awful.client")
-local layout = require("awful.layout")
-local a_screen = require("awful.screen")
-local grect = require("gears.geometry").rectangle
-local util = require("awful.util")
-local cairo = require( "lgi" ).cairo
-local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
-
-local function get_screen(s)
- return s and capi.screen[s]
-end
-
-local wrap_client = nil
-local placement
-
--- Store function -> keys
-local reverse_align_map = {}
-
--- Forward declarations
-local area_common
-local wibox_update_strut
-local attach
-
---- Allow multiple placement functions to be daisy chained.
--- This also allow the functions to be aware they are being chained and act
--- upon the previous nodes results to avoid unnecessary processing or deduce
--- extra paramaters/arguments.
-local function compose(...)
- local queue = {}
-
- local nodes = {...}
-
- -- Allow placement.foo + (var == 42 and placement.bar)
- if not nodes[2] then
- return nodes[1]
- end
-
- -- nodes[1] == self, nodes[2] == other
- for _, w in ipairs(nodes) do
- -- Build an execution queue
- if w.context and w.context == "compose" then
- for _, elem in ipairs(w.queue or {}) do
- table.insert(queue, elem)
- end
- else
- table.insert(queue, w)
- end
- end
-
- local ret
- ret = wrap_client(function(d, args, ...)
- local rets = {}
- local last_geo = nil
-
- -- As some functions may have to take into account results from
- -- previously execued ones, add the `composition_results` hint.
- args = setmetatable({composition_results=rets}, {__index=args})
-
- -- Only apply the geometry once, not once per chain node, to do this,
- -- Force the "pretend" argument and restore the original value for
- -- the last node.
- local attach_real = args.attach
- args.pretend = true
- args.attach = false
- args.offset = {}
-
- for k, f in ipairs(queue) do
- if k == #queue then
- -- Let them fallback to the parent table
- args.pretend = nil
- args.offset = nil
- end
-
- local r = {f(d, args, ...)}
- last_geo = r[1] or last_geo
- args.override_geometry = last_geo
-
- -- Keep the return value, store one per context
- if f.context then
- -- When 2 composition queue are executed, merge the return values
- if f.context == "compose" then
- for k2,v in pairs(r) do
- rets[k2] = v
- end
- else
- rets[f.context] = r
- end
- end
- end
-
- if attach_real then
- args.attach = true
- attach(d, ret, args)
- end
-
- return last_geo, rets
- end, "compose")
-
- ret.queue = queue
-
- return ret
-end
-
-wrap_client = function(f, context)
- return setmetatable(
- {
- is_placement= true,
- context = context,
- },
- {
- __call = function(_,...) return f(...) end,
- __add = compose, -- Composition is usually defined as +
- __mul = compose -- Make sense if you think of the functions as matrices
- }
- )
-end
-
-local placement_private = {}
-
--- The module is a proxy in front of the "real" functions.
--- This allow syntax like:
---
--- (awful.placement.no_overlap + awful.placement.no_offscreen)(c)
---
-placement = setmetatable({}, {
- __index = placement_private,
- __newindex = function(_, k, f)
- placement_private[k] = wrap_client(f, k)
- end
-})
-
--- 3x3 matrix of the valid sides and corners
-local corners3x3 = {{"top_left" , "top" , "top_right" },
- {"left" , nil , "right" },
- {"bottom_left", "bottom" , "bottom_right"}}
-
--- 2x2 matrix of the valid sides and corners
-local corners2x2 = {{"top_left" , "top_right" },
- {"bottom_left", "bottom_right"}}
-
--- Compute the new `x` and `y`.
--- The workarea position need to be applied by the caller
-local align_map = {
- top_left = function(_ , _ , _ , _ ) return {x=0 , y=0 } end,
- top_right = function(sw, _ , dw, _ ) return {x=sw-dw , y=0 } end,
- bottom_left = function(_ , sh, _ , 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(_ , sh, _ , 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, _ , dw, _ ) 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,
- centered = function(sw, sh, dw, dh) return {x=sw/2-dw/2, y=sh/2-dh/2} end,
- center_vertical = function(_ , sh, _ , dh) return {x= nil , y=sh-dh } end,
- center_horizontal = function(sw, _ , dw, _ ) return {x=sw/2-dw/2, y= nil } end,
-}
-
--- Some parameters to correctly compute the final size
-local resize_to_point_map = {
- -- Corners
- top_left = {p1= nil , p2={1,1}, x_only=false, y_only=false, align="bottom_right"},
- top_right = {p1={0,1} , p2= nil , x_only=false, y_only=false, align="bottom_left" },
- bottom_left = {p1= nil , p2={1,0}, x_only=false, y_only=false, align="top_right" },
- bottom_right = {p1={0,0} , p2= nil , x_only=false, y_only=false, align="top_left" },
-
- -- Sides
- left = {p1= nil , p2={1,1}, x_only=true , y_only=false, align="top_right" },
- right = {p1={0,0} , p2= nil , x_only=true , y_only=false, align="top_left" },
- top = {p1= nil , p2={1,1}, x_only=false, y_only=true , align="bottom_left" },
- bottom = {p1={0,0} , p2= nil , x_only=false, y_only=true , align="top_left" },
-}
-
--- Outer position matrix
--- 1=best case, 2=fallback
-local outer_positions = {
- left1 = function(r, w, _) return {x=r.x-w , y=r.y }, "down" end,
- left2 = function(r, w, h) return {x=r.x-w , y=r.y-h+r.height }, "up" end,
- right1 = function(r, _, _) return {x=r.x , y=r.y }, "down" end,
- right2 = function(r, _, h) return {x=r.x , y=r.y-h+r.height }, "up" end,
- top1 = function(r, _, h) return {x=r.x , y=r.y-h }, "right" end,
- top2 = function(r, w, h) return {x=r.x-w+r.width, y=r.y-h }, "left" end,
- bottom1 = function(r, _, _) return {x=r.x , y=r.y }, "right" end,
- bottom2 = function(r, w, _) return {x=r.x-w+r.width, y=r.y }, "left" end,
-}
-
---- Add a context to the arguments.
--- This function extend the argument table. The context is used by some
--- internal helper methods. If there already is a context, it has priority and
--- is kept.
-local function add_context(args, context)
- return setmetatable({context = (args or {}).context or context }, {__index=args})
-end
-
-local data = setmetatable({}, { __mode = 'k' })
-
---- Store a drawable geometry (per context) in a weak table.
--- @param d The drawin
--- @tparam string reqtype The context.
-local function store_geometry(d, reqtype)
- if not data[d] then data[d] = {} end
- if not data[d][reqtype] then data[d][reqtype] = {} end
- data[d][reqtype] = d:geometry()
- data[d][reqtype].screen = d.screen
- data[d][reqtype].border_width = d.border_width
-end
-
---- Get the margins and offset
--- @tparam table args The arguments
--- @treturn table The margins
--- @treturn table The offsets
-local function get_decoration(args)
- local offset = args.offset
-
- -- Offset are "blind" values added to the output
- offset = type(offset) == "number" and {
- x = offset,
- y = offset,
- width = offset,
- height = offset,
- } or args.offset or {}
-
- -- Margins are distances on each side to substract from the area`
- local m = type(args.margins) == "table" and args.margins or {
- left = args.margins or 0 , right = args.margins or 0,
- top = args.margins or 0 , bottom = args.margins or 0
- }
-
- return m, offset
-end
-
---- Apply some modifications before applying the new geometry.
--- @tparam table new_geo The new geometry
--- @tparam table args The common arguments
--- @tparam boolean force Always ajust the geometry, even in pretent mode. This
--- should only be used when returning the final geometry as it would otherwise
--- mess the pipeline.
--- @treturn table|nil The new geometry
-local function fix_new_geometry(new_geo, args, force)
- if (args.pretend and not force) or not new_geo then return nil end
-
- local m, offset = get_decoration(args)
-
- return {
- x = new_geo.x and (new_geo.x + (offset.x or 0) + (m.left or 0) ),
- y = new_geo.y and (new_geo.y + (offset.y or 0) + (m.top or 0) ),
- width = new_geo.width and math.max(
- 1, (new_geo.width + (offset.width or 0) - (m.left or 0) - (m.right or 0) )
- ),
- height = new_geo.height and math.max(
- 1, (new_geo.height + (offset.height or 0) - (m.top or 0) - (m.bottom or 0) )
- ),
- }
-end
-
--- Get the area covered by a drawin.
--- @param d The drawin
--- @tparam[opt=nil] table new_geo A new geometry
--- @tparam[opt=false] boolean ignore_border_width Ignore the border
--- @tparam table args the method arguments
--- @treturn The drawin's area.
-area_common = function(d, new_geo, ignore_border_width, args)
- -- The C side expect no arguments, nil isn't valid
- local geometry = new_geo and d:geometry(new_geo) or d:geometry()
- local border = ignore_border_width and 0 or d.border_width or 0
-
- -- When using the placement composition along with the "pretend"
- -- option, it is necessary to keep a "virtual" geometry.
- if args and args.override_geometry then
- geometry = util.table.clone(args.override_geometry)
- end
-
- geometry.width = geometry.width + 2 * border
- geometry.height = geometry.height + 2 * border
- return geometry
-end
-
---- Get (and optionally set) an object geometry.
--- Some elements, such as `mouse` and `screen` don't have a `:geometry()`
--- methods.
--- @param obj An object
--- @tparam table args the method arguments
--- @tparam[opt=nil] table new_geo A new geometry to replace the existing one
--- @tparam[opt=false] boolean ignore_border_width Ignore the border
--- @treturn table A table with *x*, *y*, *width* and *height*.
-local function geometry_common(obj, args, new_geo, ignore_border_width)
- -- Store the current geometry in a singleton-memento
- if args.store_geometry and new_geo and args.context then
- store_geometry(obj, args.context)
- end
-
- -- It's a mouse
- if obj.coords then
- local coords = fix_new_geometry(new_geo, args)
- and obj.coords(new_geo) or obj.coords()
- return {x=coords.x, y=coords.y, width=0, height=0}
- elseif obj.geometry then
- local geo = obj.geometry
-
- -- It is either a drawable or something that implement its API
- if type(geo) == "function" then
- local dgeo = area_common(
- obj, fix_new_geometry(new_geo, args), ignore_border_width, args
- )
-
- -- Apply the margins
- if args.margins then
- local delta = get_decoration(args)
-
- return {
- x = dgeo.x - (delta.left or 0),
- y = dgeo.y - (delta.top or 0),
- width = dgeo.width + (delta.left or 0) + (delta.right or 0),
- height = dgeo.height + (delta.top or 0) + (delta.bottom or 0),
- }
- end
-
- return dgeo
- end
-
- -- It is a screen, it doesn't support setting new sizes.
- return obj:get_bounding_geometry(args)
- else
- assert(false, "Invalid object")
- end
-end
-
---- Get the parent geometry from the standardized arguments API shared by all
--- `awful.placement` methods.
--- @param obj A screen or a drawable
--- @tparam table args the method arguments
--- @treturn table A table with *x*, *y*, *width* and *height*.
-local function get_parent_geometry(obj, args)
- -- Didable override_geometry, context and other to avoid mutating the state
- -- or using the wrong geo.
-
- if args.bounding_rect then
- return args.bounding_rect
- elseif args.parent then
- return geometry_common(args.parent, {})
- elseif obj.screen then
- return geometry_common(obj.screen, {
- honor_padding = args.honor_padding,
- honor_workarea = args.honor_workarea
- })
- else
- return geometry_common(capi.screen[capi.mouse.screen], args)
- end
-end
-
---- Move a point into an area.
--- This doesn't change the *width* and *height* values, allowing the target
--- area to be smaller than the source one.
--- @tparam table source The (larger) geometry to move `target` into
--- @tparam table target The area to move into `source`
--- @treturn table A table with *x* and *y* keys
-local function move_into_geometry(source, target)
- local ret = {x = target.x, y = target.y}
-
- -- Horizontally
- if ret.x < source.x then
- ret.x = source.x
- elseif ret.x > source.x + source.width then
- ret.x = source.x + source.width - 1
- end
-
- -- Vertically
- if ret.y < source.y then
- ret.y = source.y
- elseif ret.y > source.y + source.height then
- ret.y = source.y + source.height - 1
- end
-
- return ret
-end
-
--- Update the workarea
-wibox_update_strut = function(d, position, args)
- -- If the drawable isn't visible, remove the struts
- if not d.visible then
- d:struts { left = 0, right = 0, bottom = 0, top = 0 }
- return
- end
-
- -- Detect horizontal or vertical drawables
- local geo = area_common(d)
- local vertical = geo.width < geo.height
-
- -- Look into the `position` string to find the relevants sides to crop from
- -- the workarea
- local struts = { left = 0, right = 0, bottom = 0, top = 0 }
-
- local m = get_decoration(args)
-
- if vertical then
- for _, v in ipairs {"right", "left"} do
- if (not position) or position:match(v) then
- struts[v] = geo.width + m[v]
- end
- end
- else
- for _, v in ipairs {"top", "bottom"} do
- if (not position) or position:match(v) then
- struts[v] = geo.height + m[v]
- end
- end
- end
-
- -- Update the workarea
- d:struts(struts)
-end
-
--- Pin a drawable to a placement function.
--- Automatically update the position when the size change.
--- All other arguments will be passed to the `position` function (if any)
--- @tparam[opt=client.focus] drawable d A drawable (like `client`, `mouse`
--- or `wibox`)
--- @param position_f A position name (see `align`) or a position function
--- @tparam[opt={}] table args Other arguments
-attach = function(d, position_f, args)
- args = args or {}
-
- if args.pretend then return end
-
- if not args.attach then return end
-
- -- Avoid a connection loop
- args = setmetatable({attach=false}, {__index=args})
-
- d = d or capi.client.focus
- if not d then return end
-
- if type(position_f) == "string" then
- position_f = placement[position_f]
- end
-
- if not position_f then return end
-
- -- If there is multiple attached function, there is an high risk of infinite
- -- loop. While some combinaisons are harmless, other are very hard to debug.
- --
- -- Use the placement composition to build explicit multi step attached
- -- placement functions.
- if d.detach_callback then
- d.detach_callback()
- d.detach_callback = nil
- end
-
- local function tracker()
- position_f(d, args)
- end
-
- d:connect_signal("property::width" , tracker)
- d:connect_signal("property::height" , tracker)
- d:connect_signal("property::border_width", tracker)
-
- local function tracker_struts()
- --TODO this is too fragile and doesn't work with all methods.
- wibox_update_strut(d, d.position or reverse_align_map[position_f], args)
- end
-
- local parent = args.parent or d.screen
-
- if args.update_workarea then
- d:connect_signal("property::geometry" , tracker_struts)
- d:connect_signal("property::visible" , tracker_struts)
- capi.client.connect_signal("property::struts", tracker_struts)
-
- tracker_struts()
- elseif parent == d.screen then
- if args.honor_workarea then
- parent:connect_signal("property::workarea", tracker)
- end
-
- if args.honor_padding then
- parent:connect_signal("property::padding", tracker)
- end
- end
-
- -- If there is a parent drawable, screen, also track it.
- -- Note that tracking the mouse is not supported
- if parent and parent.connect_signal then
- parent:connect_signal("property::geometry" , tracker)
- end
-
- -- Create a way to detach a placement function
- function d.detach_callback()
- d:disconnect_signal("property::width" , tracker)
- d:disconnect_signal("property::height" , tracker)
- d:disconnect_signal("property::border_width", tracker)
- if parent then
- parent:disconnect_signal("property::geometry" , tracker)
-
- if parent == d.screen then
- if args.honor_workarea then
- parent:disconnect_signal("property::workarea", tracker)
- end
-
- if args.honor_padding then
- parent:disconnect_signal("property::padding", tracker)
- end
- end
- end
-
- if args.update_workarea then
- d:disconnect_signal("property::geometry" , tracker_struts)
- d:disconnect_signal("property::visible" , tracker_struts)
- capi.client.disconnect_signal("property::struts", tracker_struts)
- end
- end
-end
-
--- Convert 2 points into a rectangle
-local function rect_from_points(p1x, p1y, p2x, p2y)
- return {
- x = p1x,
- y = p1y,
- width = p2x - p1x,
- height = p2y - p1y,
- }
-end
-
--- Convert a rectangle and matrix info into a point
-local function rect_to_point(rect, corner_i, corner_j)
- return {
- x = rect.x + corner_i * math.floor(rect.width ),
- y = rect.y + corner_j * math.floor(rect.height),
- }
-end
-
--- Create a pair of rectangles used to set the relative areas.
--- v=vertical, h=horizontal
-local function get_cross_sections(abs_geo, mode)
- if not mode or mode == "cursor" then
- -- A 1px cross section centered around the mouse position
- local coords = capi.mouse.coords()
- return {
- h = {
- x = abs_geo.drawable_geo.x ,
- y = coords.y ,
- width = abs_geo.drawable_geo.width ,
- height = 1 ,
- },
- v = {
- x = coords.x ,
- y = abs_geo.drawable_geo.y ,
- width = 1 ,
- height = abs_geo.drawable_geo.height,
- }
- }
- elseif mode == "geometry" then
- -- The widget geometry extended to reach the end of the drawable
-
- return {
- h = {
- x = abs_geo.drawable_geo.x ,
- y = abs_geo.y ,
- width = abs_geo.drawable_geo.width ,
- height = abs_geo.height ,
- },
- v = {
- x = abs_geo.x ,
- y = abs_geo.drawable_geo.y ,
- width = abs_geo.width ,
- height = abs_geo.drawable_geo.height,
- }
- }
- elseif mode == "cursor_inside" then
- -- A 1x1 rectangle centered around the mouse position
-
- local coords = capi.mouse.coords()
- coords.width,coords.height = 1,1
- return {h=coords, v=coords}
- elseif mode == "geometry_inside" then
- -- The widget absolute geometry, unchanged
-
- return {h=abs_geo, v=abs_geo}
- end
-end
-
--- When a rectangle is embedded into a bigger one, get the regions around
--- the outline of the bigger rectangle closest to the smaller one (on each side)
-local function get_relative_regions(geo, mode, is_absolute)
-
- -- Use the mouse position and the wibox/client under it
- if not geo then
- local draw = capi.mouse.current_wibox
- geo = draw and draw:geometry() or capi.mouse.coords()
- geo.drawable = draw
- elseif is_absolute then
- -- Some signals are a bit inconsistent in their arguments convention.
- -- This little hack tries to mitigate the issue.
-
- geo.drawable = geo -- is a wibox or client, geometry and object are one
- -- and the same.
- elseif (not geo.drawable) and geo.x and geo.width then
- local coords = capi.mouse.coords()
-
- -- Check if 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 = capi.mouse.current_wibox
- end
-
- -- Maybe there is a client
- if (not geo.drawable) and capi.mouse.current_client then
- geo.drawable = capi.mouse.current_client
- end
- end
-
- -- Get the drawable geometry
- local dpos = geo.drawable and (
- geo.drawable.drawable and
- geo.drawable.drawable:geometry()
- or geo.drawable:geometry()
- ) or {x=0, y=0}
-
- -- Compute the absolute widget geometry
- local abs_widget_geo = is_absolute and geo or {
- x = dpos.x + geo.x ,
- y = dpos.y + geo.y ,
- width = geo.width ,
- height = geo.height ,
- drawable = geo.drawable ,
- }
-
- abs_widget_geo.drawable_geo = geo.drawable and dpos or geo
-
- -- Get the point for comparison.
- local center_point = mode:match("cursor") 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 widget regions for both axis
- local cs = get_cross_sections(abs_widget_geo, mode)
-
- -- Get the 4 closest points from `center_point` around the wibox
- local regions = {
- left = {x = cs.h.x , y = cs.h.y },
- right = {x = cs.h.x+cs.h.width, y = cs.h.y },
- top = {x = cs.v.x , y = cs.v.y },
- bottom = {x = cs.v.x , y = cs.v.y+cs.v.height},
- }
-
- -- Assume the section is part of a single screen until someone complains.
- -- It is much faster to compute and getting it wrong probably has no side
- -- effects.
- local s = geo.drawable and geo.drawable.screen or a_screen.getbycoord(
- center_point.x,
- center_point.y
- )
-
- -- Compute the distance (dp) between the `center_point` and the sides.
- -- This is only relevant for "cursor" and "cursor_inside" modes.
- for _, v in pairs(regions) do
- local dx, dy = v.x - center_point.x, v.y - center_point.y
-
- v.distance = math.sqrt(dx*dx + dy*dy)
- v.width = cs.v.width
- v.height = cs.h.height
- v.screen = capi.screen[s]
- end
-
- return regions
-end
-
--- Check if the proposed geometry fits the screen
-local function fit_in_bounding(obj, geo, args)
- local sgeo = get_parent_geometry(obj, args)
- 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 fits, otherwise it will be cropped.
- return geo2.width == geo.width and geo2.height == geo.height
-end
-
---- Move a drawable to the closest corner of the parent geometry (such as the
--- screen).
---
--- Valid arguments include the common ones and:
---
--- * **include_sides**: Also include the left, right, top and bottom positions
---
---
---
---![Usage example](../images/AUTOGEN_awful_placement_closest_mouse.svg)
---
---**Usage example output**:
---
--- Closest corner: top_left
---
---
--- @usage
--- -- Move the mouse to the closest corner of the focused client
---awful.placement.closest_corner(mouse, {include_sides=true, parent=c})
--- -- It is possible to emulate the mouse API to get the closest corner of
--- -- random area
---local _, corner = awful.placement.closest_corner(
--- {coords=function() return {x = 100, y=100} end},
--- {include_sides = true, bounding_rect = {x=0, y=0, width=200, height=200}}
---)
---print('Closest corner:', corner)
--- @tparam[opt=client.focus] drawable d A drawable (like `client`, `mouse`
--- or `wibox`)
--- @tparam[opt={}] table args The arguments
--- @treturn table The new geometry
--- @treturn string The corner name
-function placement.closest_corner(d, args)
- args = add_context(args, "closest_corner")
- d = d or capi.client.focus
-
- local sgeo = get_parent_geometry(d, args)
- local dgeo = geometry_common(d, args)
-
- local pos = move_into_geometry(sgeo, dgeo)
-
- local corner_i, corner_j, n
-
- -- Use the product of 3 to get the closest point in a NxN matrix
- local function f(_n, mat)
- n = _n
- -- The +1 is required to avoid a rounding error when
- -- pos.x == sgeo.x+sgeo.width
- corner_i = -math.ceil( ( (sgeo.x - pos.x) * n) / (sgeo.width + 1))
- corner_j = -math.ceil( ( (sgeo.y - pos.y) * n) / (sgeo.height + 1))
- return mat[corner_j + 1][corner_i + 1]
- end
-
- -- Turn the area into a grid and snap to the cloest point. This size of the
- -- grid will increase the accuracy. A 2x2 matrix only include the corners,
- -- at 3x3, this include the sides too technically, a random size would work,
- -- but without corner names.
- local grid_size = args.include_sides and 3 or 2
-
- -- If the point is in the center, use the closest corner
- local corner = grid_size == 3 and f(3, corners3x3) or f(2, corners2x2)
-
- -- Transpose the corner back to the original size
- local new_args = setmetatable({position = corner}, {__index=args})
- local ngeo = placement_private.align(d, new_args)
-
- return fix_new_geometry(ngeo, args, true), corner
-end
-
---- Place the client so no part of it will be outside the screen (workarea).
---
---
---![Usage example](../images/AUTOGEN_awful_placement_no_offscreen.svg)
---
---**Usage example output**:
---
--- Before: x=-30, y=-30, width=100, height=100
--- After: x=10, y=10, width=100, height=100
---
---
--- @usage
---awful.placement.no_offscreen(c)--, {honor_workarea=true, margins=40})
--- @client c The client.
--- @tparam[opt=client's screen] integer screen The screen.
--- @treturn table The new client geometry.
-function placement.no_offscreen(c, screen)
- --HACK necessary for composition to work. The API will be changed soon
- if type(screen) == "table" then
- screen = nil
- end
-
- c = c or capi.client.focus
- local geometry = area_common(c)
- screen = get_screen(screen or c.screen or a_screen.getbycoord(geometry.x, geometry.y))
- local screen_geometry = screen.workarea
-
- if geometry.x + geometry.width > screen_geometry.x + screen_geometry.width then
- geometry.x = screen_geometry.x + screen_geometry.width - geometry.width
- end
- if geometry.x < screen_geometry.x then
- geometry.x = screen_geometry.x
- end
-
- if geometry.y + geometry.height > screen_geometry.y + screen_geometry.height then
- geometry.y = screen_geometry.y + screen_geometry.height - geometry.height
- end
- if geometry.y < screen_geometry.y then
- geometry.y = screen_geometry.y
- end
-
- return c:geometry {
- x = geometry.x,
- y = geometry.y
- }
-end
-
---- Place the client where there's place available with minimum overlap.
---
---
---![Usage example](../images/AUTOGEN_awful_placement_no_overlap.svg)
---
--- @usage
---awful.placement.no_overlap(client.focus)
---local x,y = screen[4].geometry.x, screen[4].geometry.y
--- @param c The client.
--- @treturn table The new geometry
-function placement.no_overlap(c)
- c = c or capi.client.focus
- local geometry = area_common(c)
- local screen = get_screen(c.screen or a_screen.getbycoord(geometry.x, geometry.y))
- local cls = client.visible(screen)
- local curlay = layout.get()
- local areas = { screen.workarea }
- for _, cl in pairs(cls) do
- if cl ~= c and cl.type ~= "desktop" and (cl.floating or curlay == layout.suit.floating) then
- areas = grect.area_remove(areas, area_common(cl))
- end
- end
-
- -- Look for available space
- local found = false
- local new = { x = geometry.x, y = geometry.y, width = 0, height = 0 }
- for _, r in ipairs(areas) do
- if r.width >= geometry.width
- and r.height >= geometry.height
- and r.width * r.height > new.width * new.height then
- found = true
- new = r
- -- Check if the client's current position is available
- -- and prefer that one (why move it around pointlessly?)
- if geometry.x >= r.x
- and geometry.y >= r.y
- and geometry.x + geometry.width <= r.x + r.width
- and geometry.y + geometry.height <= r.y + r.height then
- new.x = geometry.x
- new.y = geometry.y
- end
- end
- end
-
- -- We did not find an area with enough space for our size:
- -- just take the biggest available one and go in.
- -- This makes sure to have the whole screen's area in case it has been
- -- removed.
- if not found then
- if #areas == 0 then
- areas = { screen.workarea }
- end
- for _, r in ipairs(areas) do
- if r.width * r.height > new.width * new.height then
- new = r
- end
- end
- end
-
- -- Restore height and width
- new.width = geometry.width
- new.height = geometry.height
-
- return c:geometry({ x = new.x, y = new.y })
-end
-
---- Place the client under the mouse.
---
---
---![Usage example](../images/AUTOGEN_awful_placement_under_mouse.svg)
---
--- @usage
---awful.placement.under_mouse(client.focus)
--- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
--- @tparam[opt={}] table args Other arguments
--- @treturn table The new geometry
-function placement.under_mouse(d, args)
- args = add_context(args, "under_mouse")
- d = d or capi.client.focus
-
- local m_coords = capi.mouse.coords()
-
- local ngeo = geometry_common(d, args)
- ngeo.x = math.floor(m_coords.x - ngeo.width / 2)
- ngeo.y = math.floor(m_coords.y - ngeo.height / 2)
-
- local bw = d.border_width or 0
- ngeo.width = ngeo.width - 2*bw
- ngeo.height = ngeo.height - 2*bw
-
- geometry_common(d, args, ngeo)
-
- return fix_new_geometry(ngeo, args, true)
-end
-
---- Place the client next to the mouse.
---
--- It will place `c` next to the mouse pointer, trying the following positions
--- in this order: right, left, above and below.
---
---
---![Usage example](../images/AUTOGEN_awful_placement_next_to_mouse.svg)
---
--- @usage
---awful.placement.next_to_mouse(client.focus)
--- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
--- @tparam[opt={}] table args Other arguments
--- @treturn table The new geometry
-function placement.next_to_mouse(d, args)
- if type(args) == "number" then
- util.deprecate(
- "awful.placement.next_to_mouse offset argument is deprecated"..
- " use awful.placement.next_to_mouse(c, {offset={x=...}})"
- )
- args = nil
- end
-
- local old_args = args or {}
-
- args = add_context(args, "next_to_mouse")
- d = d or capi.client.focus
-
- local sgeo = get_parent_geometry(d, args)
-
- args.pretend = true
- args.parent = capi.mouse
-
- local ngeo = placement.left(d, args)
-
- if ngeo.x + ngeo.width > sgeo.x+sgeo.width then
- ngeo = placement.right(d, args)
- else
- -- It is _next_ to mouse, not under_mouse
- ngeo.x = ngeo.x+1
- end
-
- args.pretend = old_args.pretend
-
- geometry_common(d, args, ngeo)
-
- attach(d, placement.next_to_mouse, old_args)
-
- return fix_new_geometry(ngeo, args, true)
-end
-
---- Resize the drawable to the cursor.
---
--- Valid args:
---
--- * *axis*: The axis (vertical or horizontal). If none is
--- specified, then the drawable will be resized on both axis.
---
---
---
---![Usage example](../images/AUTOGEN_awful_placement_resize_to_mouse.svg)
---
--- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
--- @tparam[opt={}] table args Other arguments
--- @treturn table The new geometry
-function placement.resize_to_mouse(d, args)
- d = d or capi.client.focus
- args = add_context(args, "resize_to_mouse")
-
- local coords = capi.mouse.coords()
- local ngeo = geometry_common(d, args)
- local h_only = args.axis == "horizontal"
- local v_only = args.axis == "vertical"
-
- -- To support both growing and shrinking the drawable, it is necessary
- -- to decide to use either "north or south" and "east or west" directions.
- -- Otherwise, the result will always be 1x1
- local _, closest_corner = placement.closest_corner(capi.mouse, {
- parent = d,
- pretend = true,
- include_sides = args.include_sides or false,
- })
-
- -- Given "include_sides" wasn't set, it will always return a name
- -- with the 2 axis. If only one axis is needed, adjust the result
- if h_only then
- closest_corner = closest_corner:match("left") or closest_corner:match("right")
- elseif v_only then
- closest_corner = closest_corner:match("top") or closest_corner:match("bottom")
- end
-
- -- Use p0 (mouse), p1 and p2 to create a rectangle
- local pts = resize_to_point_map[closest_corner]
- local p1 = pts.p1 and rect_to_point(ngeo, pts.p1[1], pts.p1[2]) or coords
- local p2 = pts.p2 and rect_to_point(ngeo, pts.p2[1], pts.p2[2]) or coords
-
- -- Create top_left and bottom_right points, convert to rectangle
- ngeo = rect_from_points(
- pts.y_only and ngeo.x or math.min(p1.x, p2.x),
- pts.x_only and ngeo.y or math.min(p1.y, p2.y),
- pts.y_only and ngeo.x + ngeo.width or math.max(p2.x, p1.x),
- pts.x_only and ngeo.y + ngeo.height or math.max(p2.y, p1.y)
- )
-
- local bw = d.border_width or 0
-
- for _, a in ipairs {"width", "height"} do
- ngeo[a] = ngeo[a] - 2*bw
- end
-
- -- Now, correct the geometry by the given size_hints offset
- if d.apply_size_hints then
- local w, h = d:apply_size_hints(
- ngeo.width,
- ngeo.height
- )
- local offset = align_map[pts.align](w, h, ngeo.width, ngeo.height)
- ngeo.x = ngeo.x - offset.x
- ngeo.y = ngeo.y - offset.y
- end
-
- geometry_common(d, args, ngeo)
-
- return fix_new_geometry(ngeo, args, true)
-end
-
---- Move the drawable (client or wibox) `d` to a screen position or side.
---
--- Supported args.positions are:
---
--- * top_left
--- * top_right
--- * bottom_left
--- * bottom_right
--- * left
--- * right
--- * top
--- * bottom
--- * centered
--- * center_vertical
--- * center_horizontal
---
---
---
---![Usage example](../images/AUTOGEN_awful_placement_align.svg)
---
--- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
--- @tparam[opt={}] table args Other arguments
--- @treturn table The new geometry
-function placement.align(d, args)
- args = add_context(args, "align")
- d = d or capi.client.focus
-
- if not d or not args.position then return end
-
- local sgeo = get_parent_geometry(d, args)
- local dgeo = geometry_common(d, args)
- local bw = d.border_width or 0
-
- local pos = align_map[args.position](
- sgeo.width ,
- sgeo.height,
- dgeo.width ,
- dgeo.height
- )
-
- local ngeo = {
- x = (pos.x and math.ceil(sgeo.x + pos.x) or dgeo.x) ,
- y = (pos.y and math.ceil(sgeo.y + pos.y) or dgeo.y) ,
- width = math.ceil(dgeo.width ) - 2*bw,
- height = math.ceil(dgeo.height ) - 2*bw,
- }
-
- geometry_common(d, args, ngeo)
-
- attach(d, placement[args.position], args)
-
- return fix_new_geometry(ngeo, args, true)
-end
-
--- Add the alias functions
-for k in pairs(align_map) do
- placement[k] = function(d, args)
- args = add_context(args, k)
- args.position = k
- return placement_private.align(d, args)
- end
- reverse_align_map[placement[k]] = k
-end
-
--- Add the documentation for align alias
-
----
--- Align a client to the top left of the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_top_left.svg)
--- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
--- @tparam[opt={}] table args Other arguments')
--- @treturn table The new geometry
--- @name top_left
--- @class function
---
--- @usage
---awful.placement.top_left(client.focus)
-
----
--- Align a client to the top right of the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_top_right.svg)
--- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
--- @tparam[opt={}] table args Other arguments')
--- @treturn table The new geometry
--- @name top_right
--- @class function
---
--- @usage
---awful.placement.top_right(client.focus)
-
----
--- Align a client to the bottom left of the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_bottom_left.svg)
--- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
--- @tparam[opt={}] table args Other arguments')
--- @treturn table The new geometry
--- @name bottom_left
--- @class function
---
--- @usage
---awful.placement.bottom_left(client.focus)
-
----
--- Align a client to the bottom right of the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_bottom_right.svg)
--- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
--- @tparam[opt={}] table args Other arguments')
--- @treturn table The new geometry
--- @name bottom_right
--- @class function
---
--- @usage
---awful.placement.bottom_right(client.focus)
-
----
--- Align a client to the left of the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_left.svg)
--- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
--- @tparam[opt={}] table args Other arguments')
--- @treturn table The new geometry
--- @name left
--- @class function
---
--- @usage
---awful.placement.left(client.focus)
-
----
--- Align a client to the right of the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_right.svg)
--- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
--- @tparam[opt={}] table args Other arguments')
--- @treturn table The new geometry
--- @name right
--- @class function
---
--- @usage
---awful.placement.right(client.focus)
-
----
--- Align a client to the top of the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_top.svg)
--- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
--- @tparam[opt={}] table args Other arguments')
--- @treturn table The new geometry
--- @name top
--- @class function
---
--- @usage
---awful.placement.top(client.focus)
---assert(c.x == screen[1].geometry.width/2-40/2-c.border_width)
-
----
--- Align a client to the bottom of the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_bottom.svg)
--- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
--- @tparam[opt={}] table args Other arguments')
--- @treturn table The new geometry
--- @name bottom
--- @class function
---
--- @usage
---awful.placement.bottom(client.focus)
-
----
--- Align a client to the center of the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_centered.svg)
--- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
--- @tparam[opt={}] table args Other arguments')
--- @treturn table The new geometry
--- @name centered
--- @class function
---
--- @usage
---awful.placement.centered(client.focus)
-
----
--- Align a client to the vertical center of the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_center_vertical.svg)
--- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
--- @tparam[opt={}] table args Other arguments')
--- @name center_vertical
--- @class function
---
--- @usage
---awful.placement.center_vertical(client.focus)
-
----
--- Align a client to the horizontal center left of the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_center_horizontal.svg)
--- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
--- @tparam[opt={}] table args Other arguments')
--- @treturn table The new geometry
--- @name center_horizontal
--- @class function
---
--- @usage
---awful.placement.center_horizontal(client.focus)
-
---- Stretch a drawable in a specific direction.
--- Valid args:
---
--- * **direction**: The stretch direction (*left*, *right*, *up*, *down*) or
--- a table with multiple directions.
---
---
---
---![Usage example](../images/AUTOGEN_awful_placement_stretch.svg)
---
--- @tparam[opt=client.focus] drawable d A drawable (like `client` or `wibox`)
--- @tparam[opt={}] table args The arguments
--- @treturn table The new geometry
-function placement.stretch(d, args)
- args = add_context(args, "stretch")
-
- d = d or capi.client.focus
- if not d or not args.direction then return end
-
- -- In case there is multiple directions, call `stretch` for each of them
- if type(args.direction) == "table" then
- for _, dir in ipairs(args.direction) do
- args.direction = dir
- placement_private.stretch(dir, args)
- end
- return
- end
-
- local sgeo = get_parent_geometry(d, args)
- local dgeo = geometry_common(d, args)
- local ngeo = geometry_common(d, args, nil, true)
- local bw = d.border_width or 0
-
- if args.direction == "left" then
- ngeo.x = sgeo.x
- ngeo.width = dgeo.width + (dgeo.x - ngeo.x)
- elseif args.direction == "right" then
- ngeo.width = sgeo.width - ngeo.x - 2*bw
- elseif args.direction == "up" then
- ngeo.y = sgeo.y
- ngeo.height = dgeo.height + (dgeo.y - ngeo.y)
- elseif args.direction == "down" then
- ngeo.height = sgeo.height - dgeo.y - 2*bw
- else
- assert(false)
- end
-
- -- Avoid negative sizes if args.parent isn't compatible
- ngeo.width = math.max(args.minimim_width or 1, ngeo.width )
- ngeo.height = math.max(args.minimim_height or 1, ngeo.height)
-
- geometry_common(d, args, ngeo)
-
- attach(d, placement["stretch_"..args.direction], args)
-
- return fix_new_geometry(ngeo, args, true)
-end
-
--- Add the alias functions
-for _,v in ipairs {"left", "right", "up", "down"} do
- placement["stretch_"..v] = function(d, args)
- args = add_context(args, "stretch_"..v)
- args.direction = v
- return placement_private.stretch(d, args)
- end
-end
-
----
--- Stretch the drawable to the left of the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_stretch_left.svg)
--- @tparam drawable d A drawable (like `client` or `wibox`)
--- @tparam[opt={}] table args Other arguments
--- @treturn table The new geometry
--- @name stretch_left
--- @class function
---
--- @usage
---placement.stretch_left(client.focus)
-
----
--- Stretch the drawable to the right of the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_stretch_right.svg)
--- @tparam drawable d A drawable (like `client` or `wibox`)
--- @tparam[opt={}] table args Other arguments
--- @treturn table The new geometry
--- @name stretch_right
--- @class function
---
--- @usage
---placement.stretch_right(client.focus)
-
----
--- Stretch the drawable to the top of the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_stretch_up.svg)
--- @tparam drawable d A drawable (like `client` or `wibox`)
--- @tparam[opt={}] table args Other arguments
--- @treturn table The new geometry
--- @name stretch_up
--- @class function
---
--- @usage
---placement.stretch_up(client.focus)
-
----
--- Stretch the drawable to the bottom of the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_stretch_down.svg)
--- @tparam drawable d A drawable (like `client` or `wibox`)
--- @tparam[opt={}] table args Other arguments
--- @treturn table The new geometry
--- @name stretch_down
--- @class function
---
--- @usage
---placement.stretch_down(client.focus)
-
---- Maximize a drawable horizontally, vertically or both.
--- Valid args:
---
--- * *axis*:The axis (vertical or horizontal). If none is
--- specified, then the drawable will be maximized on both axis.
---
---
---
---![Usage example](../images/AUTOGEN_awful_placement_maximize.svg)
---
--- @tparam[opt=client.focus] drawable d A drawable (like `client` or `wibox`)
--- @tparam[opt={}] table args The arguments
--- @treturn table The new geometry
-function placement.maximize(d, args)
- args = add_context(args, "maximize")
- d = d or capi.client.focus
-
- if not d then return end
-
- local sgeo = get_parent_geometry(d, args)
- local ngeo = geometry_common(d, args, nil, true)
- local bw = d.border_width or 0
-
- if (not args.axis) or args.axis :match "vertical" then
- ngeo.y = sgeo.y
- ngeo.height = sgeo.height - 2*bw
- end
-
- if (not args.axis) or args.axis :match "horizontal" then
- ngeo.x = sgeo.x
- ngeo.width = sgeo.width - 2*bw
- end
-
- geometry_common(d, args, ngeo)
-
- attach(d, placement.maximize, args)
-
- return fix_new_geometry(ngeo, args, true)
-end
-
--- Add the alias functions
-for _, v in ipairs {"vertically", "horizontally"} do
- placement["maximize_"..v] = function(d2, args)
- args = add_context(args, "maximize_"..v)
- args.axis = v
- return placement_private.maximize(d2, args)
- end
-end
-
----
--- Vetically maximize the drawable in the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_maximize_vertically.svg)
--- @tparam drawable d A drawable (like `client` or `wibox`)
--- @tparam[opt={}] table args Other arguments')
--- @name maximize_vertically
--- @class function
---
--- @usage
---placement.maximize_vertically(c)
-
----
--- Horizontally maximize the drawable in the parent area.
---
---![Usage example](../images/AUTOGEN_awful_placement_maximize_horizontally.svg)
--- @tparam drawable d A drawable (like `client` or `wibox`)
--- @tparam[opt={}] table args Other arguments')
--- @name maximize_horizontally
--- @class function
---
--- @usage
---placement.maximize_horizontally(c)
-
---- Scale the drawable by either a relative or absolute percent.
---
--- Valid args:
---
--- **to_percent** : A number between 0 and 1. It represent a percent related to
--- the parent geometry.
--- **by_percent** : A number between 0 and 1. It represent a percent related to
--- the current size.
--- **direction**: Nothing or "left", "right", "up", "down".
---
--- @tparam[opt=client.focus] drawable d A drawable (like `client` or `wibox`)
--- @tparam[opt={}] table args The arguments
--- @treturn table The new geometry
-function placement.scale(d, args)
- args = add_context(args, "scale_to_percent")
- d = d or capi.client.focus
-
- local to_percent = args.to_percent
- local by_percent = args.by_percent
-
- local percent = to_percent or by_percent
-
- local direction = args.direction
-
- local sgeo = get_parent_geometry(d, args)
- local ngeo = geometry_common(d, args, nil)
-
- local old_area = {width = ngeo.width, height = ngeo.height}
-
- if (not direction) or direction == "left" or direction == "right" then
- ngeo.width = (to_percent and sgeo or ngeo).width*percent
-
- if direction == "left" then
- ngeo.x = ngeo.x - (ngeo.width - old_area.width)
- end
- end
-
- if (not direction) or direction == "up" or direction == "down" then
- ngeo.height = (to_percent and sgeo or ngeo).height*percent
-
- if direction == "up" then
- ngeo.y = ngeo.y - (ngeo.height - old_area.height)
- end
- end
-
- local bw = d.border_width or 0
- ngeo.width = ngeo.width - 2*bw
- ngeo.height = ngeo.height - 2*bw
-
- geometry_common(d, args, ngeo)
-
- attach(d, placement.maximize, args)
-
- return fix_new_geometry(ngeo, args, true)
-end
-
---- Move a drawable to a relative position next to another one.
---
--- The `args.preferred_positions` look like this:
---
--- {"top", "right", "left", "bottom"}
---
--- In that case, if there is room on the top of the geomtry, then it will have
--- priority, followed by all the others, in order.
---
--- @tparam drawable d A wibox or client
--- @tparam table args
--- @tparam string args.mode The mode
--- @tparam string args.preferred_positions The preferred positions (in order)
--- @tparam string args.geometry A geometry inside the other drawable
--- @treturn table The new geometry
--- @treturn string The choosen position
--- @treturn string The choosen direction
-function placement.next_to(d, args)
- args = add_context(args, "next_to")
- d = d or capi.client.focus
-
- local preferred_positions = {}
-
- if #(args.preferred_positions or {}) then
- for k, v in ipairs(args.preferred_positions) do
- preferred_positions[v] = k
- end
- end
-
- local dgeo = geometry_common(d, args)
- local pref_idx, pref_name = 99, nil
- local mode,wgeo = args.mode
-
- if args.geometry then
- mode = "geometry"
- wgeo = args.geometry
- else
- local pos = capi.mouse.current_widget_geometry
-
- if pos then
- wgeo, mode = pos, "cursor"
- elseif capi.mouse.current_client then
- wgeo, mode = capi.mouse.current_client:geometry(), "cursor"
- end
- end
-
- if not wgeo then return end
-
- -- See get_relative_regions comments
- local is_absolute = wgeo.ontop ~= nil
-
- local regions = get_relative_regions(wgeo, mode, is_absolute)
-
- -- Check each possible slot around the drawable (8 total), see what fits
- -- and order them by preferred_positions
- local does_fit = {}
- for k,v in pairs(regions) do
- local geo, dir = outer_positions[k.."1"](v, dgeo.width, dgeo.height)
- geo.width, geo.height = dgeo.width, dgeo.height
- local fit = fit_in_bounding(v.screen, geo, args)
-
- -- Try the other compatible geometry
- if not fit then
- geo, dir = outer_positions[k.."2"](v, dgeo.width, dgeo.height)
- geo.width, geo.height = dgeo.width, dgeo.height
- fit = fit_in_bounding(v.screen, geo, args)
- end
-
- does_fit[k] = fit and {geo, dir} 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 ngeo, dir = unpack(does_fit[pos_name] or {}) --FIXME why does this happen
-
- geometry_common(d, args, ngeo)
-
- attach(d, placement.next_to, args)
-
- return fix_new_geometry(ngeo, args, true), pos_name, dir
-end
-
---- Restore the geometry.
--- @tparam[opt=client.focus] drawable d A drawable (like `client` or `wibox`)
--- @tparam[opt={}] table args The arguments
--- @treturn boolean If the geometry was restored
-function placement.restore(d, args)
- if not args or not args.context then return false end
- d = d or capi.client.focus
-
- if not data[d] then return false end
-
- local memento = data[d][args.context]
-
- if not memento then return false end
-
- memento.screen = nil --TODO use it
-
- d.border_width = memento.border_width
-
- d:geometry(memento)
- return true
-end
-
-return placement
-
--- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80