-- ~/.hammerspoon/bump.lua local bump = {} -- Persistence and State _G.BumpState = _G.BumpState or { gridSize = "3x2", -- 3 columns, 2 rows enabled = true } -- Cleanup existing resources if _G.BumpResources then for _, item in ipairs(_G.BumpResources) do if item.stop then item:stop() end if item.delete then item:delete() end end end _G.BumpResources = {} -- ~~~~~~~~~~ MANUAL BUMP LOGIC ~~~~~~~~~~ local bentoWatcher = hs.window.filter.new() bentoWatcher:subscribe(hs.window.filter.windowMoved, function(newWin) if not _G.BumpState.enabled then return end local f1 = newWin:frame() local screen = newWin:screen() local maxW = screen:frame().w local allWindows = hs.window.visibleWindows() -- Calculate how wide one "slot" is (e.g., 1/3 of screen) local cols = tonumber(_G.BumpState.gridSize:sub(1,1)) local slotWidth = maxW / cols for _, oldWin in ipairs(allWindows) do if oldWin:id() ~= newWin:id() and oldWin:screen() == screen and oldWin:isStandard() then local f2 = oldWin:frame() -- Manual Intersection check local dx = math.max(0, math.min(f1.x + f1.w, f2.x + f2.w) - math.max(f1.x, f2.x)) local dy = math.max(0, math.min(f1.y + f1.h, f2.y + f2.h) - math.max(f1.y, f2.y)) -- If they overlap significantly if (dx * dy) > (f1.w * f1.h * 0.4) then local newX = f2.x + slotWidth -- If pushing right stays on screen, move it. -- Otherwise, move it to the start of the next row (y + height) if (newX + 50) > maxW then oldWin:setFrame({x = 0, y = f2.y + f2.h, w = f2.w, h = f2.h}) else oldWin:setFrame({x = newX, y = f2.y, w = f2.w, h = f2.h}) end hs.alert.show("Bumping " .. oldWin:application():name()) end end end end) table.insert(_G.BumpResources, bentoWatcher) -- ~~~~~~~~~~ MENU BAR ~~~~~~~~~~ local menu = hs.menubar.new() table.insert(_G.BumpResources, menu) local function updateMenu() return { { title = "Bump Status: " .. (_G.BumpState.enabled and "ON" or "OFF"), disabled = true }, { title = "-" }, { title = "Grid: 2x2", fn = function() _G.BumpState.gridSize = "2x2"; hs.reload() end, checked = (_G.BumpState.gridSize == "2x2") }, { title = "Grid: 3x2", fn = function() _G.BumpState.gridSize = "3x2"; hs.reload() end, checked = (_G.BumpState.gridSize == "3x2") }, { title = "-" }, { title = "Reload Bump Only", fn = function() package.loaded["bump"] = nil; require("bump") end }, { title = "Hard Reload All", fn = hs.reload } } end menu:setTitle("⇄") menu:setMenu(updateMenu) return bump