awesome/lib/awful/mouse/resize.lua

231 lines
7.1 KiB
Lua

---------------------------------------------------------------------------
--- An extandable mouse resizing handler.
--
-- This module offer a resizing and moving mechanism for drawable such as
-- clients and wiboxes.
--
-- @author Emmanuel Lepage Vallee <elv1313@gmail.com>
-- @copyright 2016 Emmanuel Lepage Vallee
-- @release @AWESOME_VERSION@
-- @submodule mouse
---------------------------------------------------------------------------
local aplace = require("awful.placement")
local capi = {mousegrabber = mousegrabber}
local beautiful = require("beautiful")
local module = {}
local mode = "live"
local req = "request::geometry"
local callbacks = {enter={}, move={}, leave={}}
local cursors = {
["mouse.move" ] = "fleur",
["mouse.resize" ] = "cross",
["mouse.resize_left" ] = "sb_h_double_arrow",
["mouse.resize_right" ] = "sb_h_double_arrow",
["mouse.resize_top" ] = "sb_v_double_arrow",
["mouse.resize_bottom" ] = "sb_v_double_arrow",
["mouse.resize_top_left" ] = "top_left_corner",
["mouse.resize_top_right" ] = "top_right_corner",
["mouse.resize_bottom_left" ] = "bottom_left_corner",
["mouse.resize_bottom_right"] = "bottom_right_corner",
}
--- The resize cursor name.
-- @beautiful beautiful.cursor_mouse_resize
-- @tparam[opt=cross] string cursor
--- The move cursor name.
-- @beautiful beautiful.cursor_mouse_move
-- @tparam[opt=fleur] string cursor
--- Set the resize mode.
-- The available modes are:
--
-- * **live**: Resize the layout everytime the mouse move
-- * **after**: Resize the layout only when the mouse is released
--
-- Some clients, such as XTerm, may lose information if resized too often.
--
-- @function awful.mouse.resize.set_mode
-- @tparam string m The mode
function module.set_mode(m)
assert(m == "live" or m == "after")
mode = m
end
--- Add a initialization callback.
-- This callback will be executed before the mouse grabbing start
-- @function awful.mouse.resize.add_enter_callback
-- @tparam function cb The callback (or nil)
-- @tparam[default=other] string context The callback context
function module.add_enter_callback(cb, context)
context = context or "other"
callbacks.enter[context] = callbacks.enter[context] or {}
table.insert(callbacks.enter[context], cb)
end
--- Add a "move" callback.
-- This callback is executed in "after" mode (see `set_mode`) instead of
-- applying the operation.
-- @function awful.mouse.resize.add_move_callback
-- @tparam function cb The callback (or nil)
-- @tparam[default=other] string context The callback context
function module.add_move_callback(cb, context)
context = context or "other"
callbacks.move[context] = callbacks.move[context] or {}
table.insert(callbacks.move[context], cb)
end
--- Add a "leave" callback
-- This callback is executed just before the `mousegrabber` stop
-- @function awful.mouse.resize.add_leave_callback
-- @tparam function cb The callback (or nil)
-- @tparam[default=other] string context The callback context
function module.add_leave_callback(cb, context)
context = context or "other"
callbacks.leave[context] = callbacks.leave[context] or {}
table.insert(callbacks.leave[context], cb)
end
-- Resize, the drawable.
--
-- Valid `args` are:
--
-- * *enter_callback*: A function called before the `mousegrabber` start
-- * *move_callback*: A function called when the mouse move
-- * *leave_callback*: A function called before the `mousegrabber` is released
-- * *mode*: The resize mode
--
-- @function awful.mouse.resize
-- @tparam client client A client
-- @tparam[default=mouse.resize] string context The resizing context
-- @tparam[opt={}] table args A set of `awful.placement` arguments
local function handler(_, client, context, args) --luacheck: no unused_args
args = args or {}
context = context or "mouse.resize"
local placement = args.placement
if type(placement) == "string" and aplace[placement] then
placement = aplace[placement]
end
-- Extend the table with the default arguments
args = setmetatable(
{
placement = placement or aplace.resize_to_mouse,
mode = args.mode or mode,
pretend = true,
},
{__index = args or {}}
)
local geo
for _, cb in ipairs(callbacks.enter[context] or {}) do
geo = cb(client, args)
if geo == false then
return false
end
end
if args.enter_callback then
geo = args.enter_callback(client, args)
if geo == false then
return false
end
end
geo = nil
-- Select the cursor
local tcontext = context:gsub('[.]', '_')
local corner = args.corner and ("_".. args.corner) or ""
local cursor = beautiful["cursor_"..tcontext]
or cursors[context..corner]
or cursors[context]
or "fleur"
-- Execute the placement function and use request::geometry
capi.mousegrabber.run(function (_mouse)
if not client.valid then return end
-- Resize everytime the mouse move (default behavior)
if args.mode == "live" then
-- Get the new geometry
geo = setmetatable(args.placement(client, args),{__index=args})
end
-- Execute the move callbacks. This can be used to add features such as
-- snap or adding fancy graphical effects.
for _, cb in ipairs(callbacks.move[context] or {}) do
-- If something is returned, assume it is a modified geometry
geo = cb(client, geo, args) or geo
if geo == false then
return false
end
end
if args.move_callback then
geo = args.move_callback(client, geo, args)
if geo == false then
return false
end
end
-- In case it was modified
setmetatable(geo,{__index=args})
if args.mode == "live" then
-- Ask the resizing handler to resize the client
client:emit_signal( req, context, geo)
end
-- Quit when the button is released
for _,v in pairs(_mouse.buttons) do
if v then return true end
end
-- Only resize after the mouse is released, this avoid losing content
-- in resize sensitive apps such as XTerm or allow external modules
-- to implement custom resizing
if args.mode == "after" then
-- Get the new geometry
geo = args.placement(client, args)
-- Ask the resizing handler to resize the client
client:emit_signal( req, context, geo)
end
geo = nil
for _, cb in ipairs(callbacks.leave[context] or {}) do
geo = cb(client, geo, args)
end
if args.leave_callback then
geo = args.leave_callback(client, geo, args)
end
if not geo then return false end
-- In case it was modified
setmetatable(geo,{__index=args})
client:emit_signal( req, context, geo)
return false
end, cursor)
end
return setmetatable(module, {__call=handler})