Skip to content

Instantly share code, notes, and snippets.

@opalmay
Last active June 2, 2026 17:42
Show Gist options
  • Select an option

  • Save opalmay/d56f98817910f6178ff898a8747b9098 to your computer and use it in GitHub Desktop.

Select an option

Save opalmay/d56f98817910f6178ff898a8747b9098 to your computer and use it in GitHub Desktop.
-- scripts/hyprdvd.lua — DVD bouncing windows with inter-window collision
-- usage:
-- local dvd = require("scripts.hyprdvd")
-- hl.bind("SUPER + Y", function()
-- dvd.toggle()
-- end)
local M = {}
local SPEED = 3
local WINDOW_W = 600
local WINDOW_H = 400
local PAD = 0
local JITTER = 0.4
math.randomseed(os.time())
local windows = {}
local mon_x, mon_y, mon_lw, mon_lh = 0, 0, 0, 0
local rule = hl.window_rule({
match = { float = true },
["hyprbars:no_bar"] = true,
})
local function addr_sel(addr)
local s = tostring(addr)
return s:match("^0x") and ("address:" .. s) or ("address:0x" .. s)
end
local function wall_clamp(w)
local xmin = mon_x + PAD
local ymin = mon_y + PAD
local xmax = mon_x + mon_lw - WINDOW_W - PAD
local ymax = mon_y + mon_lh - WINDOW_H - PAD
if w.x < xmin then w.x = xmin; w.dx = math.abs(w.dx) end
if w.x > xmax then w.x = xmax; w.dx = -math.abs(w.dx) end
if w.y < ymin then w.y = ymin; w.dy = math.abs(w.dy) end
if w.y > ymax then w.y = ymax; w.dy = -math.abs(w.dy) end
end
local function nudge(w)
local ang = math.atan(w.dy, w.dx) + (math.random() - 0.5) * JITTER * 2
w.dx = SPEED * math.cos(ang)
w.dy = SPEED * math.sin(ang)
end
local function resolve_pair(a, b)
local P2 = PAD * 2
local ox = math.min(a.x + WINDOW_W, b.x + WINDOW_W) - math.max(a.x, b.x) + P2
local oy = math.min(a.y + WINDOW_H, b.y + WINDOW_H) - math.max(a.y, b.y) + P2
if ox <= 0 or oy <= 0 then return end
local dx = (a.x + WINDOW_W / 2) - (b.x + WINDOW_W / 2)
local dy = (a.y + WINDOW_H / 2) - (b.y + WINDOW_H / 2)
if ox < oy then
local push = ox / 2
local vel_swap = dx * (a.dx - b.dx) < 0
if dx >= 0 then a.x = a.x + push; b.x = b.x - push
else a.x = a.x - push; b.x = b.x + push end
if vel_swap then a.dx, b.dx = b.dx, a.dx; nudge(a); nudge(b) end
else
local push = oy / 2
local vel_swap = dy * (a.dy - b.dy) < 0
if dy >= 0 then a.y = a.y + push; b.y = b.y - push
else a.y = a.y - push; b.y = b.y + push end
if vel_swap then a.dy, b.dy = b.dy, a.dy; nudge(a); nudge(b) end
end
end
local timer = hl.timer(function()
if #windows == 0 then return end
-- resolve on current positions so contact is visible at bounce
for _ = 1, 2 do
for i = 1, #windows do
for j = i + 1, #windows do
resolve_pair(windows[i], windows[j])
end
end
for _, w in ipairs(windows) do wall_clamp(w) end
end
for _, w in ipairs(windows) do
w.x = w.x + w.dx
w.y = w.y + w.dy
wall_clamp(w)
end
for _, w in ipairs(windows) do
hl.dispatch(hl.dsp.window.move({
x = math.floor(w.x), y = math.floor(w.y), window = w.addr_sel
}))
end
end, { timeout = 8, type = "repeat" })
timer:set_enabled(false)
hl.on("window.close", function(closed)
if not closed then return end
for i, w in ipairs(windows) do
if w.addr_raw == closed.address then
table.remove(windows, i)
break
end
end
if #windows == 0 then timer:set_enabled(false) end
end)
function M.toggle()
if timer:is_enabled() then
timer:set_enabled(false)
rule:set_enabled(false)
for _, w in ipairs(windows) do
if w.was_tiled then
hl.dispatch(hl.dsp.window.float({ action = "off", window = w.addr_sel }))
end
end
windows = {}
return
else
rule:set_enabled(true)
end
local m = hl.get_active_monitor()
local ws = hl.get_active_workspace()
mon_lw = m.width / m.scale
mon_lh = m.height / m.scale
mon_x = m.x
mon_y = m.y
local wins = hl.get_workspace_windows(ws)
if not wins or #wins == 0 then return end
windows = {}
local n = #wins
local cx = mon_x + mon_lw / 2
local cy = mon_y + mon_lh / 2
for i, w in ipairs(wins) do
local sel = addr_sel(w.address)
local was_tiled = not w.floating
if was_tiled then
hl.dispatch(hl.dsp.window.float({ action = "on", window = sel }))
end
hl.dispatch(hl.dsp.window.resize({ x = WINDOW_W, y = WINDOW_H, window = sel }))
local xmin_b = mon_x + PAD
local ymin_b = mon_y + PAD
local xmax_b = mon_x + mon_lw - WINDOW_W - PAD
local ymax_b = mon_y + mon_lh - WINDOW_H - PAD
local angle = (i - 1) / n * 2 * math.pi
local rx = (xmax_b - xmin_b) * 0.4
local ry = (ymax_b - ymin_b) * 0.4
local ix = math.max(xmin_b, math.min(xmax_b, cx - WINDOW_W / 2 + rx * math.cos(angle)))
local iy = math.max(ymin_b, math.min(ymax_b, cy - WINDOW_H / 2 + ry * math.sin(angle)))
hl.dispatch(hl.dsp.window.move({ x = math.floor(ix), y = math.floor(iy), window = sel }))
local ang = math.random() * 2 * math.pi
table.insert(windows, {
addr_raw = w.address,
addr_sel = sel,
was_tiled = was_tiled,
x = ix, y = iy,
dx = SPEED * math.cos(ang),
dy = SPEED * math.sin(ang),
})
end
hl.timer(function()
if #windows > 0 then timer:set_enabled(true) end
end, { timeout = 150, type = "oneshot" })
end
return M
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment