Skip to content

Instantly share code, notes, and snippets.

@AdrianRossouw
Last active December 17, 2015 06:29
Show Gist options
  • Select an option

  • Save AdrianRossouw/5565807 to your computer and use it in GitHub Desktop.

Select an option

Save AdrianRossouw/5565807 to your computer and use it in GitHub Desktop.
zephyros 5.0 config

Anchored Windows

This Zephyros configuration introduces the concept of "Anchors" to all windows. There are nine-altogether for each window, through all the combinations of top, middle, bottom, left, right and center.

Each screen has a complimentary set of anchor points. Using ctrl+alt+cmd and the arrow keys, you are able to anchor windows to these points on the screen.

Unlike other grid layouts, anchoring windows does not make any judgement on the size of the windows.

I find that by overlapping my windows in a specific way, I have not used cmd-tab or expose nearly as much, because entire area of my screen become hot-spots for switching to a specific application.

ie: clicking on the bottom right will always switch to my vim editor.

By pressing ctrl+alt+cmd and N, you will flip the window over to the next screen, anchoring it to the same point on the next screen. this way your windows will remain in the same relative position to each other, regardless of screen.

While you are free so size your windows as you want, I have included the functionality to scale your focused window via keybindings.

The resize code will intelligentaly scale your window relative to the anchor it is mounted on, ie: if a window is mounted on the dead center of the screen, it will scale both left and right, when increasing the window width.

Additionally, while bottom/right usually imply growing a window, when right up against the bottom/right edges, it will instead flip the directions.

it seems to work for me.

## Helpers
# Each desktop is split 24 ways when it comes to resizing
# windows.
scaleSteps = 24
# Helper to move the window on the screen.
moveWindow = (fn) ->
win = api.focusedWindow()
winFrame = win.getFrame()
fn winFrame if fn
win.setFrame winFrame.frame
win.setSize winFrame.size
win.setTopLeft winFrame.origin
# Coffeescript is retarded. write this hash map helper.
mapObj = (obj, fn) ->
cb = (m, v, k) ->
m[k] = fn(v, k)
return m
_.reduce obj, cb, {}
# Abstract out some differences between X and Y coordinates.
axisMap =
x:
field: 'w'
SDMax: SDMaxX
SDMin: SDMinX
compass: ['east', 'west']
directions: ['left', 'right']
y:
field: 'h'
SDMax: SDMaxY
SDMin: SDMinY
compass: ['north', 'south']
directions: ['left', 'north']
# Generate a set of coordinates on the screen
# that a window can be mounted onto.
Screen::anchorPoints = ->
rect = @frameIncludingDockAndMenu()
rectMenu = @frameWithoutDockOrMenu()
min = x: SDMinX(rect), y: SDMinY(rect)
max = x: SDMaxX(rect), y: SDMaxY(rect)
step =
x: Math.round (max.x or min.x) / 2
y: Math.round (max.y or min.y) / 2
result =
x: i for i in [0..(max.x or min.x)] by step.x
y: i for i in [0..(max.y or min.x)] by step.y
result.x.reverse() if step.x < 0
result.y.reverse() if step.y < 0
## [result.x[0], result.x[result.length - 1]] = [SDMinX(rectMenu), SDMaxX(rectMenu)]
## [result.y[0], result.y[result.length - 1]] = [SDMinY(rectMenu), SDMaxY(rectMenu)]
return result
Window::getFrame = ->
frame: @frame()
origin: @topLeft()
size: @size()
# Generate a list of anchors on a window, to be
# mounted onto the desktop.
Window::anchors = ->
rect = @size()
x: [1, rect.w / 2, rect.w]
y: [1, rect.h / 2, rect.h]
# Return the current coordinates for each of the
# window's anchors, in relation to the position
# on the desktop.
Window::anchorCoords = ->
addOrigin = (offset) -> (v) -> v + offset
frame = @frame()
origin = @topLeft()
mapObj @anchors(), (anchors, axis) ->
_.map anchors, addOrigin(origin[axis])
# Find the anchor point on the screen closest to
# the relevant anchor on the window.
Window::closestAnchorPoint = ->
winScreen = @screen()
normalize = (points) ->
grid = _.map points.x, (xv, xk) ->
_.map points.y, (yv, yk) ->
{ xk: xk, yk: yk, xv: xv, yv: yv }
# Flatten it out so there are x*y coordinates
flat = _.flatten grid, true
# We want the middle to be more 'sticky' than the rest?
_.sortBy flat, (point) ->
((point.xk + 1) % 2) + ((point.yk + 1) % 2)
# Generate a merged, normalized structure that
# is more suitable to functional iteration,
# and doesn't require us to access variables
# outside of our functions.
screen = normalize winScreen.anchorPoints()
window = normalize @anchorCoords()
zipped = _.zip screen, window
# We already built in a preference towards the middle
# of the screen when we normalized the list, so we can
# run a basic _.min here and get away with it.
closest = _.min zipped, (anchor) ->
dx = anchor[0].xv - anchor[1].xv
dy = anchor[0].yv - anchor[1].yv
# Apply our trusty Pythagorean theorem to determine
# relative distances.
distance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2))
Math.round(distance)
{ x: closest[0].xk, y: closest[0].yk }
# Attach a window's anchor to the matching anchor
# point on the screen.
#
# The screen parameter is optional, and can be used to
# flip windows # between monitors.
Window::anchorToPoint = (xc, yc, screen) ->
frame = @getFrame()
points = (screen ? @screen()).anchorPoints()
adjust =
0: (i) -> i
1: (i, k) -> i - Math.round(frame.size[k] / 2)
2: (i, k) -> i - frame.size[k]
moveWindow (frame) ->
frame.origin.x = adjust[xc] points.x[xc], 'w'
frame.origin.y = adjust[yc] points.y[yc], 'h'
# Attach a window to an adjacant anchor point, based
# it's current position.
Window::shiftAnchorPoint = (xdiff, ydiff, screen) ->
# we actually want the closest matching point
# on the current screen, regardless of
# wether we are moving it to the other screen.
closest = @closestAnchorPoint()
adjust = (ind, diff) ->
retval = ind + diff
if retval in [0, 1, 2] then return retval else return ind
@anchorToPoint(
adjust(closest.x, xdiff),
adjust(closest.y, ydiff),
screen
)
Window::scaleCardinal = (dir) ->
screenFrame = @screen().frameWithoutDockOrMenu()
points = @screen().anchorPoints()
closest = @closestAnchorPoint()
axis = if dir in ['east', 'west'] then 'x' else 'y'
axisField = axisMap[axis].field
axisMin = axisMap[axis].SDMin
axisMax = axisMap[axis].SDMax
scaleIncrement = Math.round(screenFrame[axisField] / scaleSteps)
closestAxis = closest[axis]
closestOnAxis = points[axis][closestAxis]
# by default we will scale from the bottom right corner
isGrowing = true if dir in ['east', 'south']
# On the edges, we flip the directions to grow/shrink, to
# avoid having to move the window away, to grow.
isGrowing = !isGrowing if closestAxis == 2
# The window will remain anchored to the point, and
# will scale out linearly from the point.
adjustOrigin = {
0: (size, i) -> i
1: (size, i) -> i - Math.round(size / 2)
2: (size, i) -> i - size
}[closestAxis]
# Reset the initial size to the closest multiple
# of the scale increment.
adjustBaseSize = (size) ->
Math.round(size / scaleIncrement) * scaleIncrement
moveWindow (frame) ->
# Determine new frame size after the scaling has been done
size = adjustBaseSize frame.size[axisField]
size += if (isGrowing) then scaleIncrement else -scaleIncrement
# Determine new origin point on this axis.
origin = adjustOrigin size, closestOnAxis
# Make sure the window doesn't adjust too far so as to move out
# of the screen.
belowMax = origin + size - 5 <= axisMax screenFrame
aboveMin = origin >= axisMin screenFrame
if belowMax and aboveMin
frame.size[axisField] = size
frame.origin[axis] = origin
##
# Bindings:
#
# Next Screen : cmd-alt-ctrl + N
# Shift to next anchor : cmd-alt-ctrl + <arrow>
# Scale relative to anchor : cmd+alt + <arrow>
##
mash = ["cmd", "alt", "ctrl"]
mini_mash = ["cmd", "alt"]
grid_width = 3
grid_height = 3
require('~/.zephyros/api.coffee')
# throw to next screen
bind "N", mash, ->
win = api.focusedWindow()
screen = win.screen()
win.shiftAnchorPoint(0, 0, screen.nextScreen())
bind "left", mash, ->
api.focusedWindow().shiftAnchorPoint -1, 0
bind "right", mash, ->
api.focusedWindow().shiftAnchorPoint 1, 0
bind "up", mash, ->
api.focusedWindow().shiftAnchorPoint 0, -1
bind "down", mash, ->
api.focusedWindow().shiftAnchorPoint 0, 1
bind "left", mini_mash, ->
api.focusedWindow().scaleCardinal('west')
bind "right", mini_mash, ->
api.focusedWindow().scaleCardinal('east')
bind "up", mini_mash, ->
api.focusedWindow().scaleCardinal('north')
bind "down", mini_mash, ->
api.focusedWindow().scaleCardinal('south')
bind "R", mash, ->
reloadConfig()
#require('~/.zephyros/clipboard.coffee')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment