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, 1694 insertions, 0 deletions
diff --git a/awesome/lib/awful/placement.lua b/awesome/lib/awful/placement.lua
new file mode 100644
index 0000000..d288f49
--- /dev/null
+++ b/awesome/lib/awful/placement.lua
@@ -0,0 +1,1694 @@
+---------------------------------------------------------------------------
+--- 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