summaryrefslogtreecommitdiff
path: root/awesome/lib/awful/mouse/snap.lua
diff options
context:
space:
mode:
Diffstat (limited to 'awesome/lib/awful/mouse/snap.lua')
-rw-r--r--awesome/lib/awful/mouse/snap.lua266
1 files changed, 266 insertions, 0 deletions
diff --git a/awesome/lib/awful/mouse/snap.lua b/awesome/lib/awful/mouse/snap.lua
new file mode 100644
index 0000000..048a679
--- /dev/null
+++ b/awesome/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})