diff options
Diffstat (limited to 'awesome/lib/awful/mouse/snap.lua')
-rw-r--r-- | awesome/lib/awful/mouse/snap.lua | 266 |
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}) |