public
Last active

zephyros 5.0 config

  • Download Gist
00readme.md
Markdown

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.

api.coffee
CoffeeScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
## 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
config.coffee
CoffeeScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
##
# 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')

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.