113 lines
3.5 KiB
Lua
113 lines
3.5 KiB
Lua
-- ~~~~~~~~~~ CONFIGURATION & STATE ~~~~~~~~~~
|
|
local bento = {}
|
|
local k = _G.hyper or {"alt"}
|
|
|
|
_G.BentoState = _G.BentoState or {
|
|
gridSize = "3x2",
|
|
autoRestore = true,
|
|
windowTracker = {},
|
|
isSnapping = false -- LOCK: Prevents the bounce
|
|
}
|
|
|
|
if _G.BentoResources then
|
|
for _, item in ipairs(_G.BentoResources) do
|
|
if item.delete then item:delete() end
|
|
if item.stop then item:stop() end
|
|
end
|
|
end
|
|
_G.BentoResources = {}
|
|
|
|
hs.grid.setGrid(_G.BentoState.gridSize)
|
|
hs.grid.setMargins({5, 5})
|
|
hs.window.animationDuration = 0 -- Set to 0 to prevent movement lag during snaps
|
|
|
|
-- ~~~~~~~~~~ UTILITIES ~~~~~~~~~~
|
|
|
|
local function isWindowInGrid(win)
|
|
local cell = hs.grid.get(win)
|
|
local frame = win:frame()
|
|
local screen = win:screen()
|
|
local gridFrame = hs.grid.getCell(cell, screen)
|
|
|
|
-- Using a wider 20px buffer to account for macOS window shadows/borders
|
|
return math.abs(frame.x - gridFrame.x) < 20 and
|
|
math.abs(frame.y - gridFrame.y) < 20 and
|
|
math.abs(frame.w - gridFrame.w) < 20 and
|
|
math.abs(frame.h - gridFrame.h) < 20
|
|
end
|
|
|
|
local function manage(action)
|
|
local win = hs.window.focusedWindow()
|
|
if not win then return end
|
|
|
|
local id = win:id()
|
|
if not _G.BentoState.windowTracker[id] then
|
|
_G.BentoState.windowTracker[id] = win:frame()
|
|
end
|
|
|
|
-- ENABLE LOCK
|
|
_G.BentoState.isSnapping = true
|
|
|
|
if action == "ui" then
|
|
hs.grid.show()
|
|
else
|
|
action(win)
|
|
end
|
|
|
|
-- RELEASE LOCK after a short delay
|
|
hs.timer.doAfter(0.3, function()
|
|
_G.BentoState.isSnapping = false
|
|
end)
|
|
end
|
|
|
|
-- ~~~~~~~~~~ DYNAMIC MENU BAR ~~~~~~~~~~
|
|
|
|
local menu = hs.menubar.new()
|
|
table.insert(_G.BentoResources, menu)
|
|
|
|
local function updateMenu()
|
|
return {
|
|
{ title = "Grid: " .. _G.BentoState.gridSize, disabled = true },
|
|
{ title = "-" },
|
|
{ title = "Standard (2x2)", fn = function() _G.BentoState.gridSize = "2x2"; hs.reload() end, checked = (_G.BentoState.gridSize == "2x2") },
|
|
{ title = "Bento (3x2)", fn = function() _G.BentoState.gridSize = "3x2"; hs.reload() end, checked = (_G.BentoState.gridSize == "3x2") },
|
|
{ title = "-" },
|
|
{ title = "Reload Bento Only", fn = function() package.loaded["bento"] = nil; require("bento") end },
|
|
{ title = "Hard Reload", fn = hs.reload }
|
|
}
|
|
end
|
|
|
|
menu:setTitle("⊞")
|
|
menu:setMenu(updateMenu)
|
|
|
|
-- ~~~~~~~~~~ BINDINGS & WATCHERS ~~~~~~~~~~
|
|
|
|
local function bind(key, fn)
|
|
local b = hs.hotkey.bind(k, key, fn)
|
|
if b then table.insert(_G.BentoResources, b) end
|
|
end
|
|
|
|
bind("G", function() manage("ui") end)
|
|
bind("Left", function() manage(hs.grid.pushWindowLeft) end)
|
|
bind("Right", function() manage(hs.grid.pushWindowRight) end)
|
|
bind("Up", function() manage(hs.grid.pushWindowUp) end)
|
|
bind("Down", function() manage(hs.grid.pushWindowDown) end)
|
|
|
|
if _G.BentoState.autoRestore then
|
|
local wf = hs.window.filter.new()
|
|
wf:subscribe(hs.window.filter.windowMoved, function(win)
|
|
-- ONLY trigger if we aren't currently snapping via keyboard
|
|
if not _G.BentoState.isSnapping then
|
|
local id = win:id()
|
|
if _G.BentoState.windowTracker[id] and not isWindowInGrid(win) then
|
|
win:setFrame(_G.BentoState.windowTracker[id])
|
|
_G.BentoState.windowTracker[id] = nil
|
|
hs.alert.show("Restored", 0.5)
|
|
end
|
|
end
|
|
end)
|
|
table.insert(_G.BentoResources, wf)
|
|
end
|
|
|
|
hs.alert.show("Bento Ready", 1)
|
|
return bento |