fix(hammerspoon): simplify window highlight border rendering
This commit is contained in:
1 parent
4264022a87
commit
34989c95ae
2 files changed
+41
-170
No files matched your search
@@ -12,10 +12,6 @@ return {
|
|||||||
radius = 10,
|
radius = 10,
|
||||||
color = accentColor,
|
color = accentColor,
|
||||||
|
|
||||||
animation = true,
|
|
||||||
animationDuration = 0.10,
|
|
||||||
animationSteps = 8,
|
|
||||||
|
|
||||||
hideOnFullscreen = false,
|
hideOnFullscreen = false,
|
||||||
framePollingFps = 30,
|
framePollingFps = 30,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,14 +10,6 @@ local function sameFrame(a, b)
|
|||||||
and math.abs(a.h - b.h) < 0.1
|
and math.abs(a.h - b.h) < 0.1
|
||||||
end
|
end
|
||||||
|
|
||||||
local function lerp(a, b, t)
|
|
||||||
return a + (b - a) * t
|
|
||||||
end
|
|
||||||
|
|
||||||
local function easeOutCubic(t)
|
|
||||||
return 1 - math.pow(1 - t, 3)
|
|
||||||
end
|
|
||||||
|
|
||||||
function WindowHighlight.new(config, callbacks)
|
function WindowHighlight.new(config, callbacks)
|
||||||
local border = hs.drawing.rectangle(hs.geometry.rect(0, 0, 1, 1))
|
local border = hs.drawing.rectangle(hs.geometry.rect(0, 0, 1, 1))
|
||||||
border.canvas[1] = {
|
border.canvas[1] = {
|
||||||
@@ -48,9 +40,7 @@ function WindowHighlight.new(config, callbacks)
|
|||||||
config = config,
|
config = config,
|
||||||
callbacks = callbacks or {},
|
callbacks = callbacks or {},
|
||||||
border = border,
|
border = border,
|
||||||
cornerRadii = nil,
|
borderRadius = config.radius + config.padding,
|
||||||
currentFrame = nil,
|
|
||||||
animationTimer = nil,
|
|
||||||
missionControlActive = false,
|
missionControlActive = false,
|
||||||
dockElement = nil,
|
dockElement = nil,
|
||||||
liveWindowWatcher = nil,
|
liveWindowWatcher = nil,
|
||||||
@@ -71,24 +61,23 @@ function WindowHighlight:rectForWindow(win)
|
|||||||
local screenFrame = screen:fullFrame()
|
local screenFrame = screen:fullFrame()
|
||||||
local visibleFrame = screen:frame()
|
local visibleFrame = screen:frame()
|
||||||
local pad = self.config.padding
|
local pad = self.config.padding
|
||||||
|
local left = frame.x - pad
|
||||||
|
local top = frame.y - pad
|
||||||
|
local right = frame.x + frame.w + pad
|
||||||
|
local bottom = frame.y + frame.h + pad
|
||||||
|
local touchesScreen = left <= screenFrame.x
|
||||||
|
or top <= visibleFrame.y
|
||||||
|
or right >= screenFrame.x + screenFrame.w
|
||||||
|
or bottom >= screenFrame.y + screenFrame.h
|
||||||
|
|
||||||
local left = math.max(frame.x - pad, screenFrame.x)
|
if touchesScreen then return frame, self.config.radius end
|
||||||
local top = math.max(frame.y - pad, visibleFrame.y)
|
|
||||||
local right = math.min(
|
|
||||||
frame.x + frame.w + pad,
|
|
||||||
screenFrame.x + screenFrame.w
|
|
||||||
)
|
|
||||||
local bottom = math.min(
|
|
||||||
frame.y + frame.h + pad,
|
|
||||||
screenFrame.y + screenFrame.h
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x = left,
|
x = left,
|
||||||
y = top,
|
y = top,
|
||||||
w = right - left,
|
w = right - left,
|
||||||
h = bottom - top,
|
h = bottom - top,
|
||||||
}
|
}, self.config.radius + pad
|
||||||
end
|
end
|
||||||
|
|
||||||
function WindowHighlight:isValidWindow(win)
|
function WindowHighlight:isValidWindow(win)
|
||||||
@@ -100,11 +89,6 @@ function WindowHighlight:isValidWindow(win)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function WindowHighlight:hide()
|
function WindowHighlight:hide()
|
||||||
if self.animationTimer then
|
|
||||||
self.animationTimer:stop()
|
|
||||||
self.animationTimer = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
self.border:hide()
|
self.border:hide()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -130,174 +114,67 @@ function WindowHighlight:setFrame(frame)
|
|||||||
self.border:show()
|
self.border:show()
|
||||||
end
|
end
|
||||||
|
|
||||||
function WindowHighlight:updateCornerRadii(win, borderFrame)
|
|
||||||
local frame = win:frame()
|
|
||||||
local radius = self.config.radius
|
|
||||||
local leftPadding = math.max(frame.x - borderFrame.x, 0)
|
|
||||||
local topPadding = math.max(frame.y - borderFrame.y, 0)
|
|
||||||
local rightPadding = math.max(
|
|
||||||
borderFrame.x + borderFrame.w - (frame.x + frame.w),
|
|
||||||
0
|
|
||||||
)
|
|
||||||
local bottomPadding = math.max(
|
|
||||||
borderFrame.y + borderFrame.h - (frame.y + frame.h),
|
|
||||||
0
|
|
||||||
)
|
|
||||||
local topLeftRadius = radius + math.min(leftPadding, topPadding)
|
|
||||||
local topRightRadius = radius + math.min(rightPadding, topPadding)
|
|
||||||
local bottomRightRadius = radius
|
|
||||||
+ math.min(rightPadding, bottomPadding)
|
|
||||||
local bottomLeftRadius = radius
|
|
||||||
+ math.min(leftPadding, bottomPadding)
|
|
||||||
|
|
||||||
self.cornerRadii = {
|
|
||||||
topLeft = { x = topLeftRadius, y = topLeftRadius },
|
|
||||||
topRight = { x = topRightRadius, y = topRightRadius },
|
|
||||||
bottomRight = {
|
|
||||||
x = bottomRightRadius,
|
|
||||||
y = bottomRightRadius,
|
|
||||||
},
|
|
||||||
bottomLeft = {
|
|
||||||
x = bottomLeftRadius,
|
|
||||||
y = bottomLeftRadius,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
function WindowHighlight:updateBorderPath(frame)
|
function WindowHighlight:updateBorderPath(frame)
|
||||||
local radii = self.cornerRadii
|
|
||||||
if not radii then return end
|
|
||||||
|
|
||||||
local width = frame.w
|
local width = frame.w
|
||||||
local height = frame.h
|
local height = frame.h
|
||||||
local maxXRadius = width / 2
|
local radius = math.min(self.borderRadius, width / 2, height / 2)
|
||||||
local maxYRadius = height / 2
|
|
||||||
local bezier = 0.5522847498
|
local bezier = 0.5522847498
|
||||||
|
|
||||||
local topLeft = {
|
|
||||||
x = math.min(radii.topLeft.x, maxXRadius),
|
|
||||||
y = math.min(radii.topLeft.y, maxYRadius),
|
|
||||||
}
|
|
||||||
local topRight = {
|
|
||||||
x = math.min(radii.topRight.x, maxXRadius),
|
|
||||||
y = math.min(radii.topRight.y, maxYRadius),
|
|
||||||
}
|
|
||||||
local bottomRight = {
|
|
||||||
x = math.min(radii.bottomRight.x, maxXRadius),
|
|
||||||
y = math.min(radii.bottomRight.y, maxYRadius),
|
|
||||||
}
|
|
||||||
local bottomLeft = {
|
|
||||||
x = math.min(radii.bottomLeft.x, maxXRadius),
|
|
||||||
y = math.min(radii.bottomLeft.y, maxYRadius),
|
|
||||||
}
|
|
||||||
|
|
||||||
self.border.canvas[1].coordinates = {
|
self.border.canvas[1].coordinates = {
|
||||||
{ x = topLeft.x, y = 0 },
|
{ x = radius, y = 0 },
|
||||||
{ x = width - topRight.x, y = 0 },
|
{ x = width - radius, y = 0 },
|
||||||
{
|
{
|
||||||
x = width,
|
x = width,
|
||||||
y = topRight.y,
|
y = radius,
|
||||||
c1x = width - topRight.x + bezier * topRight.x,
|
c1x = width - radius + bezier * radius,
|
||||||
c1y = 0,
|
c1y = 0,
|
||||||
c2x = width,
|
c2x = width,
|
||||||
c2y = topRight.y - bezier * topRight.y,
|
c2y = radius - bezier * radius,
|
||||||
},
|
},
|
||||||
{ x = width, y = height - bottomRight.y },
|
{ x = width, y = height - radius },
|
||||||
{
|
{
|
||||||
x = width - bottomRight.x,
|
x = width - radius,
|
||||||
y = height,
|
y = height,
|
||||||
c1x = width,
|
c1x = width,
|
||||||
c1y = height - bottomRight.y + bezier * bottomRight.y,
|
c1y = height - radius + bezier * radius,
|
||||||
c2x = width - bottomRight.x + bezier * bottomRight.x,
|
c2x = width - radius + bezier * radius,
|
||||||
c2y = height,
|
c2y = height,
|
||||||
},
|
},
|
||||||
{ x = bottomLeft.x, y = height },
|
{ x = radius, y = height },
|
||||||
{
|
{
|
||||||
x = 0,
|
x = 0,
|
||||||
y = height - bottomLeft.y,
|
y = height - radius,
|
||||||
c1x = bottomLeft.x - bezier * bottomLeft.x,
|
c1x = radius - bezier * radius,
|
||||||
c1y = height,
|
c1y = height,
|
||||||
c2x = 0,
|
c2x = 0,
|
||||||
c2y = height - bottomLeft.y + bezier * bottomLeft.y,
|
c2y = height - radius + bezier * radius,
|
||||||
},
|
},
|
||||||
{ x = 0, y = topLeft.y },
|
{ x = 0, y = radius },
|
||||||
{
|
{
|
||||||
x = topLeft.x,
|
x = radius,
|
||||||
y = 0,
|
y = 0,
|
||||||
c1x = 0,
|
c1x = 0,
|
||||||
c1y = topLeft.y - bezier * topLeft.y,
|
c1y = radius - bezier * radius,
|
||||||
c2x = topLeft.x - bezier * topLeft.x,
|
c2x = radius - bezier * radius,
|
||||||
c2y = 0,
|
c2y = 0,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
function WindowHighlight:animateTo(targetFrame)
|
function WindowHighlight:update()
|
||||||
if self.animationTimer then
|
|
||||||
self.animationTimer:stop()
|
|
||||||
self.animationTimer = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
if not self.config.animation or not self.currentFrame then
|
|
||||||
self.currentFrame = targetFrame
|
|
||||||
self:setFrame(targetFrame)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local startFrame = self.currentFrame
|
|
||||||
local step = 0
|
|
||||||
local steps = self.config.animationSteps
|
|
||||||
local interval = self.config.animationDuration / steps
|
|
||||||
|
|
||||||
self.animationTimer = hs.timer.doEvery(interval, function()
|
|
||||||
step = step + 1
|
|
||||||
local t = easeOutCubic(step / steps)
|
|
||||||
local frame = {
|
|
||||||
x = lerp(startFrame.x, targetFrame.x, t),
|
|
||||||
y = lerp(startFrame.y, targetFrame.y, t),
|
|
||||||
w = lerp(startFrame.w, targetFrame.w, t),
|
|
||||||
h = lerp(startFrame.h, targetFrame.h, t),
|
|
||||||
}
|
|
||||||
|
|
||||||
self.currentFrame = frame
|
|
||||||
self:setFrame(frame)
|
|
||||||
|
|
||||||
if step >= steps then
|
|
||||||
self.animationTimer:stop()
|
|
||||||
self.animationTimer = nil
|
|
||||||
self.currentFrame = targetFrame
|
|
||||||
self:setFrame(targetFrame)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function WindowHighlight:update(immediate)
|
|
||||||
if self.missionControlActive then
|
if self.missionControlActive then
|
||||||
self:hide()
|
self:hide()
|
||||||
self.currentFrame = nil
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local win = hs.window.focusedWindow()
|
local win = hs.window.focusedWindow()
|
||||||
if not self:isValidWindow(win) then
|
if not self:isValidWindow(win) then
|
||||||
self:hide()
|
self:hide()
|
||||||
self.currentFrame = nil
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local targetFrame = self:rectForWindow(win)
|
local targetFrame, targetRadius = self:rectForWindow(win)
|
||||||
self:updateCornerRadii(win, targetFrame)
|
self.borderRadius = targetRadius
|
||||||
if not immediate then
|
|
||||||
self:animateTo(targetFrame)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.animationTimer then
|
|
||||||
self.animationTimer:stop()
|
|
||||||
self.animationTimer = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
self.currentFrame = targetFrame
|
|
||||||
self:setFrame(targetFrame)
|
self:setFrame(targetFrame)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -323,16 +200,15 @@ function WindowHighlight:watchFocusedWindowLive()
|
|||||||
end
|
end
|
||||||
|
|
||||||
self.watchedWindowId = nil
|
self.watchedWindowId = nil
|
||||||
self.currentFrame = nil
|
|
||||||
self:hide()
|
self:hide()
|
||||||
|
|
||||||
hs.timer.doAfter(0.05, function()
|
hs.timer.doAfter(0.05, function()
|
||||||
self:watchFocusedWindowLive()
|
self:watchFocusedWindowLive()
|
||||||
self:update(true)
|
self:update()
|
||||||
end)
|
end)
|
||||||
elseif event == hs.uielement.watcher.windowMoved
|
elseif event == hs.uielement.watcher.windowMoved
|
||||||
or event == hs.uielement.watcher.windowResized then
|
or event == hs.uielement.watcher.windowResized then
|
||||||
self:update(true)
|
self:update()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -351,7 +227,7 @@ function WindowHighlight:start()
|
|||||||
hs.window.filter.windowTitleChanged,
|
hs.window.filter.windowTitleChanged,
|
||||||
hs.window.filter.windowUnfocused,
|
hs.window.filter.windowUnfocused,
|
||||||
}, function()
|
}, function()
|
||||||
self:update(false)
|
self:update()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
windowFilter:subscribe(hs.window.filter.windowFocused, function()
|
windowFilter:subscribe(hs.window.filter.windowFocused, function()
|
||||||
@@ -364,14 +240,14 @@ function WindowHighlight:start()
|
|||||||
local applicationWatcher = hs.application.watcher.new(function()
|
local applicationWatcher = hs.application.watcher.new(function()
|
||||||
hs.timer.doAfter(0.05, function()
|
hs.timer.doAfter(0.05, function()
|
||||||
self:watchFocusedWindowLive()
|
self:watchFocusedWindowLive()
|
||||||
self:update(false)
|
self:update()
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
applicationWatcher:start()
|
applicationWatcher:start()
|
||||||
|
|
||||||
local spacesWatcher = hs.spaces.watcher.new(function()
|
local spacesWatcher = hs.spaces.watcher.new(function()
|
||||||
hs.timer.doAfter(0.15, function()
|
hs.timer.doAfter(0.15, function()
|
||||||
self:update(false)
|
self:update()
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
spacesWatcher:start()
|
spacesWatcher:start()
|
||||||
@@ -383,13 +259,12 @@ function WindowHighlight:start()
|
|||||||
if not ok or isOpen == self.missionControlActive then return end
|
if not ok or isOpen == self.missionControlActive then return end
|
||||||
|
|
||||||
self.missionControlActive = isOpen
|
self.missionControlActive = isOpen
|
||||||
self.currentFrame = nil
|
|
||||||
|
|
||||||
if self.missionControlActive then
|
if self.missionControlActive then
|
||||||
self:hide()
|
self:hide()
|
||||||
else
|
else
|
||||||
hs.timer.doAfter(0.15, function()
|
hs.timer.doAfter(0.15, function()
|
||||||
self:update(false)
|
self:update()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
@@ -406,7 +281,7 @@ function WindowHighlight:start()
|
|||||||
self.lastPolledWindowId = windowId
|
self.lastPolledWindowId = windowId
|
||||||
self.lastPolledFrame = win and win:frame() or nil
|
self.lastPolledFrame = win and win:frame() or nil
|
||||||
self:watchFocusedWindowLive()
|
self:watchFocusedWindowLive()
|
||||||
self:update(true)
|
self:update()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -416,7 +291,7 @@ function WindowHighlight:start()
|
|||||||
if sameFrame(frame, self.lastPolledFrame) then return end
|
if sameFrame(frame, self.lastPolledFrame) then return end
|
||||||
|
|
||||||
self.lastPolledFrame = frame
|
self.lastPolledFrame = frame
|
||||||
self:update(true)
|
self:update()
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -429,7 +304,7 @@ function WindowHighlight:start()
|
|||||||
}
|
}
|
||||||
|
|
||||||
hs.timer.doAfter(0.5, function()
|
hs.timer.doAfter(0.5, function()
|
||||||
self:update(false)
|
self:update()
|
||||||
self:watchFocusedWindowLive()
|
self:watchFocusedWindowLive()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user