diff options
author | ache <ache@ache.one> | 2017-03-13 23:17:19 +0100 |
---|---|---|
committer | ache <ache@ache.one> | 2017-03-13 23:17:19 +0100 |
commit | 22d656903563f75678f3634964731ccf93355dfd (patch) | |
tree | e3cb6279d95c9764093072d5e946566ea6533799 /lib/awful/mouse |
Init commit
Diffstat (limited to 'lib/awful/mouse')
-rw-r--r-- | lib/awful/mouse/drag_to_tag.lua | 58 | ||||
-rw-r--r-- | lib/awful/mouse/init.lua | 437 | ||||
-rw-r--r-- | lib/awful/mouse/resize.lua | 229 | ||||
-rw-r--r-- | lib/awful/mouse/snap.lua | 266 |
4 files changed, 990 insertions, 0 deletions
diff --git a/lib/awful/mouse/drag_to_tag.lua b/lib/awful/mouse/drag_to_tag.lua new file mode 100644 index 0000000..141456b --- /dev/null +++ b/lib/awful/mouse/drag_to_tag.lua @@ -0,0 +1,58 @@ +--------------------------------------------------------------------------- +--- When the the mouse reach the end of the screen, then switch tag instead +-- of screens. +-- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @submodule mouse +--------------------------------------------------------------------------- + +local capi = {screen = screen, mouse = mouse} +local util = require("awful.util") +local tag = require("awful.tag") +local resize = require("awful.mouse.resize") + +local module = {} + +function module.drag_to_tag(c) + if (not c) or (not c.valid) then return end + + local coords = capi.mouse.coords() + + local dir = nil + + local wa = c.screen.workarea + + if coords.x >= wa.x + wa.width - 1 then + capi.mouse.coords({ x = wa.x + 2 }, true) + dir = "right" + elseif coords.x <= wa.x + 1 then + capi.mouse.coords({ x = wa.x + wa.width - 2 }, true) + dir = "left" + end + + local tags = c.screen.tags + local t = c.screen.selected_tag + local idx = t.index + + if dir then + + if dir == "right" then + local newtag = tags[util.cycle(#tags, idx + 1)] + c:move_to_tag(newtag) + tag.viewnext() + elseif dir == "left" then + local newtag = tags[util.cycle(#tags, idx - 1)] + c:move_to_tag(newtag) + tag.viewprev() + end + end +end + +resize.add_move_callback(function(c, _, _) + if module.enabled then + module.drag_to_tag(c) + end +end, "mouse.move") + +return setmetatable(module, {__call = function(_, ...) return module.drag_to_tag(...) end}) diff --git a/lib/awful/mouse/init.lua b/lib/awful/mouse/init.lua new file mode 100644 index 0000000..03f7e89 --- /dev/null +++ b/lib/awful/mouse/init.lua @@ -0,0 +1,437 @@ +--------------------------------------------------------------------------- +--- Mouse module for awful +-- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @module mouse +--------------------------------------------------------------------------- + +-- Grab environment we need +local layout = require("awful.layout") +local aplace = require("awful.placement") +local util = require("awful.util") +local type = type +local ipairs = ipairs +local capi = +{ + root = root, + mouse = mouse, + screen = screen, + client = client, + mousegrabber = mousegrabber, +} + +local mouse = { + resize = require("awful.mouse.resize"), + snap = require("awful.mouse.snap"), + drag_to_tag = require("awful.mouse.drag_to_tag") +} + +mouse.object = {} +mouse.client = {} +mouse.wibox = {} + +--- The default snap distance. +-- @tfield integer awful.mouse.snap.default_distance +-- @tparam[opt=8] integer default_distance +-- @see awful.mouse.snap + +--- Enable screen edges snapping. +-- @tfield[opt=true] boolean awful.mouse.snap.edge_enabled + +--- Enable client to client snapping. +-- @tfield[opt=true] boolean awful.mouse.snap.client_enabled + +--- Enable changing tag when a client is dragged to the edge of the screen. +-- @tfield[opt=false] integer awful.mouse.drag_to_tag.enabled + +--- The snap outline background color. +-- @beautiful beautiful.snap_bg +-- @tparam color|string|gradient|pattern color + +--- The snap outline width. +-- @beautiful beautiful.snap_border_width +-- @param integer + +--- The snap outline shape. +-- @beautiful beautiful.snap_shape +-- @tparam function shape A `gears.shape` compatible function + +--- Get the client object under the pointer. +-- @deprecated awful.mouse.client_under_pointer +-- @return The client object under the pointer, if one can be found. +-- @see current_client +function mouse.client_under_pointer() + util.deprecate("Use mouse.current_client instead of awful.mouse.client_under_pointer()") + + return mouse.object.get_current_client() +end + +--- Move a client. +-- @function awful.mouse.client.move +-- @param c The client to move, or the focused one if nil. +-- @param snap The pixel to snap clients. +-- @param finished_cb Deprecated, do not use +function mouse.client.move(c, snap, finished_cb) --luacheck: no unused args + if finished_cb then + util.deprecate("The mouse.client.move `finished_cb` argument is no longer".. + " used, please use awful.mouse.resize.add_leave_callback(f, 'mouse.move')") + end + + c = c or capi.client.focus + + if not c + or c.fullscreen + or c.type == "desktop" + or c.type == "splash" + or c.type == "dock" then + return + end + + -- Compute the offset + local coords = capi.mouse.coords() + local geo = aplace.centered(capi.mouse,{parent=c, pretend=true}) + + local offset = { + x = geo.x - coords.x, + y = geo.y - coords.y, + } + + mouse.resize(c, "mouse.move", { + placement = aplace.under_mouse, + offset = offset, + snap = snap + }) +end + +mouse.client.dragtotag = { } + +--- Move a client to a tag by dragging it onto the left / right side of the screen. +-- @deprecated awful.mouse.client.dragtotag.border +-- @param c The client to move +function mouse.client.dragtotag.border(c) + util.deprecate("Use awful.mouse.snap.drag_to_tag_enabled = true instead ".. + "of awful.mouse.client.dragtotag.border(c). It will now be enabled.") + + -- Enable drag to border + mouse.snap.drag_to_tag_enabled = true + + return mouse.client.move(c) +end + +--- Move the wibox under the cursor. +-- @function awful.mouse.wibox.move +--@tparam wibox w The wibox to move, or none to use that under the pointer +function mouse.wibox.move(w) + w = w or mouse.wibox_under_pointer() + if not w then return end + + if not w + or w.type == "desktop" + or w.type == "splash" + or w.type == "dock" then + return + end + + -- Compute the offset + local coords = capi.mouse.coords() + local geo = aplace.centered(capi.mouse,{parent=w, pretend=true}) + + local offset = { + x = geo.x - coords.x, + y = geo.y - coords.y, + } + + mouse.resize(w, "mouse.move", { + placement = aplace.under_mouse, + offset = offset + }) +end + +--- Get a client corner coordinates. +-- @deprecated awful.mouse.client.corner +-- @tparam[opt=client.focus] client c The client to get corner from, focused one by default. +-- @tparam string corner The corner to use: auto, top_left, top_right, bottom_left, +-- bottom_right, left, right, top bottom. Default is auto, and auto find the +-- nearest corner. +-- @treturn string The corner name +-- @treturn number x The horizontal position +-- @treturn number y The vertical position +function mouse.client.corner(c, corner) + util.deprecate( + "Use awful.placement.closest_corner(mouse) or awful.placement[corner](mouse)".. + " instead of awful.mouse.client.corner" + ) + + c = c or capi.client.focus + if not c then return end + + local ngeo = nil + + if (not corner) or corner == "auto" then + ngeo, corner = aplace.closest_corner(mouse, {parent = c}) + elseif corner and aplace[corner] then + ngeo = aplace[corner](mouse, {parent = c}) + end + + return corner, ngeo and ngeo.x or nil, ngeo and ngeo.y or nil +end + +--- Resize a client. +-- @function awful.mouse.client.resize +-- @param c The client to resize, or the focused one by default. +-- @tparam string corner The corner to grab on resize. Auto detected by default. +-- @tparam[opt={}] table args A set of `awful.placement` arguments +-- @treturn string The corner (or side) name +function mouse.client.resize(c, corner, args) + c = c or capi.client.focus + + if not c then return end + + if c.fullscreen + or c.type == "desktop" + or c.type == "splash" + or c.type == "dock" then + return + end + + -- Set some default arguments + local new_args = setmetatable( + { + include_sides = (not args) or args.include_sides ~= false + }, + { + __index = args or {} + } + ) + + -- Move the mouse to the corner + if corner and aplace[corner] then + aplace[corner](capi.mouse, {parent=c}) + else + local _ + _, corner = aplace.closest_corner(capi.mouse, { + parent = c, + include_sides = new_args.include_sides ~= false, + }) + end + + new_args.corner = corner + + mouse.resize(c, "mouse.resize", new_args) + + return corner +end + +--- Default handler for `request::geometry` signals with "mouse.resize" context. +-- @signalhandler awful.mouse.resize_handler +-- @tparam client c The client +-- @tparam string context The context +-- @tparam[opt={}] table hints The hints to pass to the handler +function mouse.resize_handler(c, context, hints) + if hints and context and context:find("mouse.*") then + -- This handler only handle the floating clients. If the client is tiled, + -- then it let the layouts handle it. + local t = c.screen.selected_tag + local lay = t and t.layout or nil + + if (lay and lay == layout.suit.floating) or c.floating then + c:geometry { + x = hints.x, + y = hints.y, + width = hints.width, + height = hints.height, + } + elseif lay and lay.resize_handler then + lay.resize_handler(c, context, hints) + end + end +end + +-- Older layouts implement their own mousegrabber. +-- @tparam client c The client +-- @tparam table args Additional arguments +-- @treturn boolean This return false when the resize need to be aborted +mouse.resize.add_enter_callback(function(c, args) --luacheck: no unused args + if c.floating then return end + + local l = c.screen.selected_tag and c.screen.selected_tag.layout or nil + if l == layout.suit.floating then return end + + if l ~= layout.suit.floating and l.mouse_resize_handler then + capi.mousegrabber.stop() + + local geo, corner = aplace.closest_corner(capi.mouse, {parent=c}) + + l.mouse_resize_handler(c, corner, geo.x, geo.y) + + return false + end +end, "mouse.resize") + +--- Get the client currently under the mouse cursor. +-- @property current_client +-- @tparam client|nil The client + +function mouse.object.get_current_client() + local obj = capi.mouse.object_under_pointer() + if type(obj) == "client" then + return obj + end +end + +--- Get the wibox currently under the mouse cursor. +-- @property current_wibox +-- @tparam wibox|nil The wibox + +function mouse.object.get_current_wibox() + local obj = capi.mouse.object_under_pointer() + if type(obj) == "drawin" and obj.get_wibox then + return obj:get_wibox() + end +end + +--- Get the widgets currently under the mouse cursor. +-- +-- @property current_widgets +-- @tparam nil|table list The widget list +-- @treturn table The list of widgets.The first element is the biggest +-- container while the last is the topmost widget. The table contains *x*, *y*, +-- *width*, *height* and *widget*. +-- @see wibox.find_widgets + +function mouse.object.get_current_widgets() + local w = mouse.object.get_current_wibox() + if w then + local geo, coords = w:geometry(), capi.mouse:coords() + + local list = w:find_widgets(coords.x - geo.x, coords.y - geo.y) + + local ret = {} + + for k, v in ipairs(list) do + ret[k] = v.widget + end + + return ret, list + end +end + +--- Get the topmost widget currently under the mouse cursor. +-- @property current_widget +-- @tparam widget|nil widget The widget +-- @treturn ?widget The widget +-- @see wibox.find_widgets +-- @see current_widget_geometry + +function mouse.object.get_current_widget() + local wdgs, geos = mouse.object.get_current_widgets() + + if wdgs then + return wdgs[#wdgs], geos[#geos] + end +end + +--- Get the current widget geometry. +-- @property current_widget_geometry +-- @tparam ?table The geometry. +-- @see current_widget + +function mouse.object.get_current_widget_geometry() + local _, ret = mouse.object.get_current_widget() + + return ret +end + +--- Get the current widget geometries. +-- @property current_widget_geometries +-- @tparam ?table A list of geometry tables. +-- @see current_widgets + +function mouse.object.get_current_widget_geometries() + local _, ret = mouse.object.get_current_widgets() + + return ret +end + +--- True if the left mouse button is pressed. +-- @property is_left_mouse_button_pressed +-- @param boolean + +--- True if the right mouse button is pressed. +-- @property is_right_mouse_button_pressed +-- @param boolean + +--- True if the middle mouse button is pressed. +-- @property is_middle_mouse_button_pressed +-- @param boolean + +for _, b in ipairs {"left", "right", "middle"} do + mouse.object["is_".. b .."_mouse_button_pressed"] = function() + return capi.mouse.coords().buttons[1] + end +end + +capi.client.connect_signal("request::geometry", mouse.resize_handler) + +-- Set the cursor at startup +capi.root.cursor("left_ptr") + +-- Implement the custom property handler +local props = {} + +capi.mouse.set_newindex_miss_handler(function(_,key,value) + if mouse.object["set_"..key] then + mouse.object["set_"..key](value) + elseif not mouse.object["get_"..key] then + props[key] = value + else + -- If there is a getter, but no setter, then the property is read-only + error("Cannot set '" .. tostring(key) .. " because it is read-only") + end +end) + +capi.mouse.set_index_miss_handler(function(_,key) + if mouse.object["get_"..key] then + return mouse.object["get_"..key]() + else + return props[key] + end +end) + +--- Get or set the mouse coords. +-- +-- +-- +--![Usage example](../images/AUTOGEN_awful_mouse_coords.svg) +-- +--**Usage example output**: +-- +-- 235 +-- +-- +-- @usage +-- -- Get the position +--print(mouse.coords().x) +-- -- Change the position +--mouse.coords { +-- x = 185, +-- y = 10 +--} +-- +-- @tparam[opt=nil] table coords_table None or a table with x and y keys as mouse +-- coordinates. +-- @tparam[opt=nil] integer coords_table.x The mouse horizontal position +-- @tparam[opt=nil] integer coords_table.y The mouse vertical position +-- @tparam[opt=false] boolean silent Disable mouse::enter or mouse::leave events that +-- could be triggered by the pointer when moving. +-- @treturn integer table.x The horizontal position +-- @treturn integer table.y The vertical position +-- @treturn table table.buttons Table containing the status of buttons, e.g. field [1] is true +-- when button 1 is pressed. +-- @function mouse.coords + + +return mouse + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/awful/mouse/resize.lua b/lib/awful/mouse/resize.lua new file mode 100644 index 0000000..edd278d --- /dev/null +++ b/lib/awful/mouse/resize.lua @@ -0,0 +1,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}) diff --git a/lib/awful/mouse/snap.lua b/lib/awful/mouse/snap.lua new file mode 100644 index 0000000..048a679 --- /dev/null +++ b/lib/awful/mouse/snap.lua @@ -0,0 +1,266 @@ +--------------------------------------------------------------------------- +--- Mouse snapping related functions +-- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @submodule mouse +--------------------------------------------------------------------------- + +local aclient = require("awful.client") +local resize = require("awful.mouse.resize") +local aplace = require("awful.placement") +local wibox = require("wibox") +local beautiful = require("beautiful") +local color = require("gears.color") +local shape = require("gears.shape") +local cairo = require("lgi").cairo + +local capi = { + root = root, + mouse = mouse, + screen = screen, + client = client, + mousegrabber = mousegrabber, +} + +local module = { + default_distance = 8 +} + +local placeholder_w = nil + +local function show_placeholder(geo) + if not geo then + if placeholder_w then + placeholder_w.visible = false + end + return + end + + placeholder_w = placeholder_w or wibox { + ontop = true, + bg = color(beautiful.snap_bg or beautiful.bg_urgent or "#ff0000"), + } + + placeholder_w:geometry(geo) + + local img = cairo.ImageSurface(cairo.Format.A1, geo.width, geo.height) + local cr = cairo.Context(img) + + cr:set_operator(cairo.Operator.CLEAR) + cr:set_source_rgba(0,0,0,1) + cr:paint() + cr:set_operator(cairo.Operator.SOURCE) + cr:set_source_rgba(1,1,1,1) + + local line_width = beautiful.snap_border_width or 5 + cr:set_line_width(beautiful.xresources.apply_dpi(line_width)) + + local f = beautiful.snap_shape or function() + cr:translate(line_width,line_width) + shape.rounded_rect(cr,geo.width-2*line_width,geo.height-2*line_width, 10) + end + + f(cr, geo.width, geo.height) + + cr:stroke() + + placeholder_w.shape_bounding = img._native + + placeholder_w.visible = true +end + +local function build_placement(snap, axis) + return aplace.scale + + aplace[snap] + + ( + axis and aplace["maximize_"..axis] or nil + ) +end + +local function detect_screen_edges(c, snap) + local coords = capi.mouse.coords() + + local sg = c.screen.geometry + + local v, h = nil + + if math.abs(coords.x) <= snap + sg.x and coords.x >= sg.x then + h = "left" + elseif math.abs((sg.x + sg.width) - coords.x) <= snap then + h = "right" + end + + if math.abs(coords.y) <= snap + sg.y and coords.y >= sg.y then + v = "top" + elseif math.abs((sg.y + sg.height) - coords.y) <= snap then + v = "bottom" + end + + return v, h +end + +local current_snap, current_axis = nil + +local function detect_areasnap(c, distance) + local old_snap = current_snap + local v, h = detect_screen_edges(c, distance) + + if v and h then + current_snap = v.."_"..h + else + current_snap = v or h or nil + end + + if old_snap == current_snap then return end + + current_axis = ((v and not h) and "horizontally") + or ((h and not v) and "vertically") + or nil + + -- Show the expected geometry outline + show_placeholder( + current_snap and build_placement(current_snap, current_axis)(c, { + to_percent = 0.5, + honor_workarea = true, + pretend = true + }) or nil + ) + +end + +local function apply_areasnap(c, args) + if not current_snap then return end + + -- Remove the move offset + args.offset = {} + + placeholder_w.visible = false + + return build_placement(current_snap, current_axis)(c,{ + to_percent = 0.5, + honor_workarea = true, + }) +end + +local function snap_outside(g, sg, snap) + if g.x < snap + sg.x + sg.width and g.x > sg.x + sg.width then + g.x = sg.x + sg.width + elseif g.x + g.width < sg.x and g.x + g.width > sg.x - snap then + g.x = sg.x - g.width + end + if g.y < snap + sg.y + sg.height and g.y > sg.y + sg.height then + g.y = sg.y + sg.height + elseif g.y + g.height < sg.y and g.y + g.height > sg.y - snap then + g.y = sg.y - g.height + end + return g +end + +local function snap_inside(g, sg, snap) + local edgev = 'none' + local edgeh = 'none' + if math.abs(g.x) < snap + sg.x and g.x > sg.x then + edgev = 'left' + g.x = sg.x + elseif math.abs((sg.x + sg.width) - (g.x + g.width)) < snap then + edgev = 'right' + g.x = sg.x + sg.width - g.width + end + if math.abs(g.y) < snap + sg.y and g.y > sg.y then + edgeh = 'top' + g.y = sg.y + elseif math.abs((sg.y + sg.height) - (g.y + g.height)) < snap then + edgeh = 'bottom' + g.y = sg.y + sg.height - g.height + end + + -- What is the dominant dimension? + if g.width > g.height then + return g, edgeh + else + return g, edgev + end +end + +--- Snap a client to the closest client or screen edge. +-- @function awful.mouse.snap +-- @param c The client to snap. +-- @param snap The pixel to snap clients. +-- @param x The client x coordinate. +-- @param y The client y coordinate. +-- @param fixed_x True if the client isn't allowed to move in the x direction. +-- @param fixed_y True if the client isn't allowed to move in the y direction. +function module.snap(c, snap, x, y, fixed_x, fixed_y) + snap = snap or module.default_distance + c = c or capi.client.focus + local cur_geom = c:geometry() + local geom = c:geometry() + geom.width = geom.width + (2 * c.border_width) + geom.height = geom.height + (2 * c.border_width) + local edge + geom.x = x or geom.x + geom.y = y or geom.y + + geom, edge = snap_inside(geom, c.screen.geometry, snap) + geom = snap_inside(geom, c.screen.workarea, snap) + + -- Allow certain windows to snap to the edge of the workarea. + -- Only allow docking to workarea for consistency/to avoid problems. + if c.dockable then + local struts = c:struts() + struts['left'] = 0 + struts['right'] = 0 + struts['top'] = 0 + struts['bottom'] = 0 + if edge ~= "none" and c.floating then + if edge == "left" or edge == "right" then + struts[edge] = cur_geom.width + elseif edge == "top" or edge == "bottom" then + struts[edge] = cur_geom.height + end + end + c:struts(struts) + end + + for _, snapper in ipairs(aclient.visible(c.screen)) do + if snapper ~= c then + local snapper_geom = snapper:geometry() + snapper_geom.width = snapper_geom.width + (2 * snapper.border_width) + snapper_geom.height = snapper_geom.height + (2 * snapper.border_width) + geom = snap_outside(geom, snapper_geom, snap) + end + end + + geom.width = geom.width - (2 * c.border_width) + geom.height = geom.height - (2 * c.border_width) + + -- It's easiest to undo changes afterwards if they're not allowed + if fixed_x then geom.x = cur_geom.x end + if fixed_y then geom.y = cur_geom.y end + + return geom +end + +-- Enable edge snapping +resize.add_move_callback(function(c, geo, args) + -- Screen edge snapping (areosnap) + if (module.edge_enabled ~= false) + and args and (args.snap == nil or args.snap) then + detect_areasnap(c, 16) + end + + -- Snapping between clients + if (module.client_enabled ~= false) + and args and (args.snap == nil or args.snap) then + return module.snap(c, args.snap, geo.x, geo.y) + end +end, "mouse.move") + +-- Apply the aerosnap +resize.add_leave_callback(function(c, _, args) + if module.edge_enabled == false then return end + return apply_areasnap(c, args) +end, "mouse.move") + +return setmetatable(module, {__call = function(_, ...) return module.snap(...) end}) |