summaryrefslogtreecommitdiff
path: root/awesome/lib/awful/mouse/resize.lua
blob: edd278dc02e4387d206c5422e90abf613930ab0d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
---------------------------------------------------------------------------
--- 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
-- @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 an initialization callback.
-- This callback will be executed before the mouse grabbing starts.
-- @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})