From d538d01a9ed0008aded0fbdcda5ea923fced7285 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Sun, 11 Nov 2018 10:45:24 +1100 Subject: [PATCH 01/41] =?UTF-8?q?housekeeping=20-=20titleCase,=20round,=20?= =?UTF-8?q?frontmost=E2=80=A6=20to=20locals=20(we=20don't=20need=20to=20po?= =?UTF-8?q?llute=20the=20global=20namespace)=20-=20some=20indentation=20an?= =?UTF-8?q?d=20documentation=20fixes=20-=20and=20some=20minor=20cleanup=20?= =?UTF-8?q?to=20satisfy=20the=20lauc=20linter=20(line=20length,=20trailing?= =?UTF-8?q?=20whitespace)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MiroWindowsManager.spoon/init.lua | 150 +++++++++++++++--------------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index d3fe87a..528d4c6 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -17,15 +17,13 @@ --- === MiroWindowsManager === --- ---- With this Spoon you will be able to move the window in halves and in ---- corners using your keyboard and mainly using arrows. You would also be able ---- to resize them by thirds, quarters, or halves. +--- With this Spoon you will be able to move the window in halves and in corners using your keyboard and mainly using +--- arrows. You would also be able to resize them by thirds, quarters, or halves. --- Official homepage for more info and documentation: --- [https://github.com/miromannino/miro-windows-manager](https://github.com/miromannino/miro-windows-manager) --- ---- NOTE: This Spoon sets `hs.grid` globals with `hs.grid.setGrid()`, ---- `hs.grid.MARGINX`, and `hs.grid.MARGINY`. Changing MiroWindowsManager.GRID ---- will change these globals. +--- NOTE: This Spoon sets `hs.grid` globals with `hs.grid.setGrid()`, `hs.grid.MARGINX`, and `hs.grid.MARGINY`. +--- Changing MiroWindowsManager.GRID will change these globals. --- --- Download: --- https://github.com/miromannino/miro-windows-manager/raw/master/MiroWindowsManager.spoon.zip @@ -63,24 +61,22 @@ obj.sizes = {2, 3, 3/2} --- Variable --- The sizes that the window can have in full-screen. --- The sizes are expressed as dividend of the entire screen's size. ---- For example `{1, 4/3, 2}` means that it can be 1/1 (hence full screen), 3/4 ---- and 1/2 of the total screen's size. +--- For example `{1, 4/3, 2}` means that it can be 1/1 (hence full screen), 3/4 and 1/2 of the total screen's size. --- Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers. --- Use 'c' for the original size and shape of the window before starting to move it. obj.fullScreenSizes = {1, 2, 'c'} --- Comment: Lots of work here to save users a little work. Previous versions --- required users to call MiroWindowsManager:start() every time they changed --- GRID. The metatable work here watches for those changes and does the work --- :start() would have done. +-- Comment: Lots of work here to save users a little work. Previous versions required users to call +-- MiroWindowsManager:start() every time they changed GRID. The metatable work here watches for those changes and does +-- the work :start() would have done. package.path = package.path..";Spoons/".. ... ..".spoon/?.lua" require('extend_GRID').extend(obj, logger) --- MiroWindowsManager.GRID --- Variable --- The screen's grid size. ---- Ensuring that the numbers in MiroWindowsManager.sizes and ---- Make sure that the numbers in MiroWindowsManager.fullScreenSizes divide h and w to give integers. +--- Make sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give +--- integers. obj.GRID = { w = 24, h = 24, margins = hs.geometry.point(0,0) } function obj.GRID.cell() return hs.geometry(obj.GRID.margins, hs.geometry.size(obj.GRID.w, obj.GRID.h)) @@ -89,15 +85,14 @@ end --- MiroWindowsManager.pushToNextScreen --- Variable ---- Boolean value to decide wether or not to move the window on the next screen ---- if the window is moved the screen edge. +--- Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge. obj.pushToNextScreen = false --- MiroWindowsManager.resizeRate --- Variable ---- Float value to decide the the rate to resize windows. With a value of 1.05 it means that ---- everytime the window is made taller/wider by 5% more (or shorter/thinner by 5% less) +--- Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made +--- taller/wider (or shorter/thinner) in 5% increments. obj.resizeRate = 1.05 -- ## Internal @@ -117,7 +112,7 @@ obj._growthsRel = { thinner = { opp = 'wider', dim = 'w', pos = 'x', growthSign = -1 }, } --- The keys used to move, generally the arrow keys, but they could also be WASD or something else +-- The keys used to move, generally the arrow keys, but they could also be WASD or something else. obj._movingKeys = { } for _,move in ipairs(obj._directions) do obj._movingKeys[move] = move @@ -152,11 +147,11 @@ end -- ### Utilities -function titleCase(str) +local function titleCase(str) return (str:gsub('^%l', string.upper)) end -function round(num) +local function round(num) if num >= 0 then return math.floor(num+.499999999) else @@ -164,16 +159,16 @@ function round(num) end end --- Accessor for functions on the frontmost window -function frontmostWindow() +-- Accessor for functions on the frontmost window. +local function frontmostWindow() return hs.window.frontmostWindow() end -function frontmostScreen() +local function frontmostScreen() return frontmostWindow():screen() end -function frontmostCell() +local function frontmostCell() local win = frontmostWindow() return hs.grid.get(win, win:screen()) end @@ -227,10 +222,10 @@ end --- MiroWindowsManager:resize(growth) --- Method ---- Resize the frontmost window taller, shorter, wider, thinner. +--- Resize the frontmost window taller, shorter, wider, or thinner. --- --- Parameters: ---- * growth - 'taller', 'shorter', 'wider', 'thinner' +--- * growth - 'taller', 'shorter', 'wider', or 'thinner' --- --- Returns: --- * The MiroWindowsManager object @@ -240,9 +235,11 @@ function obj:resize(growth) local w = frontmostWindow() local fr = w:frame() - local growthDiff = fr[self._growthsRel[growth].dim] * (self.resizeRate - 1) - fr[self._growthsRel[growth].pos] = fr[self._growthsRel[growth].pos] - (self._growthsRel[growth].growthSign * growthDiff / 2) - fr[self._growthsRel[growth].dim] = fr[self._growthsRel[growth].dim] + (self._growthsRel[growth].growthSign * growthDiff) + local growthDiff = fr[self._growthsRel[growth].dim] * (self.resizeRate - 1) + fr[self._growthsRel[growth].pos] = + fr[self._growthsRel[growth].pos] - (self._growthsRel[growth].growthSign * growthDiff / 2) + fr[self._growthsRel[growth].dim] = + fr[self._growthsRel[growth].dim] + (self._growthsRel[growth].growthSign * growthDiff) if fr['y'] < 0 then fr['y'] = 0 end if fr['x'] < 0 then fr['x'] = 0 end @@ -300,16 +297,16 @@ end --- Tap both directions to go full width / height. --- --- Parameters: ---- * move - 'up', 'down', 'left', 'right' +--- * move - 'up', 'down', 'left', or 'right' --- --- Returns: --- * The MiroWindowsManager object function obj:go(move) registerPress(move) if currentlyPressed(self._directionsRel[move].opp) then - -- if still keydown moving the in the opposite direction, go full width/height - logger.i("Maximising " .. self._directionsRel[move].dim .. " since " - .. self._directionsRel[move].opp .." still active.") + -- if still keydown moving in the opposite direction, go full width/height + logger.i("Maximising " .. self._directionsRel[move].dim .. " since " .. self._directionsRel[move].opp .. + " still active.") self:growFully(self._directionsRel[move].dim) -- full width/height else local cell = frontmostCell() @@ -340,17 +337,19 @@ function obj:fullscreen() if seq == 0 then if hs.fnutils.contains(self.fullScreenSizes, 'c') then - logger.i("Since we are at seq 0, storing current position to use it with 'c' for window " .. frontmostWindow():id()) + logger.i("Since we are at seq 0, storing current position to use it with 'c' for window " .. + frontmostWindow():id()) self._originalPositionStore['fullscreen'][frontmostWindow():id()] = frontmostCell() end end - seq = seq % #self.fullScreenSizes + 1 -- if seq = #self.fullScreenSizes then 0 so next seq = 1 (we cycle through sizes) + -- if seq = #self.fullScreenSizes then 0 so next seq = 1 (we cycle through sizes) + seq = seq % #self.fullScreenSizes + 1 logger.i("Updating seq to " .. tostring(seq) .." (size: ".. tostring(self.fullScreenSizes[seq]) ..")") if self.fullScreenSizes[seq] == 'c' then logger.i("Seq is 'c' but we don't have a saved position, skip to the next one") - if not self._originalPositionStore['fullscreen'][frontmostWindow():id()] then + if not self._originalPositionStore['fullscreen'][frontmostWindow():id()] then seq = seq % #self.fullScreenSizes + 1 end end @@ -384,12 +383,11 @@ function obj:currentSeq(side) local width = frontmostCell()[dim] local relative_size = self.GRID[dim] / width - -- TODO local lastMatchedSeq = self._lastSeq[side] and -- we've recorded a last seq, and self.sizes[self._lastSeq[side]] and -- it's a valid index to sizes self._lastSeq[side] - local lastMatchedSeqMatchesFrontmost = + local lastMatchedSeqMatchesFrontmost = lastMatchedSeq and (self.sizes[lastMatchedSeq] == relative_size) -- cleanup @@ -432,22 +430,21 @@ function obj:currentlyBound(side) local cell = frontmostCell() if side == 'up' then return cell.y == 0 - elseif side == 'down' then - return cell.y + cell.h == self.GRID.h - elseif side == 'left' then - return cell.x == 0 - elseif side == 'right' then - return cell.x + cell.w == self.GRID.w - end - end + elseif side == 'down' then + return cell.y + cell.h == self.GRID.h + elseif side == 'left' then + return cell.x == 0 + elseif side == 'right' then + return cell.x + cell.w == self.GRID.w + end +end -- ### Fullscreen methods -- Query whether window is centered function obj:currentlyCentered() local cell = frontmostCell() - return cell.w + 2 * cell.x == self.GRID.w and - cell.h + 2 * cell.y == self.GRID.h + return cell.w + 2 * cell.x == self.GRID.w and cell.h + 2 * cell.y == self.GRID.h end function obj:snap_to_grid(cell) @@ -464,8 +461,8 @@ function obj:currentFullscreenSeq() self.fullScreenSizes[self._lastFullscreenSeq] and -- it's (still) a valid index to fullScreenSizes cell == self:getFullscreenCell(self._lastFullscreenSeq) then -- last matched seq is same as the current fullscreen logger.i('last matched seq is same as current cell, so returning seq = ' .. tostring(self._lastFullscreenSeq)) - return self._lastFullscreenSeq - else + return self._lastFullscreenSeq + else self._lastFullscreenSeq = nil -- cleanup if the last matched seq doesn't match the frontmost end @@ -478,20 +475,18 @@ function obj:currentFullscreenSeq() end end - -- we cannot find any fullscreen size that match the current window state, so we start with 0 + -- we cannot find any fullscreen size that matches the current window state, so we start with 0 return 0 end -- Set fullscreen sequence function obj:setToFullscreenSeq(seq) - logger.i('lastMatchedSeq: ' .. tostring(lastMatchedSeq)) - self._setPosition(self:getFullscreenCell(seq)) if self.fullScreenSizes[seq] == 'c' then - -- we want to use the value only once and then discarge - -- this is in case the window was in one of the full screen position/size + -- we want to use the value only once and then discard it + -- this is in case the window was in one of the full screen positions/sizes self._originalPositionStore['fullscreen'][frontmostWindow():id()] = nil end @@ -546,12 +541,13 @@ obj.hotkeys = {} --- * down: for the down action (usually `{hyper, "down"}`) --- * fullscreen: for the full-screen action (e.g. `{hyper, "f"}`) --- * center: for the center action (e.g. `{hyper, "c"}`) ---- * move: for the move action (e.g. `{hyper, "v"}`). The move action is ---- active as soon as the hotkey is pressed. While active the left, ---- right, up or down keys can be used (these are configured by ---- the actions above). +--- * move: for the move action (e.g. `{hyper, "v"}`). The move action is active as soon as the hotkey is pressed. +--- While active the left, right, up or down keys can be used (these are configured by the actions above). +--- * resize: for the resize action (e.g. `{hyper, "d"}`). The resize action is active as soon as the hotkey is +--- pressed. While active the left, right, up or down keys can be used (these are configured by the actions +--- above). --- ---- A configuration example can be: +--- A configuration example: --- ``` lua --- local hyper = {"ctrl", "alt", "cmd"} --- spoon.MiroWindowsManager:bindHotkeys({ @@ -561,20 +557,23 @@ obj.hotkeys = {} --- right = {hyper, "right"}, --- fullscreen = {hyper, "f"}, --- center = {hyper, "c"}, ---- move = {hyper, "v"} +--- move = {hyper, "v"}, +--- resize = {hyper, "d" } --- }) +--- ``` --- ---- In this example ctrl+alt+cmd+up will perform the 'up' action ---- Keeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up,down,left, and right +--- In this example ctrl+alt+cmd+up will perform the 'up' action. +--- Pressing ctrl+alt+cmd+c the window will be centered. --- Pressing ctrl+alt+cmd+f the window will be maximized. ---- ``` +--- Keeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up, down, left, and right. +--- Keeping ctrl+alt+cmd+d pressed you can resize the window using the arrow keys up, down, left, and right. function obj:bindHotkeys(mapping) logger.i("Bind Hotkeys for Miro's Windows Manager") for _,direction in ipairs(self._directions) do if mapping[direction] then self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping[direction][1], + mapping[direction][1], mapping[direction][2], function() self:go(direction) end, function() cancelPress(direction) end) @@ -587,38 +586,38 @@ function obj:bindHotkeys(mapping) if mapping.fullscreen then self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping.fullscreen[1], + mapping.fullscreen[1], mapping.fullscreen[2], function() self:fullscreen() end) end if mapping.center then self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping.center[1], + mapping.center[1], mapping.center[2], function() self:center() end) end if mapping.move then self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping.move[1], - mapping.move[2], - function() self:_moveModeOn() end, + mapping.move[1], + mapping.move[2], + function() self:_moveModeOn() end, function() self:_moveModeOff() end) end if mapping.resize then self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping.resize[1], - mapping.resize[2], - function() self:_resizeModeOn() end, + mapping.resize[1], + mapping.resize[2], + function() self:_resizeModeOn() end, function() self:_resizeModeOff() end) end hs.hotkey.bind( {"ctrl", "alt", "cmd"}, "l", - function () + function () logger.i('window id: ' .. tostring(frontmostWindow():id())) logger.i('windows: ' .. hs.inspect(self._originalPositionStore['fullscreen'])) end) @@ -627,6 +626,7 @@ function obj:bindHotkeys(mapping) --- MiroWindowsManager:init() --- Method +--- Currently does nothing (implemented so that treating this Spoon like others won't cause errors). function obj:init() -- void (but it could be used to initialize the module) end From fa6245cebeffbf48c8ab06db4981a32aeb47a783 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Sun, 11 Nov 2018 14:20:57 +1100 Subject: [PATCH 02/41] Use hs.hotkey.modal for modal move & resize --- MiroWindowsManager.spoon/init.lua | 72 ++++++++----------------------- 1 file changed, 17 insertions(+), 55 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 528d4c6..7247c16 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -193,32 +193,6 @@ function obj:move(side) end return self end -function obj:_moveModeOn() - logger.i("Move Mode on") - self._moveModeKeyWatcher = hs.eventtap.new({ hs.eventtap.event.types.keyDown }, function(ev) - local keyCode = ev:getKeyCode() - - if keyCode == hs.keycodes.map[self._movingKeys['left']] then - self:move('left') - return true - elseif keyCode == hs.keycodes.map[self._movingKeys['right']] then - self:move('right') - return true - elseif keyCode == hs.keycodes.map[self._movingKeys['down']] then - self:move('down') - return true - elseif keyCode == hs.keycodes.map[self._movingKeys['up']] then - self:move('up') - return true - else - return false - end - end):start() -end -function obj:_moveModeOff() - logger.i("Move Mode off"); - self._moveModeKeyWatcher:stop() -end --- MiroWindowsManager:resize(growth) --- Method @@ -247,31 +221,6 @@ function obj:resize(growth) w:setFrame(fr) return self end -function obj:_resizeModeOn() - logger.i("Resize Mode on") - self._resizeModeKeyWatcher = hs.eventtap.new({ hs.eventtap.event.types.keyDown }, function(ev) - local keyCode = ev:getKeyCode() - if keyCode == hs.keycodes.map[self._movingKeys['left']] then - self:resize('thinner') - return true - elseif keyCode == hs.keycodes.map[self._movingKeys['right']] then - self:resize('wider') - return true - elseif keyCode == hs.keycodes.map[self._movingKeys['down']] then - self:resize('shorter') - return true - elseif keyCode == hs.keycodes.map[self._movingKeys['up']] then - self:resize('taller') - return true - else - return false - end - end):start() -end -function obj:_resizeModeOff() - logger.i("Resize Mode off"); - self._resizeModeKeyWatcher:stop() -end --- MiroWindowsManager:growFully(growth) --- Method @@ -599,19 +548,32 @@ function obj:bindHotkeys(mapping) end if mapping.move then + local modal = hs.hotkey.modal.new() + function modal:entered() logger.i("Move Mode on") end + function modal:exited() logger.i("Move Mode off") end + hs.fnutils.each(self._movingKeys, function(move) + modal:bind(mapping.move[1], self._movingKeys[move], function () self:move(move) end) + end) self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.move[1], mapping.move[2], - function() self:_moveModeOn() end, - function() self:_moveModeOff() end) + function() modal:enter() end, + function() modal:exit() end) end if mapping.resize then + local modal = hs.hotkey.modal.new() + function modal:entered() logger.i("Resize Mode on") end + function modal:exited() logger.i("Resize Mode off") end + local map = { left = 'thinner', right = 'wider', down = 'shorter', up = 'taller' } + for move,resize in pairs(map) do + modal:bind(mapping.move[1], move, function () self:resize(resize) end) + end self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.resize[1], mapping.resize[2], - function() self:_resizeModeOn() end, - function() self:_resizeModeOff() end) + function() modal:enter() end, + function() modal:exit() end) end hs.hotkey.bind( From 7bfcb79a08c2ab05d76eb64ee8c38a32fff9119c Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Sun, 11 Nov 2018 18:10:52 +1100 Subject: [PATCH 03/41] bugfix: don't force windows from higher or lefter screens onto the primary screen --- MiroWindowsManager.spoon/init.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 7247c16..70e1a9e 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -215,8 +215,7 @@ function obj:resize(growth) fr[self._growthsRel[growth].dim] = fr[self._growthsRel[growth].dim] + (self._growthsRel[growth].growthSign * growthDiff) - if fr['y'] < 0 then fr['y'] = 0 end - if fr['x'] < 0 then fr['x'] = 0 end + fr = fr:intersect(frontmostScreen():frame()) -- avoid sizing out of bounds w:setFrame(fr) return self From f3e19fed941785c5bf7f4756fcbbeaa0c51a07af Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Sun, 11 Nov 2018 18:23:05 +1100 Subject: [PATCH 04/41] =?UTF-8?q?Use=20`hs.hotkey.modal`=20for=20`?= =?UTF-8?q?=E2=86=90=20+=20=E2=86=92`=20and=20`=E2=86=91=20+=20=E2=86=93`?= =?UTF-8?q?=20double=20taps,=20and=20retire=20watchers=20&=20etc.=20that?= =?UTF-8?q?=20are=20no=20longer=20needed.=20Extend=20double=20tap=20behavi?= =?UTF-8?q?our=20to=20move=20&=20resize=20states.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MiroWindowsManager.spoon/init.lua | 162 ++++++++++++++++-------------- 1 file changed, 85 insertions(+), 77 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 70e1a9e..3e96790 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -120,29 +120,8 @@ end obj._originalPositionStore = { fullscreen = {} } -obj._moveModeKeyWatcher = nil -obj._resizeModeKeyWatcher = nil - -obj._pressed = {} -obj._pressTimers = {} obj._lastSeq = {} obj._lastFullscreenSeq = nil -local function initPressed(move) - obj._pressed[move] = false - obj._pressTimers[move] = hs.timer.doAfter(1, function() obj._pressed[move] = false end) -end -hs.fnutils.each(obj._directions, initPressed) -local function registerPress(direction) - obj._pressed[direction] = true - obj._pressTimers[direction]:start() -end -local function cancelPress(direction) - obj._pressed[direction] = false - obj._pressTimers[direction]:stop() -end -local function currentlyPressed(direction) - return obj._pressed[direction] -end -- ### Utilities @@ -250,15 +229,8 @@ end --- Returns: --- * The MiroWindowsManager object function obj:go(move) - registerPress(move) - if currentlyPressed(self._directionsRel[move].opp) then - -- if still keydown moving in the opposite direction, go full width/height - logger.i("Maximising " .. self._directionsRel[move].dim .. " since " .. self._directionsRel[move].opp .. - " still active.") - self:growFully(self._directionsRel[move].dim) -- full width/height - else - local cell = frontmostCell() - local seq = self:currentSeq(move) -- current sequence index or 0 if out of sequence + local cell = frontmostCell() + local seq = self:currentSeq(move) -- current sequence index or 0 if out of sequence logger.i("We're at ".. move .." sequence ".. tostring(seq) .." (".. cell.string ..")") @@ -518,62 +490,63 @@ obj.hotkeys = {} function obj:bindHotkeys(mapping) logger.i("Bind Hotkeys for Miro's Windows Manager") + -- `growFully` modals + local growFullyModals = {} for _,direction in ipairs(self._directions) do - if mapping[direction] then - self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping[direction][1], - mapping[direction][2], - function() self:go(direction) end, - function() cancelPress(direction) end) + local modal = hs.hotkey.modal.new() - -- save the keys that the user decided to be for directions, - -- generally the arrows keys, but it could be also WASD. - self._movingKeys[direction] = mapping[direction][2] - end - end + -- primary direction + function modal.entered(_) logger.d(direction..' modal entered.') end + function modal.exited(_) logger.d(direction..' modal exited.') end - if mapping.fullscreen then - self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping.fullscreen[1], - mapping.fullscreen[2], - function() self:fullscreen() end) + -- opposite direction: growFully() + if mapping[direction] and mapping[self._directionsRel[direction].opp] then + modal:bind( + mapping[direction][1], + mapping[self._directionsRel[direction].opp][2], + function() + logger.i('… from '..direction..', `grow`ing.') + self:growFully(self._directionsRel[direction].dim) + end) + growFullyModals[direction] = modal end + end - if mapping.center then + -- `go` hotkeys + for _,direction in ipairs(self._directions) do + if mapping[direction] then self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping.center[1], - mapping.center[2], - function() self:center() end) + mapping[direction][1], + mapping[direction][2], + function() + self:go(direction) + growFullyModals[direction]:enter() + end, + function() + growFullyModals[direction]:exit() + end) + + -- save the keys that the user decided to be for directions, + -- generally the arrows keys, but it could be also WASD. + self._movingKeys[direction] = mapping[direction][2] end + end - if mapping.move then - local modal = hs.hotkey.modal.new() - function modal:entered() logger.i("Move Mode on") end - function modal:exited() logger.i("Move Mode off") end - hs.fnutils.each(self._movingKeys, function(move) - modal:bind(mapping.move[1], self._movingKeys[move], function () self:move(move) end) - end) - self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping.move[1], - mapping.move[2], - function() modal:enter() end, - function() modal:exit() end) - end + -- `fullscreen` hotkey + if mapping.fullscreen then + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( + mapping.fullscreen[1], + mapping.fullscreen[2], + function() self:fullscreen() end) + end - if mapping.resize then - local modal = hs.hotkey.modal.new() - function modal:entered() logger.i("Resize Mode on") end - function modal:exited() logger.i("Resize Mode off") end - local map = { left = 'thinner', right = 'wider', down = 'shorter', up = 'taller' } - for move,resize in pairs(map) do - modal:bind(mapping.move[1], move, function () self:resize(resize) end) - end - self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping.resize[1], - mapping.resize[2], - function() modal:enter() end, - function() modal:exit() end) - end + -- `center` hotkey + if mapping.center then + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( + mapping.center[1], + mapping.center[2], + function() self:center() end) + end hs.hotkey.bind( {"ctrl", "alt", "cmd"}, @@ -582,9 +555,44 @@ function obj:bindHotkeys(mapping) logger.i('window id: ' .. tostring(frontmostWindow():id())) logger.i('windows: ' .. hs.inspect(self._originalPositionStore['fullscreen'])) end) + -- `move` modifier + if mapping.move then + local modal = hs.hotkey.modal.new() + function modal.entered(_) logger.i("Move Mode on") end + function modal.exited(_) logger.i("Move Mode off") end + hs.fnutils.each(self._movingKeys, function(move) + modal:bind(mapping.move[1], self._movingKeys[move], + function() self:move(move); growFullyModals[move]:enter() end, + function() growFullyModals[move]:exit() end) + end) + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( + mapping.move[1], + mapping.move[2], + function() modal:enter() end, + function() modal:exit() end) + end + -- `resize` modifier + if mapping.resize then + local modal = hs.hotkey.modal.new() + function modal:entered() logger.i("Resize Mode on") end + function modal:exited() logger.i("Resize Mode off") end + local map = { left = 'thinner', right = 'wider', down = 'shorter', up = 'taller' } + local mapR = {}; for k,v in pairs(map) do mapR[v] = k end + for move,resize in pairs(map) do + modal:bind(mapping.move[1], move, + function() self:resize(resize); growFullyModals[mapR[resize]]:enter() end, + function() growFullyModals[mapR[resize]]:exit() end) + end + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( + mapping.resize[1], + mapping.resize[2], + function() modal:enter() end, + function() modal:exit() end) end +end + --- MiroWindowsManager:init() --- Method --- Currently does nothing (implemented so that treating this Spoon like others won't cause errors). From 5bf7b66d20e9f779a4d0b1dc9327da49b5af2e24 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Sun, 11 Nov 2018 18:35:34 +1100 Subject: [PATCH 05/41] minor: cleanup, docs & logging --- MiroWindowsManager.spoon/docs.json | 296 +++++++++++++++++++---------- MiroWindowsManager.spoon/init.lua | 67 ++++--- README.md | 37 ++-- 3 files changed, 254 insertions(+), 146 deletions(-) diff --git a/MiroWindowsManager.spoon/docs.json b/MiroWindowsManager.spoon/docs.json index c0bbc6c..5a08201 100644 --- a/MiroWindowsManager.spoon/docs.json +++ b/MiroWindowsManager.spoon/docs.json @@ -15,13 +15,11 @@ "stripped_doc" : [ "The sizes that the window can have. ", "The sizes are expressed as dividend of the entire screen's size. ", - "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total", - "screen's size. ", - "Ensuring that these numbers all divide both dimensions of", - "MiroWindowsManager.GRID to give integers makes everything work better." + "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size. ", + "Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers." ], + "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size. \nMake sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.", "desc" : "The sizes that the window can have.", - "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total\nscreen's size. \nEnsuring that these numbers all divide both dimensions of\nMiroWindowsManager.GRID to give integers makes everything work better.", "notes" : [ ], @@ -40,15 +38,12 @@ "stripped_doc" : [ "The sizes that the window can have in full-screen. ", "The sizes are expressed as dividend of the entire screen's size. ", - "For example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4", - "and 1\/2 of the total screen's size. ", - "Ensuring that these numbers all divide both dimensions of", - "MiroWindowsManager.GRID to give integers makes everything work better. ", - "Special: Use 'c' for the original size and shape of the window before", - "starting to move it, but centered." + "For example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4 and 1\/2 of the total screen's size. ", + "Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.", + "Use 'c' for the original size and shape of the window before starting to move it." ], + "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4 and 1\/2 of the total screen's size. \nMake sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.\nUse 'c' for the original size and shape of the window before starting to move it.", "desc" : "The sizes that the window can have in full-screen.", - "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4\nand 1\/2 of the total screen's size. \nEnsuring that these numbers all divide both dimensions of\nMiroWindowsManager.GRID to give integers makes everything work better. \nSpecial: Use 'c' for the original size and shape of the window before\nstarting to move it, but centered.", "notes" : [ ], @@ -66,12 +61,11 @@ "def" : "MiroWindowsManager.GRID", "stripped_doc" : [ "The screen's grid size. ", - "Ensuring that the numbers in MiroWindowsManager.sizes and", - "MiroWindowsManager.fullScreenSizes divide these numbers to give integers", - "makes everything work better." + "Make sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give", + "integers." ], + "doc" : "The screen's grid size. \nMake sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give\nintegers.", "desc" : "The screen's grid size.", - "doc" : "The screen's grid size. \nEnsuring that the numbers in MiroWindowsManager.sizes and\nMiroWindowsManager.fullScreenSizes divide these numbers to give integers\nmakes everything work better.", "notes" : [ ], @@ -86,22 +80,42 @@ ] }, { - "def" : "MiroWindowsManager.moveToNextScreen", + "def" : "MiroWindowsManager.pushToNextScreen", "stripped_doc" : [ - "Boolean value to decide wether or not to move the window on the next screen", - "if the window is moved the screen edge." + "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge." ], - "desc" : "Boolean value to decide wether or not to move the window on the next screen", - "doc" : "Boolean value to decide wether or not to move the window on the next screen\nif the window is moved the screen edge.", + "doc" : "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge.", + "desc" : "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge.", "notes" : [ ], - "signature" : "MiroWindowsManager.moveToNextScreen", + "signature" : "MiroWindowsManager.pushToNextScreen", "type" : "Variable", "returns" : [ ], - "name" : "moveToNextScreen", + "name" : "pushToNextScreen", + "parameters" : [ + + ] + }, + { + "def" : "MiroWindowsManager.resizeRate", + "stripped_doc" : [ + "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made", + "taller\/wider (or shorter\/thinner) in 5% increments." + ], + "doc" : "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made\ntaller\/wider (or shorter\/thinner) in 5% increments.", + "desc" : "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.resizeRate", + "type" : "Variable", + "returns" : [ + + ], + "name" : "resizeRate", "parameters" : [ ] @@ -114,13 +128,11 @@ ], "type" : "Module", - "desc" : "With this Spoon you will be able to move the window in halves and in", + "desc" : "With this Spoon you will be able to move the window in halves and in corners using your keyboard and mainly using", "Constructor" : [ ], - "Field" : [ - - ], + "doc" : "With this Spoon you will be able to move the window in halves and in corners using your keyboard and mainly using\narrows. You would also be able to resize them by thirds, quarters, or halves. \nOfficial homepage for more info and documentation:\n[https:\/\/github.com\/miromannino\/miro-windows-manager](https:\/\/github.com\/miromannino\/miro-windows-manager)\n\nNOTE: This Spoon sets `hs.grid` globals with `hs.grid.setGrid()`, `hs.grid.MARGINX`, and `hs.grid.MARGINY`.\nChanging MiroWindowsManager.GRID will change these globals.\n\nDownload:\nhttps:\/\/github.com\/miromannino\/miro-windows-manager\/raw\/master\/MiroWindowsManager.spoon.zip", "Method" : [ { "def" : "MiroWindowsManager:move(side)", @@ -128,8 +140,8 @@ "Move the frontmost window up, down, left, right. ", "" ], - "desc" : "Move the frontmost window up, down, left, right.", "doc" : "Move the frontmost window up, down, left, right. \n\nParameters:\n * side - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Move the frontmost window up, down, left, right.", "notes" : [ ], @@ -144,14 +156,36 @@ "" ] }, + { + "def" : "MiroWindowsManager:resize(growth)", + "stripped_doc" : [ + "Resize the frontmost window taller, shorter, wider, or thinner.", + "" + ], + "doc" : "Resize the frontmost window taller, shorter, wider, or thinner.\n\nParameters:\n * growth - 'taller', 'shorter', 'wider', or 'thinner'\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Resize the frontmost window taller, shorter, wider, or thinner.", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:resize(growth)", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "name" : "resize", + "parameters" : [ + " * growth - 'taller', 'shorter', 'wider', or 'thinner'", + "" + ] + }, { "def" : "MiroWindowsManager:growFully(growth)", "stripped_doc" : [ - "Grow the frontmost window to full width \/ height taller, wider. ", + "Grow the frontmost window to full width \/ height.", "" ], - "desc" : "Grow the frontmost window to full width \/ height taller, wider.", - "doc" : "Grow the frontmost window to full width \/ height taller, wider. \n\nParameters:\n * growth - 'taller', or 'wider'\n\nReturns:\n * The MiroWindowsManager object", + "doc" : "Grow the frontmost window to full width \/ height.\n\nParameters:\n * dimension - 'h', or 'w'\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Grow the frontmost window to full width \/ height.", "notes" : [ ], @@ -162,7 +196,7 @@ ], "name" : "growFully", "parameters" : [ - " * growth - 'taller', or 'wider'", + " * dimension - 'h', or 'w'", "" ] }, @@ -173,8 +207,8 @@ "Tap both directions to go full width \/ height. ", "" ], + "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \n\nParameters:\n * move - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", "desc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there.", - "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \n\nParameters:\n * move - 'up', 'down', 'left', 'right'\n\nReturns:\n * The MiroWindowsManager object", "notes" : [ ], @@ -185,7 +219,7 @@ ], "name" : "go", "parameters" : [ - " * move - 'up', 'down', 'left', 'right'", + " * move - 'up', 'down', 'left', or 'right'", "" ] }, @@ -195,8 +229,8 @@ "Fullscreen, or cycle to next fullscreen option", "" ], - "desc" : "Fullscreen, or cycle to next fullscreen option", "doc" : "Fullscreen, or cycle to next fullscreen option\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Fullscreen, or cycle to next fullscreen option", "notes" : [ ], @@ -217,8 +251,8 @@ "Center", "" ], - "desc" : "Center", "doc" : "Center\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Center", "notes" : [ ], @@ -239,8 +273,8 @@ "Binds hotkeys for Miro's Windows Manager", "" ], + "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n * center: for the center action (e.g. `{hyper, \"c\"}`)\n * move: for the move action (e.g. `{hyper, \"v\"}`). The move action is active as soon as the hotkey is pressed.\n While active the left, right, up or down keys can be used (these are configured by the actions above). \n * resize: for the resize action (e.g. `{hyper, \"d\"}`). The resize action is active as soon as the hotkey is\n pressed. While active the left, right, up or down keys can be used (these are configured by the actions\n above).\n\nA configuration example:\n``` lua\nlocal hyper = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {hyper, \"up\"},\n down = {hyper, \"down\"},\n left = {hyper, \"left\"},\n right = {hyper, \"right\"},\n fullscreen = {hyper, \"f\"},\n center = {hyper, \"c\"},\n move = {hyper, \"v\"},\n resize = {hyper, \"d\" }\n})\n```\n\nIn this example ctrl+alt+cmd+up will perform the 'up' action.\nPressing ctrl+alt+cmd+c the window will be centered.\nPressing ctrl+alt+cmd+f the window will be maximized.\nKeeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up, down, left, and right.\nKeeping ctrl+alt+cmd+d pressed you can resize the window using the arrow keys up, down, left, and right.", "desc" : "Binds hotkeys for Miro's Windows Manager", - "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n\nA configuration example can be:\n``` lua\nlocal mods = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {mods, \"up\"},\n down = {mods, \"down\"},\n left = {mods, \"left\"},\n right = {mods, \"right\"},\n fullscreen = {mods, \"f\"},\n center = {mods, \"c\"},\n move = {mods, \"v\"},\n resize = {mods, \"d\"}\n})\n```", "notes" : [ ], @@ -257,30 +291,42 @@ " * up: for the up action (usually {hyper, \"up\"})", " * down: for the down action (usually `{hyper, \"down\"}`)", " * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)", + " * center: for the center action (e.g. `{hyper, \"c\"}`)", + " * move: for the move action (e.g. `{hyper, \"v\"}`). The move action is active as soon as the hotkey is pressed.", + " While active the left, right, up or down keys can be used (these are configured by the actions above). ", + " * resize: for the resize action (e.g. `{hyper, \"d\"}`). The resize action is active as soon as the hotkey is", + " pressed. While active the left, right, up or down keys can be used (these are configured by the actions", + " above).", "", - "A configuration example can be:", + "A configuration example:", "``` lua", - "local mods = {\"ctrl\", \"alt\", \"cmd\"}", + "local hyper = {\"ctrl\", \"alt\", \"cmd\"}", "spoon.MiroWindowsManager:bindHotkeys({", - " up = {mods, \"up\"},", - " down = {mods, \"down\"},", - " left = {mods, \"left\"},", - " right = {mods, \"right\"},", - " fullscreen = {mods, \"f\"},", - " center = {mods, \"c\"},", - " move = {mods, \"v\"},", - " resize = {mods, \"d\"}", + " up = {hyper, \"up\"},", + " down = {hyper, \"down\"},", + " left = {hyper, \"left\"},", + " right = {hyper, \"right\"},", + " fullscreen = {hyper, \"f\"},", + " center = {hyper, \"c\"},", + " move = {hyper, \"v\"},", + " resize = {hyper, \"d\" }", "})", - "```" + "```", + "", + "In this example ctrl+alt+cmd+up will perform the 'up' action.", + "Pressing ctrl+alt+cmd+c the window will be centered.", + "Pressing ctrl+alt+cmd+f the window will be maximized.", + "Keeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up, down, left, and right.", + "Keeping ctrl+alt+cmd+d pressed you can resize the window using the arrow keys up, down, left, and right." ] }, { "def" : "MiroWindowsManager:init()", "stripped_doc" : [ - + "Currently does nothing (implemented so that treating this Spoon like others won't cause errors)." ], - "desc" : "UNKNOWN DESC", - "doc" : "", + "doc" : "Currently does nothing (implemented so that treating this Spoon like others won't cause errors).", + "desc" : "Currently does nothing (implemented so that treating this Spoon like others won't cause errors).", "notes" : [ ], @@ -298,18 +344,16 @@ "Command" : [ ], - "doc" : "With this Spoon you will be able to move the window in halves and in\ncorners using your keyboard and mainly using arrows. You would also be able\nto resize them by thirds, quarters, or halves. \nOfficial homepage for more info and documentation:\n[https:\/\/github.com\/miromannino\/miro-windows-manager](https:\/\/github.com\/miromannino\/miro-windows-manager)\n\nNOTE: This Spoon sets `hs.grid` globals with `hs.grid.setGrid()`,\n`hs.grid.MARGINX`, and `hs.grid.MARGINY`. Changing MiroWindowsManager.GRID\nwill change these globals.\n\nDownload:\nhttps:\/\/github.com\/miromannino\/miro-windows-manager\/raw\/master\/MiroWindowsManager.spoon.zip", "items" : [ { "def" : "MiroWindowsManager.GRID", "stripped_doc" : [ "The screen's grid size. ", - "Ensuring that the numbers in MiroWindowsManager.sizes and", - "MiroWindowsManager.fullScreenSizes divide these numbers to give integers", - "makes everything work better." + "Make sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give", + "integers." ], + "doc" : "The screen's grid size. \nMake sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give\nintegers.", "desc" : "The screen's grid size.", - "doc" : "The screen's grid size. \nEnsuring that the numbers in MiroWindowsManager.sizes and\nMiroWindowsManager.fullScreenSizes divide these numbers to give integers\nmakes everything work better.", "notes" : [ ], @@ -328,15 +372,12 @@ "stripped_doc" : [ "The sizes that the window can have in full-screen. ", "The sizes are expressed as dividend of the entire screen's size. ", - "For example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4", - "and 1\/2 of the total screen's size. ", - "Ensuring that these numbers all divide both dimensions of", - "MiroWindowsManager.GRID to give integers makes everything work better. ", - "Special: Use 'c' for the original size and shape of the window before", - "starting to move it, but centered." + "For example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4 and 1\/2 of the total screen's size. ", + "Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.", + "Use 'c' for the original size and shape of the window before starting to move it." ], + "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4 and 1\/2 of the total screen's size. \nMake sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.\nUse 'c' for the original size and shape of the window before starting to move it.", "desc" : "The sizes that the window can have in full-screen.", - "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4\nand 1\/2 of the total screen's size. \nEnsuring that these numbers all divide both dimensions of\nMiroWindowsManager.GRID to give integers makes everything work better. \nSpecial: Use 'c' for the original size and shape of the window before\nstarting to move it, but centered.", "notes" : [ ], @@ -351,22 +392,42 @@ ] }, { - "def" : "MiroWindowsManager.moveToNextScreen", + "def" : "MiroWindowsManager.pushToNextScreen", "stripped_doc" : [ - "Boolean value to decide wether or not to move the window on the next screen", - "if the window is moved the screen edge." + "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge." ], - "desc" : "Boolean value to decide wether or not to move the window on the next screen", - "doc" : "Boolean value to decide wether or not to move the window on the next screen\nif the window is moved the screen edge.", + "doc" : "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge.", + "desc" : "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge.", "notes" : [ ], - "signature" : "MiroWindowsManager.moveToNextScreen", + "signature" : "MiroWindowsManager.pushToNextScreen", "type" : "Variable", "returns" : [ ], - "name" : "moveToNextScreen", + "name" : "pushToNextScreen", + "parameters" : [ + + ] + }, + { + "def" : "MiroWindowsManager.resizeRate", + "stripped_doc" : [ + "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made", + "taller\/wider (or shorter\/thinner) in 5% increments." + ], + "doc" : "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made\ntaller\/wider (or shorter\/thinner) in 5% increments.", + "desc" : "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.resizeRate", + "type" : "Variable", + "returns" : [ + + ], + "name" : "resizeRate", "parameters" : [ ] @@ -376,13 +437,11 @@ "stripped_doc" : [ "The sizes that the window can have. ", "The sizes are expressed as dividend of the entire screen's size. ", - "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total", - "screen's size. ", - "Ensuring that these numbers all divide both dimensions of", - "MiroWindowsManager.GRID to give integers makes everything work better." + "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size. ", + "Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers." ], + "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size. \nMake sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.", "desc" : "The sizes that the window can have.", - "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total\nscreen's size. \nEnsuring that these numbers all divide both dimensions of\nMiroWindowsManager.GRID to give integers makes everything work better.", "notes" : [ ], @@ -402,8 +461,8 @@ "Binds hotkeys for Miro's Windows Manager", "" ], + "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n * center: for the center action (e.g. `{hyper, \"c\"}`)\n * move: for the move action (e.g. `{hyper, \"v\"}`). The move action is active as soon as the hotkey is pressed.\n While active the left, right, up or down keys can be used (these are configured by the actions above). \n * resize: for the resize action (e.g. `{hyper, \"d\"}`). The resize action is active as soon as the hotkey is\n pressed. While active the left, right, up or down keys can be used (these are configured by the actions\n above).\n\nA configuration example:\n``` lua\nlocal hyper = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {hyper, \"up\"},\n down = {hyper, \"down\"},\n left = {hyper, \"left\"},\n right = {hyper, \"right\"},\n fullscreen = {hyper, \"f\"},\n center = {hyper, \"c\"},\n move = {hyper, \"v\"},\n resize = {hyper, \"d\" }\n})\n```\n\nIn this example ctrl+alt+cmd+up will perform the 'up' action.\nPressing ctrl+alt+cmd+c the window will be centered.\nPressing ctrl+alt+cmd+f the window will be maximized.\nKeeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up, down, left, and right.\nKeeping ctrl+alt+cmd+d pressed you can resize the window using the arrow keys up, down, left, and right.", "desc" : "Binds hotkeys for Miro's Windows Manager", - "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n\nA configuration example can be:\n``` lua\nlocal mods = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {mods, \"up\"},\n down = {mods, \"down\"},\n left = {mods, \"left\"},\n right = {mods, \"right\"},\n fullscreen = {mods, \"f\"},\n center = {mods, \"c\"},\n move = {mods, \"v\"},\n resize = {mods, \"d\"}\n})\n```", "notes" : [ ], @@ -420,21 +479,33 @@ " * up: for the up action (usually {hyper, \"up\"})", " * down: for the down action (usually `{hyper, \"down\"}`)", " * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)", + " * center: for the center action (e.g. `{hyper, \"c\"}`)", + " * move: for the move action (e.g. `{hyper, \"v\"}`). The move action is active as soon as the hotkey is pressed.", + " While active the left, right, up or down keys can be used (these are configured by the actions above). ", + " * resize: for the resize action (e.g. `{hyper, \"d\"}`). The resize action is active as soon as the hotkey is", + " pressed. While active the left, right, up or down keys can be used (these are configured by the actions", + " above).", "", - "A configuration example can be:", + "A configuration example:", "``` lua", - "local mods = {\"ctrl\", \"alt\", \"cmd\"}", + "local hyper = {\"ctrl\", \"alt\", \"cmd\"}", "spoon.MiroWindowsManager:bindHotkeys({", - " up = {mods, \"up\"},", - " down = {mods, \"down\"},", - " left = {mods, \"left\"},", - " right = {mods, \"right\"},", - " fullscreen = {mods, \"f\"},", - " center = {mods, \"c\"},", - " move = {mods, \"v\"},", - " resize = {mods, \"d\"}", + " up = {hyper, \"up\"},", + " down = {hyper, \"down\"},", + " left = {hyper, \"left\"},", + " right = {hyper, \"right\"},", + " fullscreen = {hyper, \"f\"},", + " center = {hyper, \"c\"},", + " move = {hyper, \"v\"},", + " resize = {hyper, \"d\" }", "})", - "```" + "```", + "", + "In this example ctrl+alt+cmd+up will perform the 'up' action.", + "Pressing ctrl+alt+cmd+c the window will be centered.", + "Pressing ctrl+alt+cmd+f the window will be maximized.", + "Keeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up, down, left, and right.", + "Keeping ctrl+alt+cmd+d pressed you can resize the window using the arrow keys up, down, left, and right." ] }, { @@ -443,8 +514,8 @@ "Center", "" ], - "desc" : "Center", "doc" : "Center\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Center", "notes" : [ ], @@ -465,8 +536,8 @@ "Fullscreen, or cycle to next fullscreen option", "" ], - "desc" : "Fullscreen, or cycle to next fullscreen option", "doc" : "Fullscreen, or cycle to next fullscreen option\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Fullscreen, or cycle to next fullscreen option", "notes" : [ ], @@ -488,8 +559,8 @@ "Tap both directions to go full width \/ height. ", "" ], + "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \n\nParameters:\n * move - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", "desc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there.", - "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \n\nParameters:\n * move - 'up', 'down', 'left', 'right'\n\nReturns:\n * The MiroWindowsManager object", "notes" : [ ], @@ -500,18 +571,18 @@ ], "name" : "go", "parameters" : [ - " * move - 'up', 'down', 'left', 'right'", + " * move - 'up', 'down', 'left', or 'right'", "" ] }, { "def" : "MiroWindowsManager:growFully(growth)", "stripped_doc" : [ - "Grow the frontmost window to full width \/ height taller, wider. ", + "Grow the frontmost window to full width \/ height.", "" ], - "desc" : "Grow the frontmost window to full width \/ height taller, wider.", - "doc" : "Grow the frontmost window to full width \/ height taller, wider. \n\nParameters:\n * growth - 'taller', or 'wider'\n\nReturns:\n * The MiroWindowsManager object", + "doc" : "Grow the frontmost window to full width \/ height.\n\nParameters:\n * dimension - 'h', or 'w'\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Grow the frontmost window to full width \/ height.", "notes" : [ ], @@ -522,17 +593,17 @@ ], "name" : "growFully", "parameters" : [ - " * growth - 'taller', or 'wider'", + " * dimension - 'h', or 'w'", "" ] }, { "def" : "MiroWindowsManager:init()", "stripped_doc" : [ - + "Currently does nothing (implemented so that treating this Spoon like others won't cause errors)." ], - "desc" : "UNKNOWN DESC", - "doc" : "", + "doc" : "Currently does nothing (implemented so that treating this Spoon like others won't cause errors).", + "desc" : "Currently does nothing (implemented so that treating this Spoon like others won't cause errors).", "notes" : [ ], @@ -552,8 +623,8 @@ "Move the frontmost window up, down, left, right. ", "" ], - "desc" : "Move the frontmost window up, down, left, right.", "doc" : "Move the frontmost window up, down, left, right. \n\nParameters:\n * side - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Move the frontmost window up, down, left, right.", "notes" : [ ], @@ -567,8 +638,33 @@ " * side - 'up', 'down', 'left', or 'right'", "" ] + }, + { + "def" : "MiroWindowsManager:resize(growth)", + "stripped_doc" : [ + "Resize the frontmost window taller, shorter, wider, or thinner.", + "" + ], + "doc" : "Resize the frontmost window taller, shorter, wider, or thinner.\n\nParameters:\n * growth - 'taller', 'shorter', 'wider', or 'thinner'\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Resize the frontmost window taller, shorter, wider, or thinner.", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:resize(growth)", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "name" : "resize", + "parameters" : [ + " * growth - 'taller', 'shorter', 'wider', or 'thinner'", + "" + ] } + ], + "Field" : [ + ], "name" : "MiroWindowsManager" } -] \ No newline at end of file +] diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 3e96790..a9a64aa 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -30,6 +30,7 @@ --- -- ## TODO +-- sticky sides option when shrinking windows -- different sizes lists for specific apps local obj={} @@ -119,6 +120,7 @@ for _,move in ipairs(obj._directions) do end obj._originalPositionStore = { fullscreen = {} } +setmetatable(obj._originalPositionStore, {__mode = 'kv'}) -- weak table, so it doesn't become a memory hog obj._lastSeq = {} obj._lastFullscreenSeq = nil @@ -152,6 +154,12 @@ local function frontmostCell() return hs.grid.get(win, win:screen()) end +-- Set window to cell +local function setPosition(cell) + local win = frontmostWindow() + hs.grid.set(win, cell, win:screen()) +end + -- ## Public --- MiroWindowsManager:move(side) @@ -165,9 +173,10 @@ end --- * The MiroWindowsManager object function obj:move(side) if self:currentlyBound(side) and not self.pushToNextScreen then - logger.i("`self.pushToNextScreen` == false so not moving to ".. side .." screen.") + logger.d("`self.pushToNextScreen` == false so not moving to ".. side .." screen.") else logger.i('Moving '.. side) + hs.grid['pushWindow'.. titleCase(side)](frontmostWindow()) end return self @@ -183,7 +192,7 @@ end --- Returns: --- * The MiroWindowsManager object function obj:resize(growth) - logger.i('resize ' .. growth) + logger.i('Resizing '.. growth) local w = frontmostWindow() local fr = w:frame() @@ -210,10 +219,12 @@ end --- Returns: --- * The MiroWindowsManager object function obj:growFully(dimension) + logger.i('Growing '.. dimension) + local cell = frontmostCell() cell[dimension == 'h' and 'y' or 'x'] = 0 cell[dimension] = self.GRID[dimension] - self._setPosition(cell) + setPosition(cell) return self end @@ -232,13 +243,12 @@ function obj:go(move) local cell = frontmostCell() local seq = self:currentSeq(move) -- current sequence index or 0 if out of sequence - logger.i("We're at ".. move .." sequence ".. tostring(seq) .." (".. cell.string ..")") + logger.d("We're at ".. move .." sequence ".. tostring(seq) .." (".. cell.string ..")") - seq = seq % #self.sizes -- if at end of #self.sizes then wrap to 0 - logger.i("Updating seq to " .. tostring(seq + 1) .." (size: ".. tostring(self.sizes[seq + 1]) ..")") + seq = seq % #self.sizes -- if at end of #self.sizes then wrap to 0 + logger.d("Updating seq to " .. tostring(seq + 1) .." (size: ".. tostring(self.sizes[seq + 1]) ..")") - self:setToSeq(move, seq + 1) - end + self:setToSeq(move, seq + 1) return self end @@ -253,11 +263,11 @@ end --- * The MiroWindowsManager object function obj:fullscreen() local seq = self:currentFullscreenSeq() -- current sequence index or 0 if out of sequence - logger.i("We're at fullscreen sequence ".. tostring(seq) .." (".. frontmostCell().string ..")") + logger.d("We're at fullscreen sequence ".. tostring(seq) .." (".. frontmostCell().string ..")") if seq == 0 then if hs.fnutils.contains(self.fullScreenSizes, 'c') then - logger.i("Since we are at seq 0, storing current position to use it with 'c' for window " .. + logger.d("Since we are at seq 0, storing current position to use it with 'c' for window " .. frontmostWindow():id()) self._originalPositionStore['fullscreen'][frontmostWindow():id()] = frontmostCell() end @@ -265,10 +275,10 @@ function obj:fullscreen() -- if seq = #self.fullScreenSizes then 0 so next seq = 1 (we cycle through sizes) seq = seq % #self.fullScreenSizes + 1 - logger.i("Updating seq to " .. tostring(seq) .." (size: ".. tostring(self.fullScreenSizes[seq]) ..")") + logger.d("Updating seq to " .. tostring(seq) .." (size: ".. tostring(self.fullScreenSizes[seq]) ..")") if self.fullScreenSizes[seq] == 'c' then - logger.i("Seq is 'c' but we don't have a saved position, skip to the next one") + logger.d("Seq is 'c' but we don't have a saved position, skip to the next one") if not self._originalPositionStore['fullscreen'][frontmostWindow():id()] then seq = seq % #self.fullScreenSizes + 1 end @@ -289,9 +299,11 @@ end --- Returns: --- * The MiroWindowsManager object function obj:center() + logger.i('Centering') + local cell = frontmostCell() cell.center = self.GRID.cell().center - self._setPosition(cell) + setPosition(cell) return self end @@ -340,7 +352,7 @@ function obj:setToSeq(move, seq) cell = self:snap_to_grid(cell) - self._setPosition(cell) + setPosition(cell) self._lastSeq[move] = seq return self end @@ -380,7 +392,7 @@ function obj:currentFullscreenSeq() if self._lastFullscreenSeq and -- if there is a saved last matched seq, and self.fullScreenSizes[self._lastFullscreenSeq] and -- it's (still) a valid index to fullScreenSizes cell == self:getFullscreenCell(self._lastFullscreenSeq) then -- last matched seq is same as the current fullscreen - logger.i('last matched seq is same as current cell, so returning seq = ' .. tostring(self._lastFullscreenSeq)) + logger.d('last matched seq is same as current cell, so returning seq = ' .. tostring(self._lastFullscreenSeq)) return self._lastFullscreenSeq else self._lastFullscreenSeq = nil -- cleanup if the last matched seq doesn't match the frontmost @@ -388,9 +400,9 @@ function obj:currentFullscreenSeq() -- trying to see which fullscreen size is the current window for i = 1,#self.fullScreenSizes do - logger.i('analyze seq = ' .. tostring(i)) + logger.d('analyze seq = ' .. tostring(i)) if cell == self:getFullscreenCell(i) then - logger.i('cell == self:getFullscreenCell(seq)') + logger.d('cell == self:getFullscreenCell(seq)') return i end end @@ -402,7 +414,7 @@ end -- Set fullscreen sequence function obj:setToFullscreenSeq(seq) - self._setPosition(self:getFullscreenCell(seq)) + setPosition(self:getFullscreenCell(seq)) if self.fullScreenSizes[seq] == 'c' then -- we want to use the value only once and then discard it @@ -423,8 +435,8 @@ function obj:getFullscreenCell(seq) return self._originalPositionStore['fullscreen'][frontmostWindow():id()] end - logger.i('window id: ' .. tostring(frontmostWindow():id())) - logger.i('windows: ' .. hs.inspect(self._originalPositionStore['fullscreen'])) + logger.d('window id: ' .. tostring(frontmostWindow():id())) + logger.d('windows: ' .. hs.inspect(self._originalPositionStore['fullscreen'])) size = hs.geometry.size( self.GRID.w / seq_factor, @@ -438,12 +450,6 @@ function obj:getFullscreenCell(seq) return self:snap_to_grid(hs.geometry(pnt, size)) end --- Set window to cell -function obj._setPosition(cell) - local win = frontmostWindow() - hs.grid.set(win, cell, win:screen()) -end - -- ## Spoon mechanics (`bind`, `init`) @@ -548,13 +554,6 @@ function obj:bindHotkeys(mapping) function() self:center() end) end - hs.hotkey.bind( - {"ctrl", "alt", "cmd"}, - "l", - function () - logger.i('window id: ' .. tostring(frontmostWindow():id())) - logger.i('windows: ' .. hs.inspect(self._originalPositionStore['fullscreen'])) - end) -- `move` modifier if mapping.move then local modal = hs.hotkey.modal.new() @@ -582,7 +581,7 @@ function obj:bindHotkeys(mapping) for move,resize in pairs(map) do modal:bind(mapping.move[1], move, function() self:resize(resize); growFullyModals[mapR[resize]]:enter() end, - function() growFullyModals[mapR[resize]]:exit() end) + function() growFullyModals[mapR[resize]]:exit() end) end self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.resize[1], diff --git a/README.md b/README.md index bc9343d..314d861 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Miro's windows manager -With this script you will be able to move the window in halves and in corners using your keyboard and mainly using arrows. You would also be able to resize them by thirds, quarters, or halves. +With this Hammerspoon Spoon you will be able to move windows in halves and in corners using your keyboard, mainly using arrows. You will also be able to resize them by thirds, quarters, or halves. Other projects (e.g. Spectacle) move windows in halves using arrows, and in corners using other counterintuitive shortcuts, like letters, which makes things confusing. @@ -12,19 +12,20 @@ This script needs Hammerspoon in order to work. - Extract the zip file, containing `MiroWindowsManager.spoon` in `~/.hammerspoon/Spoons` - Now you need to configure Hammerspoon in order to load this spoon in `~/.hammerspoon/Spoons/MiroWindowsManager.spoon` adding the following snippet of code in your `init.lua` file: -``` -local hyper = {"ctrl", "alt", "cmd"} - +``` lua hs.loadSpoon("MiroWindowsManager") - -hs.window.animationDuration = 0.3 +local hyper = {"ctrl", "alt", "cmd"} spoon.MiroWindowsManager:bindHotkeys({ - up = {hyper, "up"}, - right = {hyper, "right"}, - down = {hyper, "down"}, - left = {hyper, "left"}, - fullscreen = {hyper, "f"} + up = {hyper, "up"}, + down = {hyper, "down"}, + left = {hyper, "left"}, + right = {hyper, "right"}, + fullscreen = {hyper, "f"}, + center = {hyper, "c"}, + move = {hyper, "v"}, + resize = {hyper, "d" } }) +hs.window.animationDuration = 0.3 ``` ## Shortcuts @@ -33,7 +34,7 @@ In the snippet above configure Miro'w Windows Manager in the following way: ### Hyper key - The hyper key is defined as `ctrl` + `alt` + `cmd`. This means that each shortcut will start by pressing these three keys. If you consider this too verbose for your personal keyboard interactions, you can also change it, for example replacing it with an unused key (e.g. caps lock key) with [Karabiner](https://pqrs.org/osx/karabiner/) and [Seil](https://pqrs.org/osx/karabiner/seil.html.en) to act as hyper key. + The hyper key is defined as `ctrl` + `alt` + `cmd`. This means that each shortcut will start by pressing these three keys. If you consider this too verbose for your personal keyboard interactions, you can also change it, for example replacing it with an unused modifier key (e.g. caps lock key with [Karabiner Elements](https://pqrs.org/osx/karabiner/)). ### Move in halves @@ -70,6 +71,18 @@ Note that in case the window is resized to be a half of the screen, you can also As the other shortcuts, `hyper` + `f` can be pressed multiple times to obtain a centered window of three fourth and one half of height and width. This behaviour can be customized. +### Center window + + - `hyper` + `c`: center window without changing its size + +### Move Mode + + - Press and hold `hyper` + `v`, then tap the move keys to move the window in increments of 5% of the screen size + +### Resize Mode + + - Press and hold `hyper` + `d`, then tap the move keys to resize the window in increments of 5% of the screen size. + ## Animations The snippet above configures the animation to last `0.3s` with `hs.window.animationDuration = 0.3`. To remove the animations completely change this value to `0`. From 6a29b5463a4c616e779c70e0b314509deeb09464 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Sun, 11 Nov 2018 22:15:34 +1100 Subject: [PATCH 06/41] bugfix: don't let our fullscreen originalPositionStore get garbagecollected. --- MiroWindowsManager.spoon/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index a9a64aa..00675d5 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -120,7 +120,7 @@ for _,move in ipairs(obj._directions) do end obj._originalPositionStore = { fullscreen = {} } -setmetatable(obj._originalPositionStore, {__mode = 'kv'}) -- weak table, so it doesn't become a memory hog +setmetatable(obj._originalPositionStore.fullscreen, {__mode = 'kv'}) -- weak table, so it doesn't become a memory hog obj._lastSeq = {} obj._lastFullscreenSeq = nil From 45723ed1e7289ee13a53f25edfa78a596578d56e Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Wed, 14 Nov 2018 07:32:05 +1100 Subject: [PATCH 07/41] bugfix: sometimes double tapping opposing directions to grow to full width/height didn't quite register; first entering the modal, then performing the action seems to fix this. --- MiroWindowsManager.spoon/init.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 00675d5..39fd6a0 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -525,8 +525,8 @@ function obj:bindHotkeys(mapping) mapping[direction][1], mapping[direction][2], function() - self:go(direction) growFullyModals[direction]:enter() + self:go(direction) end, function() growFullyModals[direction]:exit() @@ -561,8 +561,8 @@ function obj:bindHotkeys(mapping) function modal.exited(_) logger.i("Move Mode off") end hs.fnutils.each(self._movingKeys, function(move) modal:bind(mapping.move[1], self._movingKeys[move], - function() self:move(move); growFullyModals[move]:enter() end, - function() growFullyModals[move]:exit() end) + function() growFullyModals[move]:enter(); self:move(move) end, + function() growFullyModals[move]:exit() end) end) self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.move[1], @@ -580,8 +580,8 @@ function obj:bindHotkeys(mapping) local mapR = {}; for k,v in pairs(map) do mapR[v] = k end for move,resize in pairs(map) do modal:bind(mapping.move[1], move, - function() self:resize(resize); growFullyModals[mapR[resize]]:enter() end, - function() growFullyModals[mapR[resize]]:exit() end) + function() growFullyModals[mapR[resize]]:enter(); self:resize(resize) end, + function() growFullyModals[mapR[resize]]:exit() end) end self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.resize[1], From 99a9efe85fa7844f4f018d6dc9fc68c3268013f8 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Fri, 16 Nov 2018 09:00:25 +1100 Subject: [PATCH 08/41] bugfix: support non-arrow keys as moving keys --- MiroWindowsManager.spoon/init.lua | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 39fd6a0..532df05 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -496,6 +496,13 @@ obj.hotkeys = {} function obj:bindHotkeys(mapping) logger.i("Bind Hotkeys for Miro's Windows Manager") + -- movingKeys + for _,direction in ipairs(self._directions) do + -- save the keys that the user decided to be for directions, + -- generally the arrows keys, but it could be also WASD. + self._movingKeys[direction] = mapping[direction][2] + end + -- `growFully` modals local growFullyModals = {} for _,direction in ipairs(self._directions) do @@ -531,10 +538,6 @@ function obj:bindHotkeys(mapping) function() growFullyModals[direction]:exit() end) - - -- save the keys that the user decided to be for directions, - -- generally the arrows keys, but it could be also WASD. - self._movingKeys[direction] = mapping[direction][2] end end @@ -559,11 +562,11 @@ function obj:bindHotkeys(mapping) local modal = hs.hotkey.modal.new() function modal.entered(_) logger.i("Move Mode on") end function modal.exited(_) logger.i("Move Mode off") end - hs.fnutils.each(self._movingKeys, function(move) - modal:bind(mapping.move[1], self._movingKeys[move], + for move,key in pairs(self._movingKeys) do + modal:bind(mapping.move[1], key, function() growFullyModals[move]:enter(); self:move(move) end, function() growFullyModals[move]:exit() end) - end) + end self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.move[1], mapping.move[2], @@ -578,10 +581,11 @@ function obj:bindHotkeys(mapping) function modal:exited() logger.i("Resize Mode off") end local map = { left = 'thinner', right = 'wider', down = 'shorter', up = 'taller' } local mapR = {}; for k,v in pairs(map) do mapR[v] = k end - for move,resize in pairs(map) do - modal:bind(mapping.move[1], move, - function() growFullyModals[mapR[resize]]:enter(); self:resize(resize) end, - function() growFullyModals[mapR[resize]]:exit() end) + for move,key in pairs(self._movingKeys) do + hs.printf("move: %s, map[m]: %s, mapR[m]: %s", move, map[move], mapR[move]) + modal:bind(mapping.move[1], key, + function() growFullyModals[move]:enter(); self:resize(map[move]) end, + function() growFullyModals[move]:exit() end) end self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.resize[1], From c6ab1bfd56d39827a8bc33c4ebd5a91b0f5970ab Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Fri, 16 Nov 2018 09:05:46 +1100 Subject: [PATCH 09/41] bugfix: support holding move keys to keep moving or growing --- MiroWindowsManager.spoon/init.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 532df05..acf2746 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -565,7 +565,8 @@ function obj:bindHotkeys(mapping) for move,key in pairs(self._movingKeys) do modal:bind(mapping.move[1], key, function() growFullyModals[move]:enter(); self:move(move) end, - function() growFullyModals[move]:exit() end) + function() growFullyModals[move]:exit() end, + function() self:move(move) end) end self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.move[1], @@ -585,7 +586,8 @@ function obj:bindHotkeys(mapping) hs.printf("move: %s, map[m]: %s, mapR[m]: %s", move, map[move], mapR[move]) modal:bind(mapping.move[1], key, function() growFullyModals[move]:enter(); self:resize(map[move]) end, - function() growFullyModals[move]:exit() end) + function() growFullyModals[move]:exit() end, + function() self:resize(map[move]) end) end self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.resize[1], From 8d69f9cb1ea243033e9d504fcd6997b12f855f28 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Fri, 16 Nov 2018 09:13:21 +1100 Subject: [PATCH 10/41] minor: cleanup --- MiroWindowsManager.spoon/init.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index acf2746..2e9a688 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -581,9 +581,7 @@ function obj:bindHotkeys(mapping) function modal:entered() logger.i("Resize Mode on") end function modal:exited() logger.i("Resize Mode off") end local map = { left = 'thinner', right = 'wider', down = 'shorter', up = 'taller' } - local mapR = {}; for k,v in pairs(map) do mapR[v] = k end for move,key in pairs(self._movingKeys) do - hs.printf("move: %s, map[m]: %s, mapR[m]: %s", move, map[move], mapR[move]) modal:bind(mapping.move[1], key, function() growFullyModals[move]:enter(); self:resize(map[move]) end, function() growFullyModals[move]:exit() end, From 401d875bdf534de962c5f3e1d0267ef42d48f3ab Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Tue, 25 Dec 2018 20:18:19 -0800 Subject: [PATCH 11/41] HS docs are broken and can't do word wrapping. [sad face] --- MiroWindowsManager.spoon/init.lua | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 2e9a688..7309b02 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -17,8 +17,7 @@ --- === MiroWindowsManager === --- ---- With this Spoon you will be able to move the window in halves and in corners using your keyboard and mainly using ---- arrows. You would also be able to resize them by thirds, quarters, or halves. +--- With this Spoon you will be able to move the window in halves and in corners using your keyboard and mainly using arrows. You would also be able to resize them by thirds, quarters, or halves. --- Official homepage for more info and documentation: --- [https://github.com/miromannino/miro-windows-manager](https://github.com/miromannino/miro-windows-manager) --- @@ -68,16 +67,14 @@ obj.sizes = {2, 3, 3/2} obj.fullScreenSizes = {1, 2, 'c'} -- Comment: Lots of work here to save users a little work. Previous versions required users to call --- MiroWindowsManager:start() every time they changed GRID. The metatable work here watches for those changes and does --- the work :start() would have done. +-- MiroWindowsManager:start() every time they changed GRID. The metatable work here watches for those changes and does the work :start() would have done. package.path = package.path..";Spoons/".. ... ..".spoon/?.lua" require('extend_GRID').extend(obj, logger) --- MiroWindowsManager.GRID --- Variable --- The screen's grid size. ---- Make sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give ---- integers. +--- Make sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give integers. obj.GRID = { w = 24, h = 24, margins = hs.geometry.point(0,0) } function obj.GRID.cell() return hs.geometry(obj.GRID.margins, hs.geometry.size(obj.GRID.w, obj.GRID.h)) @@ -86,14 +83,13 @@ end --- MiroWindowsManager.pushToNextScreen --- Variable ---- Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge. +--- Boolean value to decide whether or not to move the window on the next screen if the window is moved the screen edge. obj.pushToNextScreen = false --- MiroWindowsManager.resizeRate --- Variable ---- Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made ---- taller/wider (or shorter/thinner) in 5% increments. +--- Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made taller/wider (or shorter/thinner) in 5% increments. obj.resizeRate = 1.05 -- ## Internal @@ -467,11 +463,8 @@ obj.hotkeys = {} --- * down: for the down action (usually `{hyper, "down"}`) --- * fullscreen: for the full-screen action (e.g. `{hyper, "f"}`) --- * center: for the center action (e.g. `{hyper, "c"}`) ---- * move: for the move action (e.g. `{hyper, "v"}`). The move action is active as soon as the hotkey is pressed. ---- While active the left, right, up or down keys can be used (these are configured by the actions above). ---- * resize: for the resize action (e.g. `{hyper, "d"}`). The resize action is active as soon as the hotkey is ---- pressed. While active the left, right, up or down keys can be used (these are configured by the actions ---- above). +--- * move: for the move action (e.g. `{hyper, "v"}`). The move action is active as soon as the hotkey is pressed. While active the left, right, up or down keys can be used (these are configured by the actions above). +--- * resize: for the resize action (e.g. `{hyper, "d"}`). The resize action is active as soon as the hotkey is pressed. While active the left, right, up or down keys can be used (these are configured by the actions above). --- --- A configuration example: --- ``` lua From 110436c7e29b399d2f133372b5a0456322ad7ea6 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Tue, 25 Dec 2018 20:19:45 -0800 Subject: [PATCH 12/41] Sticky sides (optionally stick to a bound side when shrinking windows). --- MiroWindowsManager.spoon/init.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 7309b02..c99670f 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -87,6 +87,12 @@ end obj.pushToNextScreen = false +--- MiroWindowsManager.stickySides +--- Variable +--- Boolean value to decide whether or not to stick the window to the edge of the screen if shrinking it would detatch it from the screen edge. +obj.stickySides = false + + --- MiroWindowsManager.resizeRate --- Variable --- Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made taller/wider (or shorter/thinner) in 5% increments. @@ -201,6 +207,18 @@ function obj:resize(growth) fr = fr:intersect(frontmostScreen():frame()) -- avoid sizing out of bounds + if self.stickySides then + if growth == 'shorter' and self:currentlyBound('up') then + fr.y = 0 + elseif growth == 'shorter' and self:currentlyBound('down') then + fr.y = fr.y + growthDiff / 2 + elseif growth == 'thinner' and self:currentlyBound('left') then + fr.x = 0 + elseif growth == 'thinner' and self:currentlyBound('right') then + fr.x = fr.x + growthDiff / 2 + end + end + w:setFrame(fr) return self end From e8ba98024b4c882dff28a3e00da5542730a41a20 Mon Sep 17 00:00:00 2001 From: Miro Mannino Date: Sun, 27 Jan 2019 11:20:48 +0400 Subject: [PATCH 13/41] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index bc9343d..49494ea 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,9 @@ Here comments from the users, just as reviews. > > — Gaurav +> the only issue I have with miro-windows-manager is the fact that I didn't discover it sooner. just getting into HammerSpoon and love this 🥄 ... so handy, nice work @miromannino ! +> — [rxng](https://github.com/miromannino/miro-windows-manager/issues/13) + ## Articles A suggested tutorial on Mic Sumner: https://www.micsumner.com/how-to-organise-window-viewing-areas-in-mac-os/ From 9f3ee0785b5e8963c2765f1e29a310937b20a09c Mon Sep 17 00:00:00 2001 From: Miro Mannino Date: Sun, 27 Jan 2019 11:21:44 +0400 Subject: [PATCH 14/41] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 49494ea..34fd52d 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,8 @@ Here comments from the users, just as reviews. > — Gaurav > the only issue I have with miro-windows-manager is the fact that I didn't discover it sooner. just getting into HammerSpoon and love this 🥄 ... so handy, nice work @miromannino ! -> — [rxng](https://github.com/miromannino/miro-windows-manager/issues/13) +> +> — [zanuka](https://github.com/miromannino/miro-windows-manager/issues/13) ## Articles From c5e86be485316ea538ced257014d861615c1f3e9 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Sat, 26 May 2018 20:30:46 +1000 Subject: [PATCH 15/41] Add `move` and `grow` methods to move and grow (or shrink) windows Major changes: - add `move` and `grow` methods to move and grow (or shrink) windows - fullscreen starts by centering the window, then proceeds through the sequence Minor changes: - update hs.grid when obj.GRID changes, making obj:init() unnecessary - forget obj._pressed state after a second (with move & grow changes I found myself wanting to shift modifier keys without releasing them, this means I can just pause) - refactor code to make it easier (for me) to understand - tweak the documentation a little --- MiroWindowsManager.spoon/docs.json | 540 ++++++++++++++++++++---- MiroWindowsManager.spoon/init.lua | 646 ++++++++++++++++++++++------- 2 files changed, 947 insertions(+), 239 deletions(-) diff --git a/MiroWindowsManager.spoon/docs.json b/MiroWindowsManager.spoon/docs.json index 3fefd7b..1a970f8 100644 --- a/MiroWindowsManager.spoon/docs.json +++ b/MiroWindowsManager.spoon/docs.json @@ -11,16 +11,17 @@ ], "Variable" : [ { - "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size.\nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size", - "name" : "sizes", + "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total\nscreen's size.", "stripped_doc" : [ - "The sizes that the window can have. ", - "The sizes are expressed as dividend of the entire screen's size.", - "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size" + "The sizes that the window can have. ", + "The sizes are expressed as dividend of the entire screen's size. ", + "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total", + "screen's size." ], "parameters" : [ ], + "def" : "MiroWindowsManager.sizes", "notes" : [ ], @@ -29,20 +30,21 @@ "returns" : [ ], - "def" : "MiroWindowsManager.sizes", - "desc" : "The sizes that the window can have." + "desc" : "The sizes that the window can have.", + "name" : "sizes" }, { - "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size.\nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4 and 1\/2 of the total screen's size", - "name" : "fullScreenSizes", + "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4\nand 1\/2 of the total screen's size", "stripped_doc" : [ - "The sizes that the window can have in full-screen. ", - "The sizes are expressed as dividend of the entire screen's size.", - "For example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4 and 1\/2 of the total screen's size" + "The sizes that the window can have in full-screen. ", + "The sizes are expressed as dividend of the entire screen's size. ", + "For example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4", + "and 1\/2 of the total screen's size" ], "parameters" : [ ], + "def" : "MiroWindowsManager.fullScreenSizes", "notes" : [ ], @@ -51,19 +53,18 @@ "returns" : [ ], - "def" : "MiroWindowsManager.fullScreenSizes", - "desc" : "The sizes that the window can have in full-screen." + "desc" : "The sizes that the window can have in full-screen.", + "name" : "fullScreenSizes" }, { - "doc" : "The screen's size using `hs.grid.setGrid()`\nThis parameter is used at the spoon's `:init()`", - "name" : "GRID", + "doc" : "The screen's grid size.", "stripped_doc" : [ - "The screen's size using `hs.grid.setGrid()`", - "This parameter is used at the spoon's `:init()`" + "The screen's grid size." ], "parameters" : [ ], + "def" : "MiroWindowsManager.GRID", "notes" : [ ], @@ -72,49 +73,170 @@ "returns" : [ ], - "def" : "MiroWindowsManager.GRID", - "desc" : "The screen's size using `hs.grid.setGrid()`" + "desc" : "The screen's grid size.", + "name" : "GRID" + }, + { + "doc" : "If `move`d past the screen edge, jump to next screen?", + "stripped_doc" : [ + "If `move`d past the screen edge, jump to next screen?" + ], + "parameters" : [ + + ], + "def" : "MiroWindowsManager.pushToNextScreen", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.pushToNextScreen", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "If `move`d past the screen edge, jump to next screen?", + "name" : "pushToNextScreen" } ], "stripped_doc" : [ ], - "desc" : "With this script you will be able to move the window in halves and in corners using your keyboard and mainly using arrows. You would also be able to resize them by thirds, quarters, or halves.", - "type" : "Module", + "desc" : "With this Spoon you will be able to move the window in halves and in", "Deprecated" : [ ], + "type" : "Module", "Constructor" : [ ], - "doc" : "With this script you will be able to move the window in halves and in corners using your keyboard and mainly using arrows. You would also be able to resize them by thirds, quarters, or halves.\n\nOfficial homepage for more info and documentation: [https:\/\/github.com\/miromannino\/miro-windows-manager](https:\/\/github.com\/miromannino\/miro-windows-manager)\n\nDownload: [https:\/\/github.com\/miromannino\/miro-windows-manager\/raw\/master\/MiroWindowsManager.spoon.zip](https:\/\/github.com\/miromannino\/miro-windows-manager\/raw\/master\/MiroWindowsManager.spoon.zip)", - "Method" : [ + "doc" : "With this Spoon you will be able to move the window in halves and in\ncorners using your keyboard and mainly using arrows. You would also be able\nto resize them by thirds, quarters, or halves. \nOfficial homepage for more info and documentation:\n[https:\/\/github.com\/miromannino\/miro-windows-manager](https:\/\/github.com\/miromannino\/miro-windows-manager)\n\nNOTE: This Spoon sets `hs.grid` globals with `hs.grid.setGrid()`,\n`hs.grid.MARGINX`, and `hs.grid.MARGINY`. Changing MiroWindowsManager.GRID\nwill change these globals.\n\nDownload:\nhttps:\/\/github.com\/miromannino\/miro-windows-manager\/raw\/master\/MiroWindowsManager.spoon.zip", + "Field" : [ + + ], + "Command" : [ + + ], + "items" : [ + { + "doc" : "The screen's grid size.", + "stripped_doc" : [ + "The screen's grid size." + ], + "parameters" : [ + + ], + "def" : "MiroWindowsManager.GRID", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.GRID", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "The screen's grid size.", + "name" : "GRID" + }, + { + "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4\nand 1\/2 of the total screen's size", + "stripped_doc" : [ + "The sizes that the window can have in full-screen. ", + "The sizes are expressed as dividend of the entire screen's size. ", + "For example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4", + "and 1\/2 of the total screen's size" + ], + "parameters" : [ + + ], + "def" : "MiroWindowsManager.fullScreenSizes", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.fullScreenSizes", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "The sizes that the window can have in full-screen.", + "name" : "fullScreenSizes" + }, + { + "doc" : "If `move`d past the screen edge, jump to next screen?", + "stripped_doc" : [ + "If `move`d past the screen edge, jump to next screen?" + ], + "parameters" : [ + + ], + "def" : "MiroWindowsManager.pushToNextScreen", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.pushToNextScreen", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "If `move`d past the screen edge, jump to next screen?", + "name" : "pushToNextScreen" + }, + { + "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total\nscreen's size.", + "stripped_doc" : [ + "The sizes that the window can have. ", + "The sizes are expressed as dividend of the entire screen's size. ", + "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total", + "screen's size." + ], + "parameters" : [ + + ], + "def" : "MiroWindowsManager.sizes", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.sizes", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "The sizes that the window can have.", + "name" : "sizes" + }, { - "doc" : "Binds hotkeys for Miro's Windows Manager\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * up: for the up action (usually {hyper, \"up\"})\n * right: for the right action (usually {hyper, \"right\"})\n * down: for the down action (usually {hyper, \"down\"})\n * left: for the left action (usually {hyper, \"left\"})\n * fullscreen: for the full-screen action (e.g. {hyper, \"f\"})\n\nA configuration example can be:\n```\nlocal hyper = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {hyper, \"up\"},\n right = {hyper, \"right\"},\n down = {hyper, \"down\"},\n left = {hyper, \"left\"},\n fullscreen = {hyper, \"f\"}\n})\n```", - "name" : "bindHotkeys", + "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n\nA configuration example can be:\n``` lua\nlocal mods = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {mods, \"up\"},\n down = {mods, \"down\"},\n left = {mods, \"left\"},\n right = {mods, \"right\"},\n fullscreen = {mods, \"f\"},\n moveUp = {{'⌃','⌥'}, \"up\"},\n moveDown = {{'⌃','⌥'}, \"down\"},\n moveLeft = {{'⌃','⌥'}, \"left\"},\n moveRight = {{'⌃','⌥'}, \"right\"},\n taller = {{'⌃','⌥','⇧'}, \"down\"},\n shorter = {{'⌃','⌥','⇧'}, \"up\"},\n wider = {{'⌃','⌥','⇧'}, \"right\"},\n thinner = {{'⌃','⌥','⇧'}, \"left\"},\n})\n```", "stripped_doc" : [ - "Binds hotkeys for Miro's Windows Manager" + "Binds hotkeys for Miro's Windows Manager", + "" ], "parameters" : [ " * mapping - A table containing hotkey details for the following items:", + " * left: for the left action (usually `{hyper, \"left\"}`)", + " * right: for the right action (usually `{hyper, \"right\"}`)", " * up: for the up action (usually {hyper, \"up\"})", - " * right: for the right action (usually {hyper, \"right\"})", - " * down: for the down action (usually {hyper, \"down\"})", - " * left: for the left action (usually {hyper, \"left\"})", - " * fullscreen: for the full-screen action (e.g. {hyper, \"f\"})", + " * down: for the down action (usually `{hyper, \"down\"}`)", + " * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)", "", "A configuration example can be:", - "```", - "local hyper = {\"ctrl\", \"alt\", \"cmd\"}", + "``` lua", + "local mods = {\"ctrl\", \"alt\", \"cmd\"}", "spoon.MiroWindowsManager:bindHotkeys({", - " up = {hyper, \"up\"},", - " right = {hyper, \"right\"},", - " down = {hyper, \"down\"},", - " left = {hyper, \"left\"},", - " fullscreen = {hyper, \"f\"}", + " up = {mods, \"up\"},", + " down = {mods, \"down\"},", + " left = {mods, \"left\"},", + " right = {mods, \"right\"},", + " fullscreen = {mods, \"f\"},", + " moveUp = {{'⌃','⌥'}, \"up\"},", + " moveDown = {{'⌃','⌥'}, \"down\"},", + " moveLeft = {{'⌃','⌥'}, \"left\"},", + " moveRight = {{'⌃','⌥'}, \"right\"},", + " taller = {{'⌃','⌥','⇧'}, \"down\"},", + " shorter = {{'⌃','⌥','⇧'}, \"up\"},", + " wider = {{'⌃','⌥','⇧'}, \"right\"},", + " thinner = {{'⌃','⌥','⇧'}, \"left\"},", "})", "```" ], + "def" : "MiroWindowsManager:bindHotkeys()", "notes" : [ ], @@ -123,105 +245,324 @@ "returns" : [ ], - "def" : "MiroWindowsManager:bindHotkeys()", - "desc" : "Binds hotkeys for Miro's Windows Manager" - } - ], - "Command" : [ + "desc" : "Binds hotkeys for Miro's Windows Manager", + "name" : "bindHotkeys" + }, + { + "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \nAlso: \n * MiroWindowsManager:goUp()\n * MiroWindowsManager:goDown()\n * MiroWindowsManager:goLeft()\n * MiroWindowsManager:goRight()\n\nParameters:\n * move - up, down, left, right\n\nReturns:\n * The MiroWindowsManager object", + "stripped_doc" : [ + "Move to screen edge, or cycle to next horizontal or vertical size if already there. ", + "Tap both directions to go full width \/ height. ", + "Also: ", + " * MiroWindowsManager:goUp()", + " * MiroWindowsManager:goDown()", + " * MiroWindowsManager:goLeft()", + " * MiroWindowsManager:goRight()", + "" + ], + "parameters" : [ + " * move - up, down, left, right", + "" + ], + "def" : "MiroWindowsManager:go(move)", + "notes" : [ - ], - "items" : [ + ], + "signature" : "MiroWindowsManager:go(move)", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "desc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there.", + "name" : "go" + }, + { + "doc" : "Fullscreen, or cycle to next fullscreen option\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", + "stripped_doc" : [ + "Fullscreen, or cycle to next fullscreen option", + "" + ], + "parameters" : [ + " * None.", + "" + ], + "def" : "MiroWindowsManager:goFullscreen()", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:goFullscreen()", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "desc" : "Fullscreen, or cycle to next fullscreen option", + "name" : "goFullscreen" + }, { - "doc" : "The screen's size using `hs.grid.setGrid()`\nThis parameter is used at the spoon's `:init()`", - "name" : "GRID", + "doc" : "Grow the frontmost window taller, shorter, wider, thinner.\nAlso:\n * MiroWindowsManager:taller()\n * MiroWindowsManager:shorter()\n * MiroWindowsManager:wider()\n * MiroWindowsManager:thinner()\n\nParameters:\n * growth - taller, shorter, wider, thinner\n\nReturns:\n * The MiroWindowsManager object", "stripped_doc" : [ - "The screen's size using `hs.grid.setGrid()`", - "This parameter is used at the spoon's `:init()`" + "Grow the frontmost window taller, shorter, wider, thinner.", + "Also:", + " * MiroWindowsManager:taller()", + " * MiroWindowsManager:shorter()", + " * MiroWindowsManager:wider()", + " * MiroWindowsManager:thinner()", + "" ], "parameters" : [ + " * growth - taller, shorter, wider, thinner", + "" + ], + "def" : "MiroWindowsManager:grow(growth)", + "notes" : [ ], + "signature" : "MiroWindowsManager:grow(growth)", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "desc" : "Grow the frontmost window taller, shorter, wider, thinner.", + "name" : "grow" + }, + { + "doc" : "Grow the frontmost window to full width \/ height taller, wider. \nAlso:\n * MiroWindowsManager:tallest()\n * MiroWindowsManager:widest()\n\nParameters:\n * growth - taller, wider\n\nReturns:\n * The MiroWindowsManager object", + "stripped_doc" : [ + "Grow the frontmost window to full width \/ height taller, wider. ", + "Also:", + " * MiroWindowsManager:tallest()", + " * MiroWindowsManager:widest()", + "" + ], + "parameters" : [ + " * growth - taller, wider", + "" + ], + "def" : "MiroWindowsManager:growFully(growth)", "notes" : [ ], - "signature" : "MiroWindowsManager.GRID", - "type" : "Variable", + "signature" : "MiroWindowsManager:growFully(growth)", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "desc" : "Grow the frontmost window to full width \/ height taller, wider.", + "name" : "growFully" + }, + { + "doc" : "LEGACY: Calling this is not required.", + "stripped_doc" : [ + "LEGACY: Calling this is not required." + ], + "parameters" : [ + + ], + "def" : "MiroWindowsManager:init()", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:init()", + "type" : "Method", "returns" : [ ], - "def" : "MiroWindowsManager.GRID", - "desc" : "The screen's size using `hs.grid.setGrid()`" + "desc" : "LEGACY: Calling this is not required.", + "name" : "init" }, { - "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size.\nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4 and 1\/2 of the total screen's size", - "name" : "fullScreenSizes", + "doc" : "Move the frontmost window up, down, left, right. \nAlso:\n * MiroWindowsManager:moveUp()\n * MiroWindowsManager:moveDown()\n * MiroWindowsManager:moveLeft()\n * MiroWindowsManager:moveRight()\n\nParameters:\n * side - up, down, left, right\n\nReturns:\n * The MiroWindowsManager object", "stripped_doc" : [ - "The sizes that the window can have in full-screen. ", - "The sizes are expressed as dividend of the entire screen's size.", - "For example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4 and 1\/2 of the total screen's size" + "Move the frontmost window up, down, left, right. ", + "Also:", + " * MiroWindowsManager:moveUp()", + " * MiroWindowsManager:moveDown()", + " * MiroWindowsManager:moveLeft()", + " * MiroWindowsManager:moveRight()", + "" ], "parameters" : [ + " * side - up, down, left, right", + "" + ], + "def" : "MiroWindowsManager:move(side)", + "notes" : [ ], + "signature" : "MiroWindowsManager:move(side)", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "desc" : "Move the frontmost window up, down, left, right.", + "name" : "move" + } + ], + "Method" : [ + { + "doc" : "Move the frontmost window up, down, left, right. \nAlso:\n * MiroWindowsManager:moveUp()\n * MiroWindowsManager:moveDown()\n * MiroWindowsManager:moveLeft()\n * MiroWindowsManager:moveRight()\n\nParameters:\n * side - up, down, left, right\n\nReturns:\n * The MiroWindowsManager object", + "stripped_doc" : [ + "Move the frontmost window up, down, left, right. ", + "Also:", + " * MiroWindowsManager:moveUp()", + " * MiroWindowsManager:moveDown()", + " * MiroWindowsManager:moveLeft()", + " * MiroWindowsManager:moveRight()", + "" + ], + "parameters" : [ + " * side - up, down, left, right", + "" + ], + "def" : "MiroWindowsManager:move(side)", "notes" : [ ], - "signature" : "MiroWindowsManager.fullScreenSizes", - "type" : "Variable", + "signature" : "MiroWindowsManager:move(side)", + "type" : "Method", "returns" : [ + " * The MiroWindowsManager object" + ], + "desc" : "Move the frontmost window up, down, left, right.", + "name" : "move" + }, + { + "doc" : "Grow the frontmost window taller, shorter, wider, thinner.\nAlso:\n * MiroWindowsManager:taller()\n * MiroWindowsManager:shorter()\n * MiroWindowsManager:wider()\n * MiroWindowsManager:thinner()\n\nParameters:\n * growth - taller, shorter, wider, thinner\n\nReturns:\n * The MiroWindowsManager object", + "stripped_doc" : [ + "Grow the frontmost window taller, shorter, wider, thinner.", + "Also:", + " * MiroWindowsManager:taller()", + " * MiroWindowsManager:shorter()", + " * MiroWindowsManager:wider()", + " * MiroWindowsManager:thinner()", + "" + ], + "parameters" : [ + " * growth - taller, shorter, wider, thinner", + "" + ], + "def" : "MiroWindowsManager:grow(growth)", + "notes" : [ ], - "def" : "MiroWindowsManager.fullScreenSizes", - "desc" : "The sizes that the window can have in full-screen." + "signature" : "MiroWindowsManager:grow(growth)", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "desc" : "Grow the frontmost window taller, shorter, wider, thinner.", + "name" : "grow" }, { - "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size.\nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size", - "name" : "sizes", + "doc" : "Grow the frontmost window to full width \/ height taller, wider. \nAlso:\n * MiroWindowsManager:tallest()\n * MiroWindowsManager:widest()\n\nParameters:\n * growth - taller, wider\n\nReturns:\n * The MiroWindowsManager object", "stripped_doc" : [ - "The sizes that the window can have. ", - "The sizes are expressed as dividend of the entire screen's size.", - "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size" + "Grow the frontmost window to full width \/ height taller, wider. ", + "Also:", + " * MiroWindowsManager:tallest()", + " * MiroWindowsManager:widest()", + "" ], "parameters" : [ + " * growth - taller, wider", + "" + ], + "def" : "MiroWindowsManager:growFully(growth)", + "notes" : [ ], + "signature" : "MiroWindowsManager:growFully(growth)", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "desc" : "Grow the frontmost window to full width \/ height taller, wider.", + "name" : "growFully" + }, + { + "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \nAlso: \n * MiroWindowsManager:goUp()\n * MiroWindowsManager:goDown()\n * MiroWindowsManager:goLeft()\n * MiroWindowsManager:goRight()\n\nParameters:\n * move - up, down, left, right\n\nReturns:\n * The MiroWindowsManager object", + "stripped_doc" : [ + "Move to screen edge, or cycle to next horizontal or vertical size if already there. ", + "Tap both directions to go full width \/ height. ", + "Also: ", + " * MiroWindowsManager:goUp()", + " * MiroWindowsManager:goDown()", + " * MiroWindowsManager:goLeft()", + " * MiroWindowsManager:goRight()", + "" + ], + "parameters" : [ + " * move - up, down, left, right", + "" + ], + "def" : "MiroWindowsManager:go(move)", "notes" : [ ], - "signature" : "MiroWindowsManager.sizes", - "type" : "Variable", + "signature" : "MiroWindowsManager:go(move)", + "type" : "Method", "returns" : [ + " * The MiroWindowsManager object" + ], + "desc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there.", + "name" : "go" + }, + { + "doc" : "Fullscreen, or cycle to next fullscreen option\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", + "stripped_doc" : [ + "Fullscreen, or cycle to next fullscreen option", + "" + ], + "parameters" : [ + " * None.", + "" + ], + "def" : "MiroWindowsManager:goFullscreen()", + "notes" : [ ], - "def" : "MiroWindowsManager.sizes", - "desc" : "The sizes that the window can have." + "signature" : "MiroWindowsManager:goFullscreen()", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "desc" : "Fullscreen, or cycle to next fullscreen option", + "name" : "goFullscreen" }, { - "doc" : "Binds hotkeys for Miro's Windows Manager\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * up: for the up action (usually {hyper, \"up\"})\n * right: for the right action (usually {hyper, \"right\"})\n * down: for the down action (usually {hyper, \"down\"})\n * left: for the left action (usually {hyper, \"left\"})\n * fullscreen: for the full-screen action (e.g. {hyper, \"f\"})\n\nA configuration example can be:\n```\nlocal hyper = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {hyper, \"up\"},\n right = {hyper, \"right\"},\n down = {hyper, \"down\"},\n left = {hyper, \"left\"},\n fullscreen = {hyper, \"f\"}\n})\n```", - "name" : "bindHotkeys", + "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n\nA configuration example can be:\n``` lua\nlocal mods = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {mods, \"up\"},\n down = {mods, \"down\"},\n left = {mods, \"left\"},\n right = {mods, \"right\"},\n fullscreen = {mods, \"f\"},\n moveUp = {{'⌃','⌥'}, \"up\"},\n moveDown = {{'⌃','⌥'}, \"down\"},\n moveLeft = {{'⌃','⌥'}, \"left\"},\n moveRight = {{'⌃','⌥'}, \"right\"},\n taller = {{'⌃','⌥','⇧'}, \"down\"},\n shorter = {{'⌃','⌥','⇧'}, \"up\"},\n wider = {{'⌃','⌥','⇧'}, \"right\"},\n thinner = {{'⌃','⌥','⇧'}, \"left\"},\n})\n```", "stripped_doc" : [ - "Binds hotkeys for Miro's Windows Manager" + "Binds hotkeys for Miro's Windows Manager", + "" ], "parameters" : [ " * mapping - A table containing hotkey details for the following items:", + " * left: for the left action (usually `{hyper, \"left\"}`)", + " * right: for the right action (usually `{hyper, \"right\"}`)", " * up: for the up action (usually {hyper, \"up\"})", - " * right: for the right action (usually {hyper, \"right\"})", - " * down: for the down action (usually {hyper, \"down\"})", - " * left: for the left action (usually {hyper, \"left\"})", - " * fullscreen: for the full-screen action (e.g. {hyper, \"f\"})", + " * down: for the down action (usually `{hyper, \"down\"}`)", + " * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)", "", "A configuration example can be:", - "```", - "local hyper = {\"ctrl\", \"alt\", \"cmd\"}", + "``` lua", + "local mods = {\"ctrl\", \"alt\", \"cmd\"}", "spoon.MiroWindowsManager:bindHotkeys({", - " up = {hyper, \"up\"},", - " right = {hyper, \"right\"},", - " down = {hyper, \"down\"},", - " left = {hyper, \"left\"},", - " fullscreen = {hyper, \"f\"}", + " up = {mods, \"up\"},", + " down = {mods, \"down\"},", + " left = {mods, \"left\"},", + " right = {mods, \"right\"},", + " fullscreen = {mods, \"f\"},", + " moveUp = {{'⌃','⌥'}, \"up\"},", + " moveDown = {{'⌃','⌥'}, \"down\"},", + " moveLeft = {{'⌃','⌥'}, \"left\"},", + " moveRight = {{'⌃','⌥'}, \"right\"},", + " taller = {{'⌃','⌥','⇧'}, \"down\"},", + " shorter = {{'⌃','⌥','⇧'}, \"up\"},", + " wider = {{'⌃','⌥','⇧'}, \"right\"},", + " thinner = {{'⌃','⌥','⇧'}, \"left\"},", "})", "```" ], + "def" : "MiroWindowsManager:bindHotkeys()", "notes" : [ ], @@ -230,13 +571,30 @@ "returns" : [ ], - "def" : "MiroWindowsManager:bindHotkeys()", - "desc" : "Binds hotkeys for Miro's Windows Manager" - } - ], - "Field" : [ + "desc" : "Binds hotkeys for Miro's Windows Manager", + "name" : "bindHotkeys" + }, + { + "doc" : "LEGACY: Calling this is not required.", + "stripped_doc" : [ + "LEGACY: Calling this is not required." + ], + "parameters" : [ + + ], + "def" : "MiroWindowsManager:init()", + "notes" : [ + ], + "signature" : "MiroWindowsManager:init()", + "type" : "Method", + "returns" : [ + + ], + "desc" : "LEGACY: Calling this is not required.", + "name" : "init" + } ], "name" : "MiroWindowsManager" } -] \ No newline at end of file +] diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 12712c2..0ecbf14 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -17,13 +17,23 @@ --- === MiroWindowsManager === --- ---- With this script you will be able to move the window in halves and in corners using your keyboard and mainly using arrows. You would also be able to resize them by thirds, quarters, or halves. +--- With this Spoon you will be able to move the window in halves and in +--- corners using your keyboard and mainly using arrows. You would also be able +--- to resize them by thirds, quarters, or halves. +--- Official homepage for more info and documentation: +--- [https://github.com/miromannino/miro-windows-manager](https://github.com/miromannino/miro-windows-manager) --- ---- Official homepage for more info and documentation: [https://github.com/miromannino/miro-windows-manager](https://github.com/miromannino/miro-windows-manager) +--- NOTE: This Spoon sets `hs.grid` globals with `hs.grid.setGrid()`, +--- `hs.grid.MARGINX`, and `hs.grid.MARGINY`. Changing MiroWindowsManager.GRID +--- will change these globals. --- ---- Download: [https://github.com/miromannino/miro-windows-manager/raw/master/MiroWindowsManager.spoon.zip](https://github.com/miromannino/miro-windows-manager/raw/master/MiroWindowsManager.spoon.zip) +--- Download: +--- https://github.com/miromannino/miro-windows-manager/raw/master/MiroWindowsManager.spoon.zip --- +-- ## TODO +-- different sizes lists for specific apps + local obj={} obj.__index = obj @@ -34,203 +44,543 @@ obj.author = "Miro Mannino " obj.homepage = "https://github.com/miromannino/miro-windows-management" obj.license = "MIT - https://opensource.org/licenses/MIT" +obj._logger = hs.logger.new(obj.name) +local logger = obj._logger +logger.i("Loading ".. obj.name) + + +-- ### Utilities +function string.titleCase(str) + return (str:gsub('^%l', string.upper)) +end +local function expect_argument_to_be_in_table(argument, to_be_in) + assert( + hs.fnutils.contains(to_be_in, argument), + 'Expected "'.. hs.inspect(argument) ..'" to be one of '.. hs.inspect(to_be_in) + ) +end +local function expect_argument_to_be_in_table_or_nil(argument, to_be_in) + assert( + hs.fnutils.contains(to_be_in, argument) or argument == nil, + 'Expected "'.. hs.inspect(argument) ..'" to be one of '.. hs.inspect(to_be_in) + ) +end +local function expect_truthy(argument, expression) + assert( + argument, + 'Expected truthyness from '.. hs.inspect(expression) + ) +end + + +-- ## Public variables + --- MiroWindowsManager.sizes --- Variable ---- The sizes that the window can have. ---- The sizes are expressed as dividend of the entire screen's size. ---- For example `{2, 3, 3/2}` means that it can be 1/2, 1/3 and 2/3 of the total screen's size +--- The sizes that the window can have. +--- The sizes are expressed as dividend of the entire screen's size. +--- For example `{2, 3, 3/2}` means that it can be 1/2, 1/3 and 2/3 of the total +--- screen's size. obj.sizes = {2, 3, 3/2} --- MiroWindowsManager.fullScreenSizes --- Variable ---- The sizes that the window can have in full-screen. ---- The sizes are expressed as dividend of the entire screen's size. ---- For example `{1, 4/3, 2}` means that it can be 1/1 (hence full screen), 3/4 and 1/2 of the total screen's size +--- The sizes that the window can have in full-screen. +--- The sizes are expressed as dividend of the entire screen's size. +--- For example `{1, 4/3, 2}` means that it can be 1/1 (hence full screen), 3/4 +--- and 1/2 of the total screen's size obj.fullScreenSizes = {1, 4/3, 2} +-- Ensure changes to GRID update hs.grid +-- Prevent obj.GRID from being replaced +local _grid_store_yeeV5hiG = {} -- we store the real GRID here +setmetatable(obj, { + __index = function(t,k) + if k == 'GRID' then return _grid_store_yeeV5hiG else return rawget(t,k) end end, + __newindex = + function(t,k,v) + if k == 'GRID' then + assert(type(v)=='table',rawget(obj,'name')..".GRID must be a table.") + -- assign the assigned table's content to our real GRID table + for kk,vv in pairs(v) do obj.GRID[kk] = vv end + else + rawset(t,k,v) + end + end, +}) +-- Update hs.grid after relevant changes to obj.GRID +local _grid_value_store_Oocaeyim = {} -- we store the real GRID values here +local m = { + __newindex = + function(t,k,v) + rawset(_grid_value_store_Oocaeyim,k,v) + if hs.fnutils.contains({'w','h'}, k) then + hs.grid.setGrid(tostring(_grid_value_store_Oocaeyim.w or 0) .. 'x' .. + tostring(_grid_value_store_Oocaeyim.h or 0)) + elseif k == 'margins' then + hs.grid.setMargins(v) + elseif hs.fnutils.contains({'MARGINX','MARGINY'}, k) then + -- LEGACY + if k == 'MARGINX' then + obj.GRID.margins = hs.geometry(tostring(v) ..'x'.. + tostring(rawget(_grid_value_store_Oocaeyim, 'margins').h or 0)) + elseif k == 'MARGINY' then + obj.GRID.margins = + hs.geometry(tostring(rawget(_grid_value_store_Oocaeyim, 'margins').w or 0) ..'x'.. + tostring(v)) + end + end + logger.i("Updated hs.grid to ".. hs.inspect(_grid_value_store_Oocaeyim)) + end, + __index = + function(t,k) return rawget(_grid_value_store_Oocaeyim, k) end, +} +setmetatable(_grid_store_yeeV5hiG, m) + --- MiroWindowsManager.GRID --- Variable ---- The screen's size using `hs.grid.setGrid()` ---- This parameter is used at the spoon's `:init()` -obj.GRID = {w = 24, h = 24} - -obj._pressed = { - up = false, - down = false, - left = false, - right = false +--- The screen's grid size. +obj.GRID = { w = 24, h = 24, margins = hs.geometry("0x0") } + + +--- MiroWindowsManager.pushToNextScreen +--- Variable +--- If `move`d past the screen edge, jump to next screen? +obj.pushToNextScreen = false + + +-- ## Internal + +-- ### Internal configuration + +-- Window moves and their relationships +local directions = { 'up', 'down', 'left', 'right' } +local directions_rel = { + up = { opp = 'down', grow = 'taller', dim = 'h', pos = 'y', home = 0 }, + down = { opp = 'up', grow = 'taller', dim = 'h', pos = 'y', home = obj.GRID.h }, + left = { opp = 'right', grow = 'wider', dim = 'w', pos = 'x', home = 0 }, + right = { opp = 'left', grow = 'wider', dim = 'w', pos = 'x', home = obj.GRID.w }, +} +-- Window growths and their relationships +local growths = { 'taller', 'shorter', 'wider', 'thinner' } +local growths_rel = { + taller = { opp = 'shorter', dim = 'h', pos = 'y', side = 'up', sticky_bound_fix = false }, + shorter = { opp = 'taller', dim = 'h', pos = 'y', side = 'down', sticky_bound_fix = true }, + wider = { opp = 'thinner', dim = 'w', pos = 'x', side = 'left', sticky_bound_fix = false }, + thinner = { opp = 'wider', dim = 'w', pos = 'x', side = 'right', sticky_bound_fix = true }, } -function obj:_nextStep(dim, offs, cb) - if hs.window.focusedWindow() then - local axis = dim == 'w' and 'x' or 'y' - local oppDim = dim == 'w' and 'h' or 'w' - local oppAxis = dim == 'w' and 'y' or 'x' - local win = hs.window.frontmostWindow() - local id = win:id() - local screen = win:screen() - - cell = hs.grid.get(win, screen) - - local nextSize = self.sizes[1] - for i=1,#self.sizes do - if cell[dim] == self.GRID[dim] / self.sizes[i] and - (cell[axis] + (offs and cell[dim] or 0)) == (offs and self.GRID[dim] or 0) - then - nextSize = self.sizes[(i % #self.sizes) + 1] - break - end +-- ### Internal state + +obj._pressed = {} +obj._press_timers = {} +hs.fnutils.each(hs.fnutils.concat(directions, growths), function(move) + obj._pressed[move] = false + obj._press_timers[move] = hs.timer.doAfter(1, function() obj._pressed[move] = false end) +end) + +local function register_press(direction) + obj._pressed[direction] = true + obj._press_timers[direction]:start() +end +local function cancel_press(direction) + obj._pressed[direction] = false + obj._press_timers[direction]:stop() +end +local function pressedQ(direction) + return obj._pressed[direction] +end + +-- ### Internal convenience functions + +-- Accessor for functions on the frontmost window +obj._frontmost = {} +local frontmost = obj._frontmost + +-- An hs.window for the frontmost window +function frontmost.window() + return hs.window.frontmostWindow() +end +-- An hs.grid for the frontmost window +function frontmost.cell() + local win = frontmost.window() + return hs.grid.get(win, win:screen()) +end + +-- ## Public + +--- MiroWindowsManager:move(side) +--- Method +--- Move the frontmost window up, down, left, right. +--- Also: +--- * MiroWindowsManager:moveUp() +--- * MiroWindowsManager:moveDown() +--- * MiroWindowsManager:moveLeft() +--- * MiroWindowsManager:moveRight() +--- +--- Parameters: +--- * side - up, down, left, right +--- +--- Returns: +--- * The MiroWindowsManager object +function obj:move(side) + expect_argument_to_be_in_table(side, directions) + + if self:boundQ(side) and not self.pushToNextScreen then + logger.i("`self.pushToNextScreen` == false so not moving to ".. side .." screen.") + else + hs.grid['pushWindow'.. side:titleCase()](frontmost.window()) + end + return self +end +hs.fnutils.each(directions, -- up(), down, left, right + function(move) + obj['move'.. move:titleCase()] = function(self) return self:move(move) end + end ) + + +--- MiroWindowsManager:grow(growth) +--- Method +--- Grow the frontmost window taller, shorter, wider, thinner. +--- Also: +--- * MiroWindowsManager:taller() +--- * MiroWindowsManager:shorter() +--- * MiroWindowsManager:wider() +--- * MiroWindowsManager:thinner() +--- +--- Parameters: +--- * growth - taller, shorter, wider, thinner +--- +--- Returns: +--- * The MiroWindowsManager object +function obj:grow(growth) + expect_argument_to_be_in_table(growth, growths) + + register_press(growth) + if pressedQ(growths_rel[growth].opp) then + logger.i("Maximising ".. growths_rel[growth].dim .." since ".. + growths_rel[growth].opp .." still active.") + return self:growFully(growth) -- full width/height + else + local sticky_bound_fix = growths_rel[growth].sticky_bound_fix and + self:boundQ(growths_rel[growth].side) and + not self:boundQ(directions_rel[growths_rel[growth].side].opp) + local prev_window_ani + if sticky_bound_fix then + prev_window_ani = hs.window.animationDuration + hs.window.animationDuration = 0 end - cb(cell, nextSize) - if cell[oppAxis] ~= 0 and cell[oppAxis] + cell[oppDim] ~= self.GRID[oppDim] then - cell[oppDim] = self.GRID[oppDim] - cell[oppAxis] = 0 + hs.grid['resizeWindow'.. growth:titleCase()](frontmost.window()) + + if sticky_bound_fix then + logger.i("Sticking to ".. growths_rel[growth].side .. + " side since we're bound to it.") + hs.window.animationDuration = prev_window_ani + self:move(growths_rel[growth].side) end + end + return self +end +hs.fnutils.each(growths, -- taller(), shorter, wider, thinner + function(growth) + obj[growth] = function(self) return self:grow(growth) end + end ) - hs.grid.set(win, cell, screen) + +--- MiroWindowsManager:growFully(growth) +--- Method +--- Grow the frontmost window to full width / height taller, wider. +--- Also: +--- * MiroWindowsManager:tallest() +--- * MiroWindowsManager:widest() +--- +--- Parameters: +--- * growth - taller, wider +--- +--- Returns: +--- * The MiroWindowsManager object +function obj:growFully(growth) + expect_argument_to_be_in_table(growth, growths) + + local cell = frontmost.cell() + cell[growths_rel[growth].pos] = 0 + cell[growths_rel[growth].dim] = self.GRID[growths_rel[growth].dim] + self._setPosition(cell) + return self +end +function obj:growTallest() return self:growFully('taller') end +function obj:growWidest() return self:growFully('wider') end + + +--- MiroWindowsManager:go(move) +--- Method +--- Move to screen edge, or cycle to next horizontal or vertical size if already there. +--- Tap both directions to go full width / height. +--- Also: +--- * MiroWindowsManager:goUp() +--- * MiroWindowsManager:goDown() +--- * MiroWindowsManager:goLeft() +--- * MiroWindowsManager:goRight() +--- +--- Parameters: +--- * move - up, down, left, right +--- +--- Returns: +--- * The MiroWindowsManager object +function obj:go(move) + if move == 'fullscreen' then return self:goFullscreen() end + + expect_argument_to_be_in_table(move, directions) + + register_press(move) + if pressedQ(directions_rel[move].opp) then + -- if still keydown moving the in the opposite direction, go full width/height + logger.i("Maximising ".. directions_rel[move].dim .." since ".. + directions_rel[move].opp .." still active.") + self:growFully(directions_rel[move].grow) -- full width/height + else + local cell = frontmost.cell() + local seq = self:seqQ(move) + local log_info = "We're at ".. move .." sequence ".. tostring(seq) .."(".. hs.inspect(cell) ..")" + seq = seq % #self.sizes -- if #self.sizes then 0 + log_info = log_info .. ", so moving to sequence " .. tostring(seq + 1) + logger.i(log_info) + cell[directions_rel[move].dim] = self.GRID[directions_rel[move].dim] / self.sizes[seq + 1] + if hs.fnutils.contains({'left', 'up'}, move) then + cell[directions_rel[move].pos] = directions_rel[move].home + else + cell[directions_rel[move].pos] = directions_rel[move].home - cell[directions_rel[move].dim] + end + self._setPosition(cell) end + return self end +hs.fnutils.each(directions, -- goUp(), goDown, goLeft, goRight + function(move) + obj['go'.. move:titleCase()] = function(self) return self:go(move) end + end) -function obj:_nextFullScreenStep() - if hs.window.focusedWindow() then - local win = hs.window.frontmostWindow() - local id = win:id() - local screen = win:screen() +--- MiroWindowsManager:goFullscreen() +--- Method +--- Fullscreen, or cycle to next fullscreen option +--- +--- Parameters: +--- * None. +--- +--- Returns: +--- * The MiroWindowsManager object +function obj:goFullscreen() + local cell + if not self:centeredQ() then + cell = frontmost.cell() + cell.x = (self.GRID.w - cell.w) / 2 + cell.y = (self.GRID.h - cell.h) / 2 + self._setPosition(cell) + if cell == frontmost.cell() then + -- we didn't move, assume a rounding error + self._setPosition(self:seqFullCell(1)) + else + logger.i("Not centered, so centering (".. hs.inspect(cell) ..")") + end + else + local seq = self:seqFullQ() + local log_info = "We're at fullscreen sequence ".. tostring(seq) .."(".. hs.inspect(frontmost.cell()) ..")" + seq = seq % #self.fullScreenSizes -- if #self.fullScreenSizes then 0 + log_info = log_info .. ", so moving to sequence " .. tostring(seq + 1) + logger.i(log_info) + cell = self:seqFullCell(seq + 1) -- next in sequence + self._setPosition(cell) + end + return self +end - cell = hs.grid.get(win, screen) - local nextSize = self.fullScreenSizes[1] - for i=1,#self.fullScreenSizes do - if cell.w == self.GRID.w / self.fullScreenSizes[i] and - cell.h == self.GRID.h / self.fullScreenSizes[i] and - cell.x == (self.GRID.w - self.GRID.w / self.fullScreenSizes[i]) / 2 and - cell.y == (self.GRID.h - self.GRID.h / self.fullScreenSizes[i]) / 2 then - nextSize = self.fullScreenSizes[(i % #self.fullScreenSizes) + 1] - break - end +-- ## Public undocumented + +-- Query fullscreen sequence - 0 means out of sequence +function obj:seqFullQ() + local cell = frontmost.cell() + for i = 1,#self.fullScreenSizes do + if cell == self:seqFullCell(i) then + return i end + end + return 0 +end + +-- Query whether window is centered +function obj:centeredQ() + local cell = frontmost.cell() + local h_center = cell.w + 2 * cell.x == self.GRID.w + local v_center = cell.h + 2 * cell.y == self.GRID.h + return h_center and v_center +end - cell.w = self.GRID.w / nextSize - cell.h = self.GRID.h / nextSize - cell.x = (self.GRID.w - self.GRID.w / nextSize) / 2 - cell.y = (self.GRID.h - self.GRID.h / nextSize) / 2 +-- hs.grid cell for fullscreen sequence `seq` +function obj:seqFullCell(seq) + local size = { + x = self.GRID.w / self.fullScreenSizes[seq], + y = self.GRID.h / self.fullScreenSizes[seq], + } + local offset = { + x = (self.GRID.w - size.x) / 2, + y = (self.GRID.h - size.y) / 2, + } + local cell = hs.geometry({ + x = offset.x, + y = offset.y, + w = size.x, + h = size.y, + }) + return cell +end - hs.grid.set(win, cell, screen) + +-- Query sequence for `side` - 0 means out of sequence +function obj:seqQ(side) + expect_argument_to_be_in_table(side, directions) + + if self:boundQ(side) then + local dim = directions_rel[side].dim + local width = frontmost.cell()[dim] + local relative_size = self.GRID[dim] / width + return hs.fnutils.indexOf(self.sizes, relative_size) or 0 + else + return 0 end end +-- upSeqQ(seq), downSeqQ, leftSeqQ, rightSeqQ +hs.fnutils.each(directions, + function(side) + obj[side ..'SeqQ'] = function(self) return self:seqQ(side) end + end ) -function obj:_fullDimension(dim) - if hs.window.focusedWindow() then - local win = hs.window.frontmostWindow() - local id = win:id() - local screen = win:screen() - cell = hs.grid.get(win, screen) +-- Set sequence for `side` +function obj:setToSeq(side, seq) + expect_argument_to_be_in_table(side, directions) + expect_truthy(type(seq) == 'number', "type(seq) == 'number'") + expect_truthy(seq ~= 0 and seq <= #self.sizes, "seq ~= 0 and seq <= #self.sizes") - if (dim == 'x') then - cell = '0,0 ' .. self.GRID.w .. 'x' .. self.GRID.h - else - cell[dim] = self.GRID[dim] - cell[dim == 'w' and 'x' or 'y'] = 0 - end + local cell = frontmost.cell() + cell[directions_rel[side].pos] = directions_rel[side].home + cell[directions_rel[side].dim] = self.sizes[seq] + self._setPosition(cell) + return self +end +hs.fnutils.each(directions, -- setToUpSeq(seq), setToDownSeq, setToLeftSeq, setToRightSeq + function(side) + obj['setTo'.. side:titleCase() ..'Seq'] = function(self, seq) return self:seq(side, seq) end + end ) + +-- Query whether window is bound to `side` (is touching that side of the screen) +function obj:boundQ(side) + expect_argument_to_be_in_table(side, directions) - hs.grid.set(win, cell, screen) + local cell = frontmost.cell() + if side == 'up' then + return cell.y == 0 + elseif side == 'down' then + return cell.y + cell.h == self.GRID.h + elseif side == 'left' then + return cell.x == 0 + elseif side == 'right' then + return cell.x + cell.w == self.GRID.w end end +hs.fnutils.each(directions, -- upBoundQ(), downBoundQ, leftBoundQ, rightBoundQ - on edge? + function(side) + obj[side ..'BoundQ'] = function(self) return self:boundQ(side) end + end ) + +-- Set window to cell +function obj._setPosition(cell) + expect_truthy( + type(cell) == 'table' and type(cell.type) == 'function' and cell:type() == 'rect', + "type(cell) == 'table' and type(cell.type) == 'function' and cell:type() == 'rect'") + + local win = hs.window.frontmostWindow() + hs.grid.set(win, cell, win:screen()) +end + + +-- ## Spoon mechanics (`bind`, `init`) + +obj.hotkeys = {} --- MiroWindowsManager:bindHotkeys() --- Method --- Binds hotkeys for Miro's Windows Manager +--- --- Parameters: --- * mapping - A table containing hotkey details for the following items: +--- * left: for the left action (usually `{hyper, "left"}`) +--- * right: for the right action (usually `{hyper, "right"}`) --- * up: for the up action (usually {hyper, "up"}) ---- * right: for the right action (usually {hyper, "right"}) ---- * down: for the down action (usually {hyper, "down"}) ---- * left: for the left action (usually {hyper, "left"}) ---- * fullscreen: for the full-screen action (e.g. {hyper, "f"}) +--- * down: for the down action (usually `{hyper, "down"}`) +--- * fullscreen: for the full-screen action (e.g. `{hyper, "f"}`) --- --- A configuration example can be: ---- ``` ---- local hyper = {"ctrl", "alt", "cmd"} +--- ``` lua +--- local mods = {"ctrl", "alt", "cmd"} --- spoon.MiroWindowsManager:bindHotkeys({ ---- up = {hyper, "up"}, ---- right = {hyper, "right"}, ---- down = {hyper, "down"}, ---- left = {hyper, "left"}, ---- fullscreen = {hyper, "f"} +--- up = {mods, "up"}, +--- down = {mods, "down"}, +--- left = {mods, "left"}, +--- right = {mods, "right"}, +--- fullscreen = {mods, "f"}, +--- moveUp = {{'⌃','⌥'}, "up"}, +--- moveDown = {{'⌃','⌥'}, "down"}, +--- moveLeft = {{'⌃','⌥'}, "left"}, +--- moveRight = {{'⌃','⌥'}, "right"}, +--- taller = {{'⌃','⌥','⇧'}, "down"}, +--- shorter = {{'⌃','⌥','⇧'}, "up"}, +--- wider = {{'⌃','⌥','⇧'}, "right"}, +--- thinner = {{'⌃','⌥','⇧'}, "left"}, --- }) --- ``` function obj:bindHotkeys(mapping) - hs.inspect(mapping) - print("Bind Hotkeys for Miro's Windows Manager") + logger.i("Bind Hotkeys for Miro's Windows Manager") - hs.hotkey.bind(mapping.down[1], mapping.down[2], function () - self._pressed.down = true - if self._pressed.up then - self:_fullDimension('h') - else - self:_nextStep('h', true, function (cell, nextSize) - cell.y = self.GRID.h - self.GRID.h / nextSize - cell.h = self.GRID.h / nextSize - end) - end - end, function () - self._pressed.down = false - end) - - hs.hotkey.bind(mapping.right[1], mapping.right[2], function () - self._pressed.right = true - if self._pressed.left then - self:_fullDimension('w') - else - self:_nextStep('w', true, function (cell, nextSize) - cell.x = self.GRID.w - self.GRID.w / nextSize - cell.w = self.GRID.w / nextSize - end) - end - end, function () - self._pressed.right = false - end) + hs.fnutils.each(directions, -- up, down, left, right + function(direction) + -- go + if mapping[direction] then + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind(mapping[direction][1], mapping[direction][2], + function() self:go(direction) end, + function() cancel_press(direction) end) + end - hs.hotkey.bind(mapping.left[1], mapping.left[2], function () - self._pressed.left = true - if self._pressed.right then - self:_fullDimension('w') - else - self:_nextStep('w', false, function (cell, nextSize) - cell.x = 0 - cell.w = self.GRID.w / nextSize - end) - end - end, function () - self._pressed.left = false - end) + -- move + local move_command = 'move'.. direction:titleCase() + if mapping[move_command] then + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind(mapping[move_command][1], mapping[move_command][2], + function() self:move(direction) end) + end + end) - hs.hotkey.bind(mapping.up[1], mapping.up[2], function () - self._pressed.up = true - if self._pressed.down then - self:_fullDimension('h') - else - self:_nextStep('h', false, function (cell, nextSize) - cell.y = 0 - cell.h = self.GRID.h / nextSize - end) - end - end, function () - self._pressed.up = false - end) + hs.fnutils.each(growths, -- taller, shorter, wider, thinner + function(sense) + -- grow + if mapping[sense] then + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind(mapping[sense][1], mapping[sense][2], + function() self:grow(sense) end, + function() cancel_press(sense) end) + end + end) - hs.hotkey.bind(mapping.fullscreen[1], mapping.fullscreen[2], function () - self:_nextFullScreenStep() - end) + if mapping.fullscreen then + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind(mapping.fullscreen[1], mapping.fullscreen[2], + function() self:goFullscreen() end) + end end +--- MiroWindowsManager:init() +--- Method +--- LEGACY: Calling this is not required. function obj:init() - print("Initializing Miro's Windows Manager") - hs.grid.setGrid(obj.GRID.w .. 'x' .. obj.GRID.h) - hs.grid.MARGINX = 0 - hs.grid.MARGINY = 0 + -- Nothing to do here end return obj From 7a6c82f066f72b9d8c89d57016f59425a039ef77 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Wed, 30 May 2018 21:39:25 +1000 Subject: [PATCH 16/41] bugfix: hs.grid rounding issues were preventing fullscreen sizing, fixed --- MiroWindowsManager.spoon/docs.json | 599 +---------------------------- MiroWindowsManager.spoon/init.lua | 236 +++++++----- 2 files changed, 141 insertions(+), 694 deletions(-) diff --git a/MiroWindowsManager.spoon/docs.json b/MiroWindowsManager.spoon/docs.json index 1a970f8..c44dc44 100644 --- a/MiroWindowsManager.spoon/docs.json +++ b/MiroWindowsManager.spoon/docs.json @@ -1,600 +1,3 @@ [ - { - "Constant" : [ - ], - "submodules" : [ - - ], - "Function" : [ - - ], - "Variable" : [ - { - "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total\nscreen's size.", - "stripped_doc" : [ - "The sizes that the window can have. ", - "The sizes are expressed as dividend of the entire screen's size. ", - "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total", - "screen's size." - ], - "parameters" : [ - - ], - "def" : "MiroWindowsManager.sizes", - "notes" : [ - - ], - "signature" : "MiroWindowsManager.sizes", - "type" : "Variable", - "returns" : [ - - ], - "desc" : "The sizes that the window can have.", - "name" : "sizes" - }, - { - "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4\nand 1\/2 of the total screen's size", - "stripped_doc" : [ - "The sizes that the window can have in full-screen. ", - "The sizes are expressed as dividend of the entire screen's size. ", - "For example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4", - "and 1\/2 of the total screen's size" - ], - "parameters" : [ - - ], - "def" : "MiroWindowsManager.fullScreenSizes", - "notes" : [ - - ], - "signature" : "MiroWindowsManager.fullScreenSizes", - "type" : "Variable", - "returns" : [ - - ], - "desc" : "The sizes that the window can have in full-screen.", - "name" : "fullScreenSizes" - }, - { - "doc" : "The screen's grid size.", - "stripped_doc" : [ - "The screen's grid size." - ], - "parameters" : [ - - ], - "def" : "MiroWindowsManager.GRID", - "notes" : [ - - ], - "signature" : "MiroWindowsManager.GRID", - "type" : "Variable", - "returns" : [ - - ], - "desc" : "The screen's grid size.", - "name" : "GRID" - }, - { - "doc" : "If `move`d past the screen edge, jump to next screen?", - "stripped_doc" : [ - "If `move`d past the screen edge, jump to next screen?" - ], - "parameters" : [ - - ], - "def" : "MiroWindowsManager.pushToNextScreen", - "notes" : [ - - ], - "signature" : "MiroWindowsManager.pushToNextScreen", - "type" : "Variable", - "returns" : [ - - ], - "desc" : "If `move`d past the screen edge, jump to next screen?", - "name" : "pushToNextScreen" - } - ], - "stripped_doc" : [ - - ], - "desc" : "With this Spoon you will be able to move the window in halves and in", - "Deprecated" : [ - - ], - "type" : "Module", - "Constructor" : [ - - ], - "doc" : "With this Spoon you will be able to move the window in halves and in\ncorners using your keyboard and mainly using arrows. You would also be able\nto resize them by thirds, quarters, or halves. \nOfficial homepage for more info and documentation:\n[https:\/\/github.com\/miromannino\/miro-windows-manager](https:\/\/github.com\/miromannino\/miro-windows-manager)\n\nNOTE: This Spoon sets `hs.grid` globals with `hs.grid.setGrid()`,\n`hs.grid.MARGINX`, and `hs.grid.MARGINY`. Changing MiroWindowsManager.GRID\nwill change these globals.\n\nDownload:\nhttps:\/\/github.com\/miromannino\/miro-windows-manager\/raw\/master\/MiroWindowsManager.spoon.zip", - "Field" : [ - - ], - "Command" : [ - - ], - "items" : [ - { - "doc" : "The screen's grid size.", - "stripped_doc" : [ - "The screen's grid size." - ], - "parameters" : [ - - ], - "def" : "MiroWindowsManager.GRID", - "notes" : [ - - ], - "signature" : "MiroWindowsManager.GRID", - "type" : "Variable", - "returns" : [ - - ], - "desc" : "The screen's grid size.", - "name" : "GRID" - }, - { - "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4\nand 1\/2 of the total screen's size", - "stripped_doc" : [ - "The sizes that the window can have in full-screen. ", - "The sizes are expressed as dividend of the entire screen's size. ", - "For example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4", - "and 1\/2 of the total screen's size" - ], - "parameters" : [ - - ], - "def" : "MiroWindowsManager.fullScreenSizes", - "notes" : [ - - ], - "signature" : "MiroWindowsManager.fullScreenSizes", - "type" : "Variable", - "returns" : [ - - ], - "desc" : "The sizes that the window can have in full-screen.", - "name" : "fullScreenSizes" - }, - { - "doc" : "If `move`d past the screen edge, jump to next screen?", - "stripped_doc" : [ - "If `move`d past the screen edge, jump to next screen?" - ], - "parameters" : [ - - ], - "def" : "MiroWindowsManager.pushToNextScreen", - "notes" : [ - - ], - "signature" : "MiroWindowsManager.pushToNextScreen", - "type" : "Variable", - "returns" : [ - - ], - "desc" : "If `move`d past the screen edge, jump to next screen?", - "name" : "pushToNextScreen" - }, - { - "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total\nscreen's size.", - "stripped_doc" : [ - "The sizes that the window can have. ", - "The sizes are expressed as dividend of the entire screen's size. ", - "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total", - "screen's size." - ], - "parameters" : [ - - ], - "def" : "MiroWindowsManager.sizes", - "notes" : [ - - ], - "signature" : "MiroWindowsManager.sizes", - "type" : "Variable", - "returns" : [ - - ], - "desc" : "The sizes that the window can have.", - "name" : "sizes" - }, - { - "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n\nA configuration example can be:\n``` lua\nlocal mods = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {mods, \"up\"},\n down = {mods, \"down\"},\n left = {mods, \"left\"},\n right = {mods, \"right\"},\n fullscreen = {mods, \"f\"},\n moveUp = {{'⌃','⌥'}, \"up\"},\n moveDown = {{'⌃','⌥'}, \"down\"},\n moveLeft = {{'⌃','⌥'}, \"left\"},\n moveRight = {{'⌃','⌥'}, \"right\"},\n taller = {{'⌃','⌥','⇧'}, \"down\"},\n shorter = {{'⌃','⌥','⇧'}, \"up\"},\n wider = {{'⌃','⌥','⇧'}, \"right\"},\n thinner = {{'⌃','⌥','⇧'}, \"left\"},\n})\n```", - "stripped_doc" : [ - "Binds hotkeys for Miro's Windows Manager", - "" - ], - "parameters" : [ - " * mapping - A table containing hotkey details for the following items:", - " * left: for the left action (usually `{hyper, \"left\"}`)", - " * right: for the right action (usually `{hyper, \"right\"}`)", - " * up: for the up action (usually {hyper, \"up\"})", - " * down: for the down action (usually `{hyper, \"down\"}`)", - " * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)", - "", - "A configuration example can be:", - "``` lua", - "local mods = {\"ctrl\", \"alt\", \"cmd\"}", - "spoon.MiroWindowsManager:bindHotkeys({", - " up = {mods, \"up\"},", - " down = {mods, \"down\"},", - " left = {mods, \"left\"},", - " right = {mods, \"right\"},", - " fullscreen = {mods, \"f\"},", - " moveUp = {{'⌃','⌥'}, \"up\"},", - " moveDown = {{'⌃','⌥'}, \"down\"},", - " moveLeft = {{'⌃','⌥'}, \"left\"},", - " moveRight = {{'⌃','⌥'}, \"right\"},", - " taller = {{'⌃','⌥','⇧'}, \"down\"},", - " shorter = {{'⌃','⌥','⇧'}, \"up\"},", - " wider = {{'⌃','⌥','⇧'}, \"right\"},", - " thinner = {{'⌃','⌥','⇧'}, \"left\"},", - "})", - "```" - ], - "def" : "MiroWindowsManager:bindHotkeys()", - "notes" : [ - - ], - "signature" : "MiroWindowsManager:bindHotkeys()", - "type" : "Method", - "returns" : [ - - ], - "desc" : "Binds hotkeys for Miro's Windows Manager", - "name" : "bindHotkeys" - }, - { - "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \nAlso: \n * MiroWindowsManager:goUp()\n * MiroWindowsManager:goDown()\n * MiroWindowsManager:goLeft()\n * MiroWindowsManager:goRight()\n\nParameters:\n * move - up, down, left, right\n\nReturns:\n * The MiroWindowsManager object", - "stripped_doc" : [ - "Move to screen edge, or cycle to next horizontal or vertical size if already there. ", - "Tap both directions to go full width \/ height. ", - "Also: ", - " * MiroWindowsManager:goUp()", - " * MiroWindowsManager:goDown()", - " * MiroWindowsManager:goLeft()", - " * MiroWindowsManager:goRight()", - "" - ], - "parameters" : [ - " * move - up, down, left, right", - "" - ], - "def" : "MiroWindowsManager:go(move)", - "notes" : [ - - ], - "signature" : "MiroWindowsManager:go(move)", - "type" : "Method", - "returns" : [ - " * The MiroWindowsManager object" - ], - "desc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there.", - "name" : "go" - }, - { - "doc" : "Fullscreen, or cycle to next fullscreen option\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", - "stripped_doc" : [ - "Fullscreen, or cycle to next fullscreen option", - "" - ], - "parameters" : [ - " * None.", - "" - ], - "def" : "MiroWindowsManager:goFullscreen()", - "notes" : [ - - ], - "signature" : "MiroWindowsManager:goFullscreen()", - "type" : "Method", - "returns" : [ - " * The MiroWindowsManager object" - ], - "desc" : "Fullscreen, or cycle to next fullscreen option", - "name" : "goFullscreen" - }, - { - "doc" : "Grow the frontmost window taller, shorter, wider, thinner.\nAlso:\n * MiroWindowsManager:taller()\n * MiroWindowsManager:shorter()\n * MiroWindowsManager:wider()\n * MiroWindowsManager:thinner()\n\nParameters:\n * growth - taller, shorter, wider, thinner\n\nReturns:\n * The MiroWindowsManager object", - "stripped_doc" : [ - "Grow the frontmost window taller, shorter, wider, thinner.", - "Also:", - " * MiroWindowsManager:taller()", - " * MiroWindowsManager:shorter()", - " * MiroWindowsManager:wider()", - " * MiroWindowsManager:thinner()", - "" - ], - "parameters" : [ - " * growth - taller, shorter, wider, thinner", - "" - ], - "def" : "MiroWindowsManager:grow(growth)", - "notes" : [ - - ], - "signature" : "MiroWindowsManager:grow(growth)", - "type" : "Method", - "returns" : [ - " * The MiroWindowsManager object" - ], - "desc" : "Grow the frontmost window taller, shorter, wider, thinner.", - "name" : "grow" - }, - { - "doc" : "Grow the frontmost window to full width \/ height taller, wider. \nAlso:\n * MiroWindowsManager:tallest()\n * MiroWindowsManager:widest()\n\nParameters:\n * growth - taller, wider\n\nReturns:\n * The MiroWindowsManager object", - "stripped_doc" : [ - "Grow the frontmost window to full width \/ height taller, wider. ", - "Also:", - " * MiroWindowsManager:tallest()", - " * MiroWindowsManager:widest()", - "" - ], - "parameters" : [ - " * growth - taller, wider", - "" - ], - "def" : "MiroWindowsManager:growFully(growth)", - "notes" : [ - - ], - "signature" : "MiroWindowsManager:growFully(growth)", - "type" : "Method", - "returns" : [ - " * The MiroWindowsManager object" - ], - "desc" : "Grow the frontmost window to full width \/ height taller, wider.", - "name" : "growFully" - }, - { - "doc" : "LEGACY: Calling this is not required.", - "stripped_doc" : [ - "LEGACY: Calling this is not required." - ], - "parameters" : [ - - ], - "def" : "MiroWindowsManager:init()", - "notes" : [ - - ], - "signature" : "MiroWindowsManager:init()", - "type" : "Method", - "returns" : [ - - ], - "desc" : "LEGACY: Calling this is not required.", - "name" : "init" - }, - { - "doc" : "Move the frontmost window up, down, left, right. \nAlso:\n * MiroWindowsManager:moveUp()\n * MiroWindowsManager:moveDown()\n * MiroWindowsManager:moveLeft()\n * MiroWindowsManager:moveRight()\n\nParameters:\n * side - up, down, left, right\n\nReturns:\n * The MiroWindowsManager object", - "stripped_doc" : [ - "Move the frontmost window up, down, left, right. ", - "Also:", - " * MiroWindowsManager:moveUp()", - " * MiroWindowsManager:moveDown()", - " * MiroWindowsManager:moveLeft()", - " * MiroWindowsManager:moveRight()", - "" - ], - "parameters" : [ - " * side - up, down, left, right", - "" - ], - "def" : "MiroWindowsManager:move(side)", - "notes" : [ - - ], - "signature" : "MiroWindowsManager:move(side)", - "type" : "Method", - "returns" : [ - " * The MiroWindowsManager object" - ], - "desc" : "Move the frontmost window up, down, left, right.", - "name" : "move" - } - ], - "Method" : [ - { - "doc" : "Move the frontmost window up, down, left, right. \nAlso:\n * MiroWindowsManager:moveUp()\n * MiroWindowsManager:moveDown()\n * MiroWindowsManager:moveLeft()\n * MiroWindowsManager:moveRight()\n\nParameters:\n * side - up, down, left, right\n\nReturns:\n * The MiroWindowsManager object", - "stripped_doc" : [ - "Move the frontmost window up, down, left, right. ", - "Also:", - " * MiroWindowsManager:moveUp()", - " * MiroWindowsManager:moveDown()", - " * MiroWindowsManager:moveLeft()", - " * MiroWindowsManager:moveRight()", - "" - ], - "parameters" : [ - " * side - up, down, left, right", - "" - ], - "def" : "MiroWindowsManager:move(side)", - "notes" : [ - - ], - "signature" : "MiroWindowsManager:move(side)", - "type" : "Method", - "returns" : [ - " * The MiroWindowsManager object" - ], - "desc" : "Move the frontmost window up, down, left, right.", - "name" : "move" - }, - { - "doc" : "Grow the frontmost window taller, shorter, wider, thinner.\nAlso:\n * MiroWindowsManager:taller()\n * MiroWindowsManager:shorter()\n * MiroWindowsManager:wider()\n * MiroWindowsManager:thinner()\n\nParameters:\n * growth - taller, shorter, wider, thinner\n\nReturns:\n * The MiroWindowsManager object", - "stripped_doc" : [ - "Grow the frontmost window taller, shorter, wider, thinner.", - "Also:", - " * MiroWindowsManager:taller()", - " * MiroWindowsManager:shorter()", - " * MiroWindowsManager:wider()", - " * MiroWindowsManager:thinner()", - "" - ], - "parameters" : [ - " * growth - taller, shorter, wider, thinner", - "" - ], - "def" : "MiroWindowsManager:grow(growth)", - "notes" : [ - - ], - "signature" : "MiroWindowsManager:grow(growth)", - "type" : "Method", - "returns" : [ - " * The MiroWindowsManager object" - ], - "desc" : "Grow the frontmost window taller, shorter, wider, thinner.", - "name" : "grow" - }, - { - "doc" : "Grow the frontmost window to full width \/ height taller, wider. \nAlso:\n * MiroWindowsManager:tallest()\n * MiroWindowsManager:widest()\n\nParameters:\n * growth - taller, wider\n\nReturns:\n * The MiroWindowsManager object", - "stripped_doc" : [ - "Grow the frontmost window to full width \/ height taller, wider. ", - "Also:", - " * MiroWindowsManager:tallest()", - " * MiroWindowsManager:widest()", - "" - ], - "parameters" : [ - " * growth - taller, wider", - "" - ], - "def" : "MiroWindowsManager:growFully(growth)", - "notes" : [ - - ], - "signature" : "MiroWindowsManager:growFully(growth)", - "type" : "Method", - "returns" : [ - " * The MiroWindowsManager object" - ], - "desc" : "Grow the frontmost window to full width \/ height taller, wider.", - "name" : "growFully" - }, - { - "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \nAlso: \n * MiroWindowsManager:goUp()\n * MiroWindowsManager:goDown()\n * MiroWindowsManager:goLeft()\n * MiroWindowsManager:goRight()\n\nParameters:\n * move - up, down, left, right\n\nReturns:\n * The MiroWindowsManager object", - "stripped_doc" : [ - "Move to screen edge, or cycle to next horizontal or vertical size if already there. ", - "Tap both directions to go full width \/ height. ", - "Also: ", - " * MiroWindowsManager:goUp()", - " * MiroWindowsManager:goDown()", - " * MiroWindowsManager:goLeft()", - " * MiroWindowsManager:goRight()", - "" - ], - "parameters" : [ - " * move - up, down, left, right", - "" - ], - "def" : "MiroWindowsManager:go(move)", - "notes" : [ - - ], - "signature" : "MiroWindowsManager:go(move)", - "type" : "Method", - "returns" : [ - " * The MiroWindowsManager object" - ], - "desc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there.", - "name" : "go" - }, - { - "doc" : "Fullscreen, or cycle to next fullscreen option\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", - "stripped_doc" : [ - "Fullscreen, or cycle to next fullscreen option", - "" - ], - "parameters" : [ - " * None.", - "" - ], - "def" : "MiroWindowsManager:goFullscreen()", - "notes" : [ - - ], - "signature" : "MiroWindowsManager:goFullscreen()", - "type" : "Method", - "returns" : [ - " * The MiroWindowsManager object" - ], - "desc" : "Fullscreen, or cycle to next fullscreen option", - "name" : "goFullscreen" - }, - { - "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n\nA configuration example can be:\n``` lua\nlocal mods = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {mods, \"up\"},\n down = {mods, \"down\"},\n left = {mods, \"left\"},\n right = {mods, \"right\"},\n fullscreen = {mods, \"f\"},\n moveUp = {{'⌃','⌥'}, \"up\"},\n moveDown = {{'⌃','⌥'}, \"down\"},\n moveLeft = {{'⌃','⌥'}, \"left\"},\n moveRight = {{'⌃','⌥'}, \"right\"},\n taller = {{'⌃','⌥','⇧'}, \"down\"},\n shorter = {{'⌃','⌥','⇧'}, \"up\"},\n wider = {{'⌃','⌥','⇧'}, \"right\"},\n thinner = {{'⌃','⌥','⇧'}, \"left\"},\n})\n```", - "stripped_doc" : [ - "Binds hotkeys for Miro's Windows Manager", - "" - ], - "parameters" : [ - " * mapping - A table containing hotkey details for the following items:", - " * left: for the left action (usually `{hyper, \"left\"}`)", - " * right: for the right action (usually `{hyper, \"right\"}`)", - " * up: for the up action (usually {hyper, \"up\"})", - " * down: for the down action (usually `{hyper, \"down\"}`)", - " * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)", - "", - "A configuration example can be:", - "``` lua", - "local mods = {\"ctrl\", \"alt\", \"cmd\"}", - "spoon.MiroWindowsManager:bindHotkeys({", - " up = {mods, \"up\"},", - " down = {mods, \"down\"},", - " left = {mods, \"left\"},", - " right = {mods, \"right\"},", - " fullscreen = {mods, \"f\"},", - " moveUp = {{'⌃','⌥'}, \"up\"},", - " moveDown = {{'⌃','⌥'}, \"down\"},", - " moveLeft = {{'⌃','⌥'}, \"left\"},", - " moveRight = {{'⌃','⌥'}, \"right\"},", - " taller = {{'⌃','⌥','⇧'}, \"down\"},", - " shorter = {{'⌃','⌥','⇧'}, \"up\"},", - " wider = {{'⌃','⌥','⇧'}, \"right\"},", - " thinner = {{'⌃','⌥','⇧'}, \"left\"},", - "})", - "```" - ], - "def" : "MiroWindowsManager:bindHotkeys()", - "notes" : [ - - ], - "signature" : "MiroWindowsManager:bindHotkeys()", - "type" : "Method", - "returns" : [ - - ], - "desc" : "Binds hotkeys for Miro's Windows Manager", - "name" : "bindHotkeys" - }, - { - "doc" : "LEGACY: Calling this is not required.", - "stripped_doc" : [ - "LEGACY: Calling this is not required." - ], - "parameters" : [ - - ], - "def" : "MiroWindowsManager:init()", - "notes" : [ - - ], - "signature" : "MiroWindowsManager:init()", - "type" : "Method", - "returns" : [ - - ], - "desc" : "LEGACY: Calling this is not required.", - "name" : "init" - } - ], - "name" : "MiroWindowsManager" - } -] +] \ No newline at end of file diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 0ecbf14..9869d86 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -39,38 +39,48 @@ obj.__index = obj -- Metadata obj.name = "MiroWindowsManager" -obj.version = "1.1" +obj.version = "1.2" obj.author = "Miro Mannino " obj.homepage = "https://github.com/miromannino/miro-windows-management" obj.license = "MIT - https://opensource.org/licenses/MIT" -obj._logger = hs.logger.new(obj.name) -local logger = obj._logger +local logger = hs.logger.new(obj.name) +obj._logger = logger -- make logger available so users can turn up the volume! logger.i("Loading ".. obj.name) -- ### Utilities function string.titleCase(str) + -- ## WARNING: Global change ## return (str:gsub('^%l', string.upper)) end -local function expect_argument_to_be_in_table(argument, to_be_in) +local expect = {} +function expect.argument_to_be_in_table(argument, to_be_in) assert( hs.fnutils.contains(to_be_in, argument), 'Expected "'.. hs.inspect(argument) ..'" to be one of '.. hs.inspect(to_be_in) ) end -local function expect_argument_to_be_in_table_or_nil(argument, to_be_in) +function expect.argument_to_be_in_table_or_nil(argument, to_be_in) assert( hs.fnutils.contains(to_be_in, argument) or argument == nil, 'Expected "'.. hs.inspect(argument) ..'" to be one of '.. hs.inspect(to_be_in) ) end -local function expect_truthy(argument, expression) +function expect.truthy(argument, expression) assert( argument, 'Expected truthyness from '.. hs.inspect(expression) ) end +local function round(num) + if num >= 0 then + return math.floor(num+.499999999) + else + return math.ceil(num-.499999999) + end +end +obj._round = round -- ## Public variables @@ -80,7 +90,9 @@ end --- The sizes that the window can have. --- The sizes are expressed as dividend of the entire screen's size. --- For example `{2, 3, 3/2}` means that it can be 1/2, 1/3 and 2/3 of the total ---- screen's size. +--- screen's size. +--- Ensuring that these numbers all divide both dimensions of +--- MiroWindowsManager.GRID to give integers makes everything work better. obj.sizes = {2, 3, 3/2} --- MiroWindowsManager.fullScreenSizes @@ -88,18 +100,31 @@ obj.sizes = {2, 3, 3/2} --- The sizes that the window can have in full-screen. --- The sizes are expressed as dividend of the entire screen's size. --- For example `{1, 4/3, 2}` means that it can be 1/1 (hence full screen), 3/4 ---- and 1/2 of the total screen's size +--- and 1/2 of the total screen's size. +--- Ensuring that these numbers all divide both dimensions of +--- MiroWindowsManager.GRID to give integers makes everything work better. +--- Special: Use 'c' for the original size and shape of the window before +--- starting to move it, but centered. obj.fullScreenSizes = {1, 4/3, 2} +-- Comment: Lots of work here to save users a little work. Previous versions +-- required users to call MiroWindowsManager:start() every time they changed +-- GRID. The metatable work here watches for those changes and does the work +-- :start() would have done. +-- -- Ensure changes to GRID update hs.grid -- Prevent obj.GRID from being replaced -local _grid_store_yeeV5hiG = {} -- we store the real GRID here +local _grid_store_yeeV5hiG = {} -- we'll store the real GRID here setmetatable(obj, { - __index = function(t,k) + __index = function(t,k) -- if code reads obj, use this function (eg. `obj.sizes`) + -- if code accesses obj.GRID, return the real GRID we created above, + -- otherwise access obj as normal if k == 'GRID' then return _grid_store_yeeV5hiG else return rawget(t,k) end end, __newindex = - function(t,k,v) + function(t,k,v) -- if code writes to obj, use this function (eg. `obj.sizes = {2, 3}`) if k == 'GRID' then + -- if code assigns to obj.GRID (`obj.GRID = ...`), don't overwrite our + -- real GRID, otherwise access obj as normal assert(type(v)=='table',rawget(obj,'name')..".GRID must be a table.") -- assign the assigned table's content to our real GRID table for kk,vv in pairs(v) do obj.GRID[kk] = vv end @@ -109,16 +134,20 @@ setmetatable(obj, { end, }) -- Update hs.grid after relevant changes to obj.GRID -local _grid_value_store_Oocaeyim = {} -- we store the real GRID values here +local _grid_value_store_Oocaeyim = {} -- we store the real GRID *values* here local m = { __newindex = - function(t,k,v) - rawset(_grid_value_store_Oocaeyim,k,v) + function(t,k,v) -- if code assigns to our real GRID… + rawset(_grid_value_store_Oocaeyim,k,v) -- do the assignment, then… if hs.fnutils.contains({'w','h'}, k) then + -- update hs.grid with hs.grid.setGrid, so the user doesn't have to hs.grid.setGrid(tostring(_grid_value_store_Oocaeyim.w or 0) .. 'x' .. tostring(_grid_value_store_Oocaeyim.h or 0)) + logger.i("Updated hs.grid to ".. hs.inspect(_grid_value_store_Oocaeyim)) elseif k == 'margins' then + -- update hs.grid with hs.grid.setMargins, so the user doesn't have to hs.grid.setMargins(v) + logger.i("Updated hs.grid to ".. hs.inspect(_grid_value_store_Oocaeyim)) elseif hs.fnutils.contains({'MARGINX','MARGINY'}, k) then -- LEGACY if k == 'MARGINX' then @@ -129,8 +158,8 @@ local m = { hs.geometry(tostring(rawget(_grid_value_store_Oocaeyim, 'margins').w or 0) ..'x'.. tostring(v)) end + logger.i("Updated hs.grid to ".. hs.inspect(_grid_value_store_Oocaeyim)) end - logger.i("Updated hs.grid to ".. hs.inspect(_grid_value_store_Oocaeyim)) end, __index = function(t,k) return rawget(_grid_value_store_Oocaeyim, k) end, @@ -139,13 +168,19 @@ setmetatable(_grid_store_yeeV5hiG, m) --- MiroWindowsManager.GRID --- Variable ---- The screen's grid size. -obj.GRID = { w = 24, h = 24, margins = hs.geometry("0x0") } +--- The screen's grid size. +--- Ensuring that the numbers in MiroWindowsManager.sizes and +--- MiroWindowsManager.fullScreenSizes divide these numbers to give integers +--- makes everything work better. +obj.GRID = { w = 24, h = 24, margins = hs.geometry.point(0,0) } +function obj.GRID.cell() + return hs.geometry(obj.GRID.margins, hs.geometry.size(obj.GRID.w, obj.GRID.h)) +end --- MiroWindowsManager.pushToNextScreen --- Variable ---- If `move`d past the screen edge, jump to next screen? +--- If `move`d past the screen edge, jump to next screen? (true|false) obj.pushToNextScreen = false @@ -156,11 +191,12 @@ obj.pushToNextScreen = false -- Window moves and their relationships local directions = { 'up', 'down', 'left', 'right' } local directions_rel = { - up = { opp = 'down', grow = 'taller', dim = 'h', pos = 'y', home = 0 }, - down = { opp = 'up', grow = 'taller', dim = 'h', pos = 'y', home = obj.GRID.h }, - left = { opp = 'right', grow = 'wider', dim = 'w', pos = 'x', home = 0 }, - right = { opp = 'left', grow = 'wider', dim = 'w', pos = 'x', home = obj.GRID.w }, + up = { opp = 'down', grow = 'taller', dim = 'h', pos = 'y', home = function() return 0 end }, + down = { opp = 'up', grow = 'taller', dim = 'h', pos = 'y', home = function() return obj.GRID.h end }, + left = { opp = 'right', grow = 'wider', dim = 'w', pos = 'x', home = function() return 0 end }, + right = { opp = 'left', grow = 'wider', dim = 'w', pos = 'x', home = function() return obj.GRID.w end }, } +obj._directions_rel = directions_rel -- Window growths and their relationships local growths = { 'taller', 'shorter', 'wider', 'thinner' } local growths_rel = { @@ -169,6 +205,7 @@ local growths_rel = { wider = { opp = 'thinner', dim = 'w', pos = 'x', side = 'left', sticky_bound_fix = false }, thinner = { opp = 'wider', dim = 'w', pos = 'x', side = 'right', sticky_bound_fix = true }, } +obj._growths_rel = growths_rel -- ### Internal state @@ -178,6 +215,7 @@ hs.fnutils.each(hs.fnutils.concat(directions, growths), function(move) obj._pressed[move] = false obj._press_timers[move] = hs.timer.doAfter(1, function() obj._pressed[move] = false end) end) +obj._originalPositionStore = {} local function register_press(direction) obj._pressed[direction] = true @@ -194,8 +232,8 @@ end -- ### Internal convenience functions -- Accessor for functions on the frontmost window -obj._frontmost = {} -local frontmost = obj._frontmost +local frontmost = {} +obj._frontmost = frontmost -- An hs.window for the frontmost window function frontmost.window() @@ -224,7 +262,7 @@ end --- Returns: --- * The MiroWindowsManager object function obj:move(side) - expect_argument_to_be_in_table(side, directions) + expect.argument_to_be_in_table(side, directions) if self:boundQ(side) and not self.pushToNextScreen then logger.i("`self.pushToNextScreen` == false so not moving to ".. side .." screen.") @@ -254,7 +292,7 @@ hs.fnutils.each(directions, -- up(), down, left, right --- Returns: --- * The MiroWindowsManager object function obj:grow(growth) - expect_argument_to_be_in_table(growth, growths) + expect.argument_to_be_in_table(growth, growths) register_press(growth) if pressedQ(growths_rel[growth].opp) then @@ -301,7 +339,7 @@ hs.fnutils.each(growths, -- taller(), shorter, wider, thinner --- Returns: --- * The MiroWindowsManager object function obj:growFully(growth) - expect_argument_to_be_in_table(growth, growths) + expect.argument_to_be_in_table(growth, growths) local cell = frontmost.cell() cell[growths_rel[growth].pos] = 0 @@ -331,7 +369,7 @@ function obj:growWidest() return self:growFully('wider') end function obj:go(move) if move == 'fullscreen' then return self:goFullscreen() end - expect_argument_to_be_in_table(move, directions) + expect.argument_to_be_in_table(move, directions) register_press(move) if pressedQ(directions_rel[move].opp) then @@ -341,18 +379,13 @@ function obj:go(move) self:growFully(directions_rel[move].grow) -- full width/height else local cell = frontmost.cell() - local seq = self:seqQ(move) - local log_info = "We're at ".. move .." sequence ".. tostring(seq) .."(".. hs.inspect(cell) ..")" + local seq = self:seqQ(move) -- current sequence index or 0 if out of sequence + local log_info = "We're at ".. move .." sequence ".. tostring(seq) .." (".. cell.string ..")" seq = seq % #self.sizes -- if #self.sizes then 0 log_info = log_info .. ", so moving to sequence " .. tostring(seq + 1) logger.i(log_info) - cell[directions_rel[move].dim] = self.GRID[directions_rel[move].dim] / self.sizes[seq + 1] - if hs.fnutils.contains({'left', 'up'}, move) then - cell[directions_rel[move].pos] = directions_rel[move].home - else - cell[directions_rel[move].pos] = directions_rel[move].home - cell[directions_rel[move].dim] - end - self._setPosition(cell) + + cell = self:setToSeq(move, seq + 1) end return self end @@ -372,74 +405,43 @@ hs.fnutils.each(directions, -- goUp(), goDown, goLeft, goRight --- * The MiroWindowsManager object function obj:goFullscreen() local cell - if not self:centeredQ() then - cell = frontmost.cell() - cell.x = (self.GRID.w - cell.w) / 2 - cell.y = (self.GRID.h - cell.h) / 2 - self._setPosition(cell) - if cell == frontmost.cell() then - -- we didn't move, assume a rounding error - self._setPosition(self:seqFullCell(1)) - else - logger.i("Not centered, so centering (".. hs.inspect(cell) ..")") - end - else - local seq = self:seqFullQ() - local log_info = "We're at fullscreen sequence ".. tostring(seq) .."(".. hs.inspect(frontmost.cell()) ..")" - seq = seq % #self.fullScreenSizes -- if #self.fullScreenSizes then 0 - log_info = log_info .. ", so moving to sequence " .. tostring(seq + 1) - logger.i(log_info) - cell = self:seqFullCell(seq + 1) -- next in sequence - self._setPosition(cell) + local seq = self:seqFullQ() -- current sequence index or 0 if out of sequence + local log_info = "We're at fullscreen sequence ".. tostring(seq) .." (".. frontmost.cell().string .."), so" + + if hs.fnutils.contains(self.fullScreenSizes, 'c') and seq == 0 then + -- We're out of the sequence, so store the current window position + obj._originalPositionStore[frontmost.window():id()] = frontmost.cell() + log_info = log_info .." remembering position then" end + + seq = seq % #self.fullScreenSizes -- if #self.fullScreenSizes then 0 + log_info = log_info .. " moving to sequence " .. tostring(seq + 1) + logger.i(log_info) + + cell = self:seqFullCell(seq + 1) -- next in sequence + self._setPosition(cell) + return self end -- ## Public undocumented --- Query fullscreen sequence - 0 means out of sequence -function obj:seqFullQ() - local cell = frontmost.cell() - for i = 1,#self.fullScreenSizes do - if cell == self:seqFullCell(i) then - return i - end - end - return 0 -end - -- Query whether window is centered function obj:centeredQ() local cell = frontmost.cell() - local h_center = cell.w + 2 * cell.x == self.GRID.w - local v_center = cell.h + 2 * cell.y == self.GRID.h - return h_center and v_center + return cell.w + 2 * cell.x == self.GRID.w and + cell.h + 2 * cell.y == self.GRID.h end --- hs.grid cell for fullscreen sequence `seq` -function obj:seqFullCell(seq) - local size = { - x = self.GRID.w / self.fullScreenSizes[seq], - y = self.GRID.h / self.fullScreenSizes[seq], - } - local offset = { - x = (self.GRID.w - size.x) / 2, - y = (self.GRID.h - size.y) / 2, - } - local cell = hs.geometry({ - x = offset.x, - y = offset.y, - w = size.x, - h = size.y, - }) +local function snap_to_grid(cell) + hs.fnutils.each({'w','h','x','y'}, function(d) cell[d] = round(cell[d]) end) return cell end - -- Query sequence for `side` - 0 means out of sequence function obj:seqQ(side) - expect_argument_to_be_in_table(side, directions) + expect.argument_to_be_in_table(side, directions) if self:boundQ(side) then local dim = directions_rel[side].dim @@ -458,13 +460,19 @@ hs.fnutils.each(directions, -- Set sequence for `side` function obj:setToSeq(side, seq) - expect_argument_to_be_in_table(side, directions) - expect_truthy(type(seq) == 'number', "type(seq) == 'number'") - expect_truthy(seq ~= 0 and seq <= #self.sizes, "seq ~= 0 and seq <= #self.sizes") + expect.argument_to_be_in_table(side, directions) + expect.truthy(type(seq) == 'number', "type(seq) == 'number'") + expect.truthy(seq ~= 0 and seq <= #self.sizes, "seq ~= 0 and seq <= #self.sizes") local cell = frontmost.cell() - cell[directions_rel[side].pos] = directions_rel[side].home - cell[directions_rel[side].dim] = self.sizes[seq] + cell[directions_rel[side].dim] = self.GRID[directions_rel[side].dim] / self.sizes[seq] + if hs.fnutils.contains({'left', 'up'}, side) then + cell[directions_rel[side].pos] = directions_rel[side].home() + else + cell[directions_rel[side].pos] = directions_rel[side].home() - cell[directions_rel[side].dim] + end + + cell = snap_to_grid(cell) self._setPosition(cell) return self end @@ -475,7 +483,7 @@ hs.fnutils.each(directions, -- setToUpSeq(seq), setToDownSeq, setToLeftSeq, set -- Query whether window is bound to `side` (is touching that side of the screen) function obj:boundQ(side) - expect_argument_to_be_in_table(side, directions) + expect.argument_to_be_in_table(side, directions) local cell = frontmost.cell() if side == 'up' then @@ -493,13 +501,49 @@ hs.fnutils.each(directions, -- upBoundQ(), downBoundQ, leftBoundQ, rightBoundQ obj[side ..'BoundQ'] = function(self) return self:boundQ(side) end end ) + +-- Query fullscreen sequence - 0 means out of sequence +function obj:seqFullQ() + local cell = frontmost.cell() + for i = 1,#self.fullScreenSizes do + if cell == self:seqFullCell(i) then + return i + end + end + return 0 +end + +-- hs.grid cell for fullscreen sequence `seq` +function obj:seqFullCell(seq) + local seq_factor = self.fullScreenSizes[seq] + + local cell, pnt, size + if seq_factor ~= 'c' then + size = hs.geometry.size( + self.GRID.w / seq_factor, + self.GRID.h / seq_factor + ) + pnt = hs.geometry.point( + (self.GRID.w - size.w) / 2, + (self.GRID.h - size.h) / 2 + ) + cell = hs.geometry(pnt, size) + else + cell = + obj._originalPositionStore[frontmost.window():id()] or self.GRID.cell() + cell.center = self.GRID.cell().center + end + return snap_to_grid(cell) +end + + -- Set window to cell function obj._setPosition(cell) - expect_truthy( + expect.truthy( type(cell) == 'table' and type(cell.type) == 'function' and cell:type() == 'rect', "type(cell) == 'table' and type(cell.type) == 'function' and cell:type() == 'rect'") - local win = hs.window.frontmostWindow() + local win = frontmost.window() hs.grid.set(win, cell, win:screen()) end From 7a600c7fbc4b029719df82c3d0652c15d4b57bbb Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Mon, 4 Jun 2018 18:56:04 +1000 Subject: [PATCH 17/41] bugfixes: handle repeated sizes in sizes and fullscreenSizes, 'c' sizes when there is nothing stored, and some other edge cases. --- MiroWindowsManager.spoon/init.lua | 213 ++++++++++++++++++++++-------- 1 file changed, 158 insertions(+), 55 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 9869d86..e41ac4d 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -190,6 +190,7 @@ obj.pushToNextScreen = false -- Window moves and their relationships local directions = { 'up', 'down', 'left', 'right' } +obj._directions = directions local directions_rel = { up = { opp = 'down', grow = 'taller', dim = 'h', pos = 'y', home = function() return 0 end }, down = { opp = 'up', grow = 'taller', dim = 'h', pos = 'y', home = function() return obj.GRID.h end }, @@ -199,6 +200,7 @@ local directions_rel = { obj._directions_rel = directions_rel -- Window growths and their relationships local growths = { 'taller', 'shorter', 'wider', 'thinner' } +obj._growths = growths local growths_rel = { taller = { opp = 'shorter', dim = 'h', pos = 'y', side = 'up', sticky_bound_fix = false }, shorter = { opp = 'taller', dim = 'h', pos = 'y', side = 'down', sticky_bound_fix = true }, @@ -211,11 +213,14 @@ obj._growths_rel = growths_rel obj._pressed = {} obj._press_timers = {} -hs.fnutils.each(hs.fnutils.concat(directions, growths), function(move) +obj._originalPositionStore = {} +hs.fnutils.each(hs.fnutils.concat(hs.fnutils.concat(directions, growths), {'fullscreen'}), function(move) obj._pressed[move] = false obj._press_timers[move] = hs.timer.doAfter(1, function() obj._pressed[move] = false end) + obj._originalPositionStore[move] = {} end) -obj._originalPositionStore = {} +obj._lastSeq = {} +obj._lastFullSeq = nil -- this is for you, reader, so you know we're going to use it (Lua doesn't care) local function register_press(direction) obj._pressed[direction] = true @@ -225,7 +230,7 @@ local function cancel_press(direction) obj._pressed[direction] = false obj._press_timers[direction]:stop() end -local function pressedQ(direction) +local function currentlyPressed(direction) return obj._pressed[direction] end @@ -264,9 +269,10 @@ end function obj:move(side) expect.argument_to_be_in_table(side, directions) - if self:boundQ(side) and not self.pushToNextScreen then + if self:currentlyBound(side) and not self.pushToNextScreen then logger.i("`self.pushToNextScreen` == false so not moving to ".. side .." screen.") else + logger.i('Moving '.. side) hs.grid['pushWindow'.. side:titleCase()](frontmost.window()) end return self @@ -295,20 +301,21 @@ function obj:grow(growth) expect.argument_to_be_in_table(growth, growths) register_press(growth) - if pressedQ(growths_rel[growth].opp) then - logger.i("Maximising ".. growths_rel[growth].dim .." since ".. - growths_rel[growth].opp .." still active.") + if currentlyPressed(growths_rel[growth].opp) then + logger.i("Maximising ".. growths_rel[growth].dim .." since '".. + growths_rel[growth].opp .."' still active.") return self:growFully(growth) -- full width/height else local sticky_bound_fix = growths_rel[growth].sticky_bound_fix and - self:boundQ(growths_rel[growth].side) and - not self:boundQ(directions_rel[growths_rel[growth].side].opp) + self:currentlyBound(growths_rel[growth].side) and + not self:currentlyBound(directions_rel[growths_rel[growth].side].opp) local prev_window_ani if sticky_bound_fix then prev_window_ani = hs.window.animationDuration hs.window.animationDuration = 0 end + logger.i('Gowing '.. growth) hs.grid['resizeWindow'.. growth:titleCase()](frontmost.window()) if sticky_bound_fix then @@ -370,19 +377,31 @@ function obj:go(move) if move == 'fullscreen' then return self:goFullscreen() end expect.argument_to_be_in_table(move, directions) + expect.truthy(not hs.fnutils.every(self.sizes, + function(x) + return x == 'c' + end), "hunting for something other than 'c' in self.sizes") register_press(move) - if pressedQ(directions_rel[move].opp) then + if currentlyPressed(directions_rel[move].opp) then -- if still keydown moving the in the opposite direction, go full width/height logger.i("Maximising ".. directions_rel[move].dim .." since ".. directions_rel[move].opp .." still active.") self:growFully(directions_rel[move].grow) -- full width/height else local cell = frontmost.cell() - local seq = self:seqQ(move) -- current sequence index or 0 if out of sequence - local log_info = "We're at ".. move .." sequence ".. tostring(seq) .." (".. cell.string ..")" - seq = seq % #self.sizes -- if #self.sizes then 0 - log_info = log_info .. ", so moving to sequence " .. tostring(seq + 1) + local seq = self:currentSeq(move) -- current sequence index or 0 if out of sequence + + local log_info = "We're at ".. move .." sequence ".. tostring(seq) .." (".. cell.string .."), so" + + if hs.fnutils.contains(self.sizes, 'c') and seq == 0 then + -- We're out of the sequence, so store the current window position + obj._originalPositionStore[move][frontmost.window():id()] = frontmost.cell() + log_info = log_info .." remembering position then" + end + + seq = seq % #self.sizes -- if at end of #self.sizes then wrap to 0 + log_info = log_info .. " moving to sequence " .. tostring(seq + 1) .." (size: ".. tostring(self.sizes[seq + 1]) ..")" logger.i(log_info) cell = self:setToSeq(move, seq + 1) @@ -404,22 +423,26 @@ hs.fnutils.each(directions, -- goUp(), goDown, goLeft, goRight --- Returns: --- * The MiroWindowsManager object function obj:goFullscreen() + expect.truthy(not hs.fnutils.every(self.fullScreenSizes, + function(x) + return x == 'c' + end), "hunting for something other than 'c' in self.fullScreenSizes") + local cell - local seq = self:seqFullQ() -- current sequence index or 0 if out of sequence + local seq = self:currentFullSeq() -- current sequence index or 0 if out of sequence local log_info = "We're at fullscreen sequence ".. tostring(seq) .." (".. frontmost.cell().string .."), so" if hs.fnutils.contains(self.fullScreenSizes, 'c') and seq == 0 then -- We're out of the sequence, so store the current window position - obj._originalPositionStore[frontmost.window():id()] = frontmost.cell() + obj._originalPositionStore['fullscreen'][frontmost.window():id()] = frontmost.cell() log_info = log_info .." remembering position then" end seq = seq % #self.fullScreenSizes -- if #self.fullScreenSizes then 0 - log_info = log_info .. " moving to sequence " .. tostring(seq + 1) + log_info = log_info .. " moving to sequence " .. tostring(seq + 1) .." (size: ".. tostring(self.fullScreenSizes[seq + 1]) ..")" logger.i(log_info) - cell = self:seqFullCell(seq + 1) -- next in sequence - self._setPosition(cell) + cell = self:setToFullSeq(seq + 1) -- next in sequence return self end @@ -428,7 +451,7 @@ end -- ## Public undocumented -- Query whether window is centered -function obj:centeredQ() +function obj:currentlyCentered() local cell = frontmost.cell() return cell.w + 2 * cell.x == self.GRID.w and cell.h + 2 * cell.y == self.GRID.h @@ -439,24 +462,39 @@ local function snap_to_grid(cell) return cell end + +-- ### Side methods (up, down, left, right) -- Query sequence for `side` - 0 means out of sequence -function obj:seqQ(side) +function obj:currentSeq(side) expect.argument_to_be_in_table(side, directions) - if self:boundQ(side) then + if self:currentlyBound(side) then local dim = directions_rel[side].dim local width = frontmost.cell()[dim] local relative_size = self.GRID[dim] / width - return hs.fnutils.indexOf(self.sizes, relative_size) or 0 + + local last_matched_seq = + self._lastSeq[side] and -- we've recorded a last seq, and + self.sizes[self._lastSeq[side]] and -- it's a valid index to sizes + self._lastSeq[side] + local last_matched_seq_matches_frontmost = + last_matched_seq and (self.sizes[last_matched_seq] == relative_size) + + -- cleanup + if not last_matched_seq_matches_frontmost then self._lastSeq[side] = nil end + + local seq = + last_matched_seq_matches_frontmost and last_matched_seq or -- return it + -- if another from sizes matches, return it + hs.fnutils.indexOf(self.sizes, relative_size) or + -- else 0 + 0 + + return seq else return 0 end end --- upSeqQ(seq), downSeqQ, leftSeqQ, rightSeqQ -hs.fnutils.each(directions, - function(side) - obj[side ..'SeqQ'] = function(self) return self:seqQ(side) end - end ) -- Set sequence for `side` function obj:setToSeq(side, seq) @@ -464,25 +502,48 @@ function obj:setToSeq(side, seq) expect.truthy(type(seq) == 'number', "type(seq) == 'number'") expect.truthy(seq ~= 0 and seq <= #self.sizes, "seq ~= 0 and seq <= #self.sizes") - local cell = frontmost.cell() - cell[directions_rel[side].dim] = self.GRID[directions_rel[side].dim] / self.sizes[seq] + local cell = self:seqCell(side, seq) + + self._setPosition(cell) + self._lastSeq[side] = seq + return self +end + +-- hs.grid cell for sequence `seq` +function obj:seqCell(side, seq) + local cell + local seq_factor = self.sizes[seq] + + while seq_factor == 'c' and + obj._originalPositionStore[side][frontmost.window():id()] == nil do + logger.i("... but nothing stored, so bouncing to the next position.") + + seq = seq + 1 + seq_factor = self.sizes[seq] + end + + if seq_factor == 'c' then + cell = + obj._originalPositionStore[side][frontmost.window():id()] + logger.i('Restoring stored window size ('.. cell.string ..')') + else + cell = frontmost.cell() + cell[directions_rel[side].dim] = + self.GRID[directions_rel[side].dim] / self.sizes[seq] + end + if hs.fnutils.contains({'left', 'up'}, side) then cell[directions_rel[side].pos] = directions_rel[side].home() else - cell[directions_rel[side].pos] = directions_rel[side].home() - cell[directions_rel[side].dim] + cell[directions_rel[side].pos] = + directions_rel[side].home() - cell[directions_rel[side].dim] end - cell = snap_to_grid(cell) - self._setPosition(cell) - return self + return snap_to_grid(cell) end -hs.fnutils.each(directions, -- setToUpSeq(seq), setToDownSeq, setToLeftSeq, setToRightSeq - function(side) - obj['setTo'.. side:titleCase() ..'Seq'] = function(self, seq) return self:seq(side, seq) end - end ) -- Query whether window is bound to `side` (is touching that side of the screen) -function obj:boundQ(side) +function obj:currentlyBound(side) expect.argument_to_be_in_table(side, directions) local cell = frontmost.cell() @@ -496,29 +557,71 @@ function obj:boundQ(side) return cell.x + cell.w == self.GRID.w end end -hs.fnutils.each(directions, -- upBoundQ(), downBoundQ, leftBoundQ, rightBoundQ - on edge? +hs.fnutils.each(directions, -- currentlyUpBound(), currentlyDownBound, currentlyLeftBound, currentlyRightBound - on edge? function(side) - obj[side ..'BoundQ'] = function(self) return self:boundQ(side) end + obj['currently'.. side:titleCase() ..'Bound'] = function(self) return self:currentlyBound(side) end end ) +-- ### Fullscreen methods -- Query fullscreen sequence - 0 means out of sequence -function obj:seqFullQ() +function obj:currentFullSeq() local cell = frontmost.cell() - for i = 1,#self.fullScreenSizes do - if cell == self:seqFullCell(i) then - return i + + local last_matched_seq = + self._lastFullSeq and -- we've recorded a last seq, and + self.fullScreenSizes[self._lastFullSeq] and -- it's a valid index to fullScreenSizes + self._lastFullSeq + local last_matched_seq_matches_frontmost = + last_matched_seq and (cell == self:seqFullCell(last_matched_seq)) + + -- cleanup + if not last_matched_seq_matches_frontmost then self._lastFullSeq = nil end + + local seq = + last_matched_seq_matches_frontmost and last_matched_seq or + nil + + if seq then + return seq + else + for i = 1,#self.fullScreenSizes do + if cell == self:seqFullCell(i) then + seq = i + if obj._lastFullSeq and i == obj._lastFullSeq then return i end + end end + return seq or 0 end - return 0 + +end + +-- Set fullscreen sequence +function obj:setToFullSeq(seq) + local cell = self:seqFullCell(seq) + + self._setPosition(cell) + self._lastFullSeq = seq + return self end -- hs.grid cell for fullscreen sequence `seq` function obj:seqFullCell(seq) local seq_factor = self.fullScreenSizes[seq] + while seq_factor == 'c' and + obj._originalPositionStore['fullscreen'][frontmost.window():id()] == nil do + logger.i("... but nothing stored, so bouncing to the next position.") + + seq = seq + 1 + seq_factor = self.fullScreenSizes[seq] + end local cell, pnt, size - if seq_factor ~= 'c' then + if seq_factor == 'c' then + cell = + obj._originalPositionStore['fullscreen'][frontmost.window():id()] + cell.center = self.GRID.cell().center + else size = hs.geometry.size( self.GRID.w / seq_factor, self.GRID.h / seq_factor @@ -528,10 +631,6 @@ function obj:seqFullCell(seq) (self.GRID.h - size.h) / 2 ) cell = hs.geometry(pnt, size) - else - cell = - obj._originalPositionStore[frontmost.window():id()] or self.GRID.cell() - cell.center = self.GRID.cell().center end return snap_to_grid(cell) end @@ -590,7 +689,8 @@ function obj:bindHotkeys(mapping) function(direction) -- go if mapping[direction] then - self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind(mapping[direction][1], mapping[direction][2], + self.hotkeys[#self.hotkeys + 1] = + hs.hotkey.bind(mapping[direction][1], mapping[direction][2], function() self:go(direction) end, function() cancel_press(direction) end) end @@ -598,7 +698,8 @@ function obj:bindHotkeys(mapping) -- move local move_command = 'move'.. direction:titleCase() if mapping[move_command] then - self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind(mapping[move_command][1], mapping[move_command][2], + self.hotkeys[#self.hotkeys + 1] = + hs.hotkey.bind(mapping[move_command][1], mapping[move_command][2], function() self:move(direction) end) end end) @@ -607,14 +708,16 @@ function obj:bindHotkeys(mapping) function(sense) -- grow if mapping[sense] then - self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind(mapping[sense][1], mapping[sense][2], + self.hotkeys[#self.hotkeys + 1] = + hs.hotkey.bind(mapping[sense][1], mapping[sense][2], function() self:grow(sense) end, function() cancel_press(sense) end) end end) if mapping.fullscreen then - self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind(mapping.fullscreen[1], mapping.fullscreen[2], + self.hotkeys[#self.hotkeys + 1] = + hs.hotkey.bind(mapping.fullscreen[1], mapping.fullscreen[2], function() self:goFullscreen() end) end From f057a980771e5e9d4a14d5b51577c3914afb3d7d Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Wed, 17 Oct 2018 21:19:36 -0700 Subject: [PATCH 18/41] reduce luacheck noise --- MiroWindowsManager.spoon/.luacheckrc | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 MiroWindowsManager.spoon/.luacheckrc diff --git a/MiroWindowsManager.spoon/.luacheckrc b/MiroWindowsManager.spoon/.luacheckrc new file mode 100644 index 0000000..ef29b07 --- /dev/null +++ b/MiroWindowsManager.spoon/.luacheckrc @@ -0,0 +1,9 @@ +stds.hs = { + globals = { + hs = { + other_fields = true, + }, + }, +} +std = 'max+hs' +ignore = { '614' } From c05116e6769afa83052cb33d316942e44b039793 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Wed, 17 Oct 2018 21:20:30 -0700 Subject: [PATCH 19/41] Move GRID update meta magic into its own file --- MiroWindowsManager.spoon/extend_GRID.lua | 65 ++++++++++++++++++++++++ MiroWindowsManager.spoon/init.lua | 56 +------------------- 2 files changed, 67 insertions(+), 54 deletions(-) create mode 100644 MiroWindowsManager.spoon/extend_GRID.lua diff --git a/MiroWindowsManager.spoon/extend_GRID.lua b/MiroWindowsManager.spoon/extend_GRID.lua new file mode 100644 index 0000000..8ebbf32 --- /dev/null +++ b/MiroWindowsManager.spoon/extend_GRID.lua @@ -0,0 +1,65 @@ +-- Comment: Lots of work here to save users a little work. Previous versions +-- required users to call MiroWindowsManager:start() every time they changed +-- GRID. The metatable work here watches for those changes and does the work +-- :start() would have done. +-- +-- usage: +-- require('extend_GRID').extend(obj, logger) + +local M = {} + +function M.extend(obj, logger) + -- Ensure changes to GRID update hs.grid + -- Prevent obj.GRID from being replaced + local _grid_store = {} -- we'll store the real GRID here + setmetatable(obj, { + __index = function(t,k) -- if code reads obj, use this function (eg. `obj.sizes`) + -- if code accesses obj.GRID, return the real GRID we created above, + -- otherwise access obj as normal + if k == 'GRID' then return _grid_store else return rawget(t,k) end end, + __newindex = + function(t,k,v) -- if code writes to obj, use this function (eg. `obj.sizes = {2, 3}`) + if k == 'GRID' then + -- if code assigns to obj.GRID (`obj.GRID = ...`), don't overwrite our + -- real GRID, otherwise access obj as normal + assert(type(v)=='table', rawget(obj,'name')..".GRID must be a table.") + -- assign the assigned table's content to our real GRID table + for kk,vv in pairs(v) do obj.GRID[kk] = vv end + else + rawset(t,k,v) + end + end, + }) + -- Update hs.grid after relevant changes to obj.GRID + local _grid_value_store = {} -- we store the real GRID *values* here + setmetatable(_grid_store, { + __newindex = + function(_,k,v) -- if code assigns to our real GRID… + rawset(_grid_value_store,k,v) -- do the assignment, then… + if hs.fnutils.contains({'w','h'}, k) then + -- update hs.grid with hs.grid.setGrid, so the user doesn't have to + hs.grid.setGrid(tostring(_grid_value_store.w or 0) .. 'x' .. + tostring(_grid_value_store.h or 0)) + logger.i("Updated hs.grid to ".. hs.inspect(_grid_value_store)) + elseif k == 'margins' then + -- update hs.grid with hs.grid.setMargins, so the user doesn't have to + hs.grid.setMargins(v) + logger.i("Updated hs.grid to ".. hs.inspect(_grid_value_store)) + elseif hs.fnutils.contains({'MARGINX','MARGINY'}, k) then + -- LEGACY + if k == 'MARGINX' then + obj.GRID.margins = hs.geometry(tostring(v) ..'x'.. + tostring(rawget(_grid_value_store, 'margins').h or 0)) + elseif k == 'MARGINY' then + obj.GRID.margins = + hs.geometry(tostring(rawget(_grid_value_store, 'margins').w or 0) ..'x'.. + tostring(v)) + end + logger.i("Updated hs.grid to ".. hs.inspect(_grid_value_store)) + end + end, + __index = function(_,k) return rawget(_grid_value_store, k) end, + }) +end + +return M diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index e41ac4d..947f0a9 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -111,60 +111,8 @@ obj.fullScreenSizes = {1, 4/3, 2} -- required users to call MiroWindowsManager:start() every time they changed -- GRID. The metatable work here watches for those changes and does the work -- :start() would have done. --- --- Ensure changes to GRID update hs.grid --- Prevent obj.GRID from being replaced -local _grid_store_yeeV5hiG = {} -- we'll store the real GRID here -setmetatable(obj, { - __index = function(t,k) -- if code reads obj, use this function (eg. `obj.sizes`) - -- if code accesses obj.GRID, return the real GRID we created above, - -- otherwise access obj as normal - if k == 'GRID' then return _grid_store_yeeV5hiG else return rawget(t,k) end end, - __newindex = - function(t,k,v) -- if code writes to obj, use this function (eg. `obj.sizes = {2, 3}`) - if k == 'GRID' then - -- if code assigns to obj.GRID (`obj.GRID = ...`), don't overwrite our - -- real GRID, otherwise access obj as normal - assert(type(v)=='table',rawget(obj,'name')..".GRID must be a table.") - -- assign the assigned table's content to our real GRID table - for kk,vv in pairs(v) do obj.GRID[kk] = vv end - else - rawset(t,k,v) - end - end, -}) --- Update hs.grid after relevant changes to obj.GRID -local _grid_value_store_Oocaeyim = {} -- we store the real GRID *values* here -local m = { - __newindex = - function(t,k,v) -- if code assigns to our real GRID… - rawset(_grid_value_store_Oocaeyim,k,v) -- do the assignment, then… - if hs.fnutils.contains({'w','h'}, k) then - -- update hs.grid with hs.grid.setGrid, so the user doesn't have to - hs.grid.setGrid(tostring(_grid_value_store_Oocaeyim.w or 0) .. 'x' .. - tostring(_grid_value_store_Oocaeyim.h or 0)) - logger.i("Updated hs.grid to ".. hs.inspect(_grid_value_store_Oocaeyim)) - elseif k == 'margins' then - -- update hs.grid with hs.grid.setMargins, so the user doesn't have to - hs.grid.setMargins(v) - logger.i("Updated hs.grid to ".. hs.inspect(_grid_value_store_Oocaeyim)) - elseif hs.fnutils.contains({'MARGINX','MARGINY'}, k) then - -- LEGACY - if k == 'MARGINX' then - obj.GRID.margins = hs.geometry(tostring(v) ..'x'.. - tostring(rawget(_grid_value_store_Oocaeyim, 'margins').h or 0)) - elseif k == 'MARGINY' then - obj.GRID.margins = - hs.geometry(tostring(rawget(_grid_value_store_Oocaeyim, 'margins').w or 0) ..'x'.. - tostring(v)) - end - logger.i("Updated hs.grid to ".. hs.inspect(_grid_value_store_Oocaeyim)) - end - end, - __index = - function(t,k) return rawget(_grid_value_store_Oocaeyim, k) end, -} -setmetatable(_grid_store_yeeV5hiG, m) +package.path = package.path..";Spoons/".. ... ..".spoon/?.lua" +require('extend_GRID').extend(obj, logger) --- MiroWindowsManager.GRID --- Variable From ebc35bd8366b185e87688c5e578024f6aa270844 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Wed, 17 Oct 2018 21:23:25 -0700 Subject: [PATCH 20/41] Pressing both directions while moving should also maximise (as it does going or growing) --- MiroWindowsManager.spoon/init.lua | 39 ++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 947f0a9..cb26d22 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -217,11 +217,19 @@ end function obj:move(side) expect.argument_to_be_in_table(side, directions) - if self:currentlyBound(side) and not self.pushToNextScreen then - logger.i("`self.pushToNextScreen` == false so not moving to ".. side .." screen.") + register_press(side) + if currentlyPressed(directions_rel[side].opp) then + -- if still keydown moving the in the opposite direction, go full width/height + logger.i("Maximising ".. directions_rel[side].dim .." since ".. + directions_rel[side].opp .." still active.") + self:growFully(directions_rel[side].grow) -- full width/height else - logger.i('Moving '.. side) - hs.grid['pushWindow'.. side:titleCase()](frontmost.window()) + if self:currentlyBound(side) and not self.pushToNextScreen then + logger.i("`self.pushToNextScreen` == false so not moving to ".. side .." screen.") + else + logger.i('Moving '.. side) + hs.grid['pushWindow'.. side:titleCase()](frontmost.window()) + end end return self end @@ -249,7 +257,7 @@ function obj:grow(growth) expect.argument_to_be_in_table(growth, growths) register_press(growth) - if currentlyPressed(growths_rel[growth].opp) then + if currentlyPressed(growths_rel[growth].opp) then logger.i("Maximising ".. growths_rel[growth].dim .." since '".. growths_rel[growth].opp .."' still active.") return self:growFully(growth) -- full width/height @@ -331,7 +339,7 @@ function obj:go(move) end), "hunting for something other than 'c' in self.sizes") register_press(move) - if currentlyPressed(directions_rel[move].opp) then + if currentlyPressed(directions_rel[move].opp) then -- if still keydown moving the in the opposite direction, go full width/height logger.i("Maximising ".. directions_rel[move].dim .." since ".. directions_rel[move].opp .." still active.") @@ -349,10 +357,11 @@ function obj:go(move) end seq = seq % #self.sizes -- if at end of #self.sizes then wrap to 0 - log_info = log_info .. " moving to sequence " .. tostring(seq + 1) .." (size: ".. tostring(self.sizes[seq + 1]) ..")" + log_info = + log_info .. " moving to sequence " .. tostring(seq + 1) .." (size: ".. tostring(self.sizes[seq + 1]) ..")" logger.i(log_info) - cell = self:setToSeq(move, seq + 1) + self:setToSeq(move, seq + 1) end return self end @@ -376,7 +385,6 @@ function obj:goFullscreen() return x == 'c' end), "hunting for something other than 'c' in self.fullScreenSizes") - local cell local seq = self:currentFullSeq() -- current sequence index or 0 if out of sequence local log_info = "We're at fullscreen sequence ".. tostring(seq) .." (".. frontmost.cell().string .."), so" @@ -387,10 +395,11 @@ function obj:goFullscreen() end seq = seq % #self.fullScreenSizes -- if #self.fullScreenSizes then 0 - log_info = log_info .. " moving to sequence " .. tostring(seq + 1) .." (size: ".. tostring(self.fullScreenSizes[seq + 1]) ..")" + log_info = + log_info .. " moving to sequence " .. tostring(seq + 1) .." (size: ".. tostring(self.fullScreenSizes[seq + 1]) ..")" logger.i(log_info) - cell = self:setToFullSeq(seq + 1) -- next in sequence + self:setToFullSeq(seq + 1) -- next in sequence return self end @@ -505,10 +514,12 @@ function obj:currentlyBound(side) return cell.x + cell.w == self.GRID.w end end -hs.fnutils.each(directions, -- currentlyUpBound(), currentlyDownBound, currentlyLeftBound, currentlyRightBound - on edge? +hs.fnutils.each( + directions, -- currentlyUpBound(), currentlyDownBound, currentlyLeftBound, currentlyRightBound - on edge? function(side) obj['currently'.. side:titleCase() ..'Bound'] = function(self) return self:currentlyBound(side) end - end ) + end +) -- ### Fullscreen methods @@ -516,7 +527,7 @@ hs.fnutils.each(directions, -- currentlyUpBound(), currentlyDownBound, currentl function obj:currentFullSeq() local cell = frontmost.cell() - local last_matched_seq = + local last_matched_seq = self._lastFullSeq and -- we've recorded a last seq, and self.fullScreenSizes[self._lastFullSeq] and -- it's a valid index to fullScreenSizes self._lastFullSeq From 58bc88860afe3778a412c9372b40769a66ed4f1c Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Wed, 17 Oct 2018 21:23:56 -0700 Subject: [PATCH 21/41] Add Center method --- MiroWindowsManager.spoon/init.lua | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index cb26d22..e102784 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -404,6 +404,19 @@ function obj:goFullscreen() return self end +--- MiroWindowsManager:center() +--- Method +--- Center +--- +--- Parameters: +--- * None. +--- +--- Returns: +--- * The MiroWindowsManager object +function obj:goCenter() + return self:setToCenter() +end + -- ## Public undocumented @@ -595,6 +608,16 @@ function obj:seqFullCell(seq) end +-- Center window +function obj:setToCenter() + local cell = frontmost.cell() + cell.center = self.GRID.cell().center + + self._setPosition(cell) + return self +end + + -- Set window to cell function obj._setPosition(cell) expect.truthy( @@ -631,6 +654,7 @@ obj.hotkeys = {} --- left = {mods, "left"}, --- right = {mods, "right"}, --- fullscreen = {mods, "f"}, +--- center = {mods, "c"}, --- moveUp = {{'⌃','⌥'}, "up"}, --- moveDown = {{'⌃','⌥'}, "down"}, --- moveLeft = {{'⌃','⌥'}, "left"}, @@ -680,6 +704,12 @@ function obj:bindHotkeys(mapping) function() self:goFullscreen() end) end + if mapping.center then + self.hotkeys[#self.hotkeys + 1] = + hs.hotkey.bind(mapping.center[1], mapping.center[2], + function() self:goCenter() end) + end + end --- MiroWindowsManager:init() From c736d7d85e696449b0c29eae9c951bc51b9b5331 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Wed, 24 Oct 2018 09:17:00 -0700 Subject: [PATCH 22/41] `cancel_press` as `releasedfn` (keyup) for moves (prevents `growFully` on direction change). --- MiroWindowsManager.spoon/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index e102784..c5f926a 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -683,7 +683,8 @@ function obj:bindHotkeys(mapping) if mapping[move_command] then self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind(mapping[move_command][1], mapping[move_command][2], - function() self:move(direction) end) + function() self:move(direction) end, + function() cancel_press(direction) end) end end) From 6c52e3a2c343319a510144440ad2b4e5a5bcb224 Mon Sep 17 00:00:00 2001 From: Miro Mannino Date: Fri, 26 Oct 2018 12:00:13 +0400 Subject: [PATCH 23/41] first cleanup and refactorings --- MiroWindowsManager.spoon/docs.json | 571 +++++++++++++++++++++++++++++ MiroWindowsManager.spoon/init.lua | 493 ++++++++----------------- 2 files changed, 715 insertions(+), 349 deletions(-) diff --git a/MiroWindowsManager.spoon/docs.json b/MiroWindowsManager.spoon/docs.json index c44dc44..c0bbc6c 100644 --- a/MiroWindowsManager.spoon/docs.json +++ b/MiroWindowsManager.spoon/docs.json @@ -1,3 +1,574 @@ [ + { + "Constant" : [ + ], + "submodules" : [ + + ], + "Function" : [ + + ], + "Variable" : [ + { + "def" : "MiroWindowsManager.sizes", + "stripped_doc" : [ + "The sizes that the window can have. ", + "The sizes are expressed as dividend of the entire screen's size. ", + "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total", + "screen's size. ", + "Ensuring that these numbers all divide both dimensions of", + "MiroWindowsManager.GRID to give integers makes everything work better." + ], + "desc" : "The sizes that the window can have.", + "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total\nscreen's size. \nEnsuring that these numbers all divide both dimensions of\nMiroWindowsManager.GRID to give integers makes everything work better.", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.sizes", + "type" : "Variable", + "returns" : [ + + ], + "name" : "sizes", + "parameters" : [ + + ] + }, + { + "def" : "MiroWindowsManager.fullScreenSizes", + "stripped_doc" : [ + "The sizes that the window can have in full-screen. ", + "The sizes are expressed as dividend of the entire screen's size. ", + "For example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4", + "and 1\/2 of the total screen's size. ", + "Ensuring that these numbers all divide both dimensions of", + "MiroWindowsManager.GRID to give integers makes everything work better. ", + "Special: Use 'c' for the original size and shape of the window before", + "starting to move it, but centered." + ], + "desc" : "The sizes that the window can have in full-screen.", + "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4\nand 1\/2 of the total screen's size. \nEnsuring that these numbers all divide both dimensions of\nMiroWindowsManager.GRID to give integers makes everything work better. \nSpecial: Use 'c' for the original size and shape of the window before\nstarting to move it, but centered.", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.fullScreenSizes", + "type" : "Variable", + "returns" : [ + + ], + "name" : "fullScreenSizes", + "parameters" : [ + + ] + }, + { + "def" : "MiroWindowsManager.GRID", + "stripped_doc" : [ + "The screen's grid size. ", + "Ensuring that the numbers in MiroWindowsManager.sizes and", + "MiroWindowsManager.fullScreenSizes divide these numbers to give integers", + "makes everything work better." + ], + "desc" : "The screen's grid size.", + "doc" : "The screen's grid size. \nEnsuring that the numbers in MiroWindowsManager.sizes and\nMiroWindowsManager.fullScreenSizes divide these numbers to give integers\nmakes everything work better.", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.GRID", + "type" : "Variable", + "returns" : [ + + ], + "name" : "GRID", + "parameters" : [ + + ] + }, + { + "def" : "MiroWindowsManager.moveToNextScreen", + "stripped_doc" : [ + "Boolean value to decide wether or not to move the window on the next screen", + "if the window is moved the screen edge." + ], + "desc" : "Boolean value to decide wether or not to move the window on the next screen", + "doc" : "Boolean value to decide wether or not to move the window on the next screen\nif the window is moved the screen edge.", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.moveToNextScreen", + "type" : "Variable", + "returns" : [ + + ], + "name" : "moveToNextScreen", + "parameters" : [ + + ] + } + ], + "stripped_doc" : [ + + ], + "Deprecated" : [ + + ], + "type" : "Module", + "desc" : "With this Spoon you will be able to move the window in halves and in", + "Constructor" : [ + + ], + "Field" : [ + + ], + "Method" : [ + { + "def" : "MiroWindowsManager:move(side)", + "stripped_doc" : [ + "Move the frontmost window up, down, left, right. ", + "" + ], + "desc" : "Move the frontmost window up, down, left, right.", + "doc" : "Move the frontmost window up, down, left, right. \n\nParameters:\n * side - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:move(side)", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "name" : "move", + "parameters" : [ + " * side - 'up', 'down', 'left', or 'right'", + "" + ] + }, + { + "def" : "MiroWindowsManager:growFully(growth)", + "stripped_doc" : [ + "Grow the frontmost window to full width \/ height taller, wider. ", + "" + ], + "desc" : "Grow the frontmost window to full width \/ height taller, wider.", + "doc" : "Grow the frontmost window to full width \/ height taller, wider. \n\nParameters:\n * growth - 'taller', or 'wider'\n\nReturns:\n * The MiroWindowsManager object", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:growFully(growth)", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "name" : "growFully", + "parameters" : [ + " * growth - 'taller', or 'wider'", + "" + ] + }, + { + "def" : "MiroWindowsManager:go(move)", + "stripped_doc" : [ + "Move to screen edge, or cycle to next horizontal or vertical size if already there. ", + "Tap both directions to go full width \/ height. ", + "" + ], + "desc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there.", + "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \n\nParameters:\n * move - 'up', 'down', 'left', 'right'\n\nReturns:\n * The MiroWindowsManager object", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:go(move)", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "name" : "go", + "parameters" : [ + " * move - 'up', 'down', 'left', 'right'", + "" + ] + }, + { + "def" : "MiroWindowsManager:fullscreen()", + "stripped_doc" : [ + "Fullscreen, or cycle to next fullscreen option", + "" + ], + "desc" : "Fullscreen, or cycle to next fullscreen option", + "doc" : "Fullscreen, or cycle to next fullscreen option\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:fullscreen()", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "name" : "fullscreen", + "parameters" : [ + " * None.", + "" + ] + }, + { + "def" : "MiroWindowsManager:center()", + "stripped_doc" : [ + "Center", + "" + ], + "desc" : "Center", + "doc" : "Center\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:center()", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "name" : "center", + "parameters" : [ + " * None.", + "" + ] + }, + { + "def" : "MiroWindowsManager:bindHotkeys()", + "stripped_doc" : [ + "Binds hotkeys for Miro's Windows Manager", + "" + ], + "desc" : "Binds hotkeys for Miro's Windows Manager", + "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n\nA configuration example can be:\n``` lua\nlocal mods = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {mods, \"up\"},\n down = {mods, \"down\"},\n left = {mods, \"left\"},\n right = {mods, \"right\"},\n fullscreen = {mods, \"f\"},\n center = {mods, \"c\"},\n move = {mods, \"v\"},\n resize = {mods, \"d\"}\n})\n```", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:bindHotkeys()", + "type" : "Method", + "returns" : [ + + ], + "name" : "bindHotkeys", + "parameters" : [ + " * mapping - A table containing hotkey details for the following items:", + " * left: for the left action (usually `{hyper, \"left\"}`)", + " * right: for the right action (usually `{hyper, \"right\"}`)", + " * up: for the up action (usually {hyper, \"up\"})", + " * down: for the down action (usually `{hyper, \"down\"}`)", + " * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)", + "", + "A configuration example can be:", + "``` lua", + "local mods = {\"ctrl\", \"alt\", \"cmd\"}", + "spoon.MiroWindowsManager:bindHotkeys({", + " up = {mods, \"up\"},", + " down = {mods, \"down\"},", + " left = {mods, \"left\"},", + " right = {mods, \"right\"},", + " fullscreen = {mods, \"f\"},", + " center = {mods, \"c\"},", + " move = {mods, \"v\"},", + " resize = {mods, \"d\"}", + "})", + "```" + ] + }, + { + "def" : "MiroWindowsManager:init()", + "stripped_doc" : [ + + ], + "desc" : "UNKNOWN DESC", + "doc" : "", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:init()", + "type" : "Method", + "returns" : [ + + ], + "name" : "init", + "parameters" : [ + + ] + } + ], + "Command" : [ + + ], + "doc" : "With this Spoon you will be able to move the window in halves and in\ncorners using your keyboard and mainly using arrows. You would also be able\nto resize them by thirds, quarters, or halves. \nOfficial homepage for more info and documentation:\n[https:\/\/github.com\/miromannino\/miro-windows-manager](https:\/\/github.com\/miromannino\/miro-windows-manager)\n\nNOTE: This Spoon sets `hs.grid` globals with `hs.grid.setGrid()`,\n`hs.grid.MARGINX`, and `hs.grid.MARGINY`. Changing MiroWindowsManager.GRID\nwill change these globals.\n\nDownload:\nhttps:\/\/github.com\/miromannino\/miro-windows-manager\/raw\/master\/MiroWindowsManager.spoon.zip", + "items" : [ + { + "def" : "MiroWindowsManager.GRID", + "stripped_doc" : [ + "The screen's grid size. ", + "Ensuring that the numbers in MiroWindowsManager.sizes and", + "MiroWindowsManager.fullScreenSizes divide these numbers to give integers", + "makes everything work better." + ], + "desc" : "The screen's grid size.", + "doc" : "The screen's grid size. \nEnsuring that the numbers in MiroWindowsManager.sizes and\nMiroWindowsManager.fullScreenSizes divide these numbers to give integers\nmakes everything work better.", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.GRID", + "type" : "Variable", + "returns" : [ + + ], + "name" : "GRID", + "parameters" : [ + + ] + }, + { + "def" : "MiroWindowsManager.fullScreenSizes", + "stripped_doc" : [ + "The sizes that the window can have in full-screen. ", + "The sizes are expressed as dividend of the entire screen's size. ", + "For example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4", + "and 1\/2 of the total screen's size. ", + "Ensuring that these numbers all divide both dimensions of", + "MiroWindowsManager.GRID to give integers makes everything work better. ", + "Special: Use 'c' for the original size and shape of the window before", + "starting to move it, but centered." + ], + "desc" : "The sizes that the window can have in full-screen.", + "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4\nand 1\/2 of the total screen's size. \nEnsuring that these numbers all divide both dimensions of\nMiroWindowsManager.GRID to give integers makes everything work better. \nSpecial: Use 'c' for the original size and shape of the window before\nstarting to move it, but centered.", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.fullScreenSizes", + "type" : "Variable", + "returns" : [ + + ], + "name" : "fullScreenSizes", + "parameters" : [ + + ] + }, + { + "def" : "MiroWindowsManager.moveToNextScreen", + "stripped_doc" : [ + "Boolean value to decide wether or not to move the window on the next screen", + "if the window is moved the screen edge." + ], + "desc" : "Boolean value to decide wether or not to move the window on the next screen", + "doc" : "Boolean value to decide wether or not to move the window on the next screen\nif the window is moved the screen edge.", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.moveToNextScreen", + "type" : "Variable", + "returns" : [ + + ], + "name" : "moveToNextScreen", + "parameters" : [ + + ] + }, + { + "def" : "MiroWindowsManager.sizes", + "stripped_doc" : [ + "The sizes that the window can have. ", + "The sizes are expressed as dividend of the entire screen's size. ", + "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total", + "screen's size. ", + "Ensuring that these numbers all divide both dimensions of", + "MiroWindowsManager.GRID to give integers makes everything work better." + ], + "desc" : "The sizes that the window can have.", + "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total\nscreen's size. \nEnsuring that these numbers all divide both dimensions of\nMiroWindowsManager.GRID to give integers makes everything work better.", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.sizes", + "type" : "Variable", + "returns" : [ + + ], + "name" : "sizes", + "parameters" : [ + + ] + }, + { + "def" : "MiroWindowsManager:bindHotkeys()", + "stripped_doc" : [ + "Binds hotkeys for Miro's Windows Manager", + "" + ], + "desc" : "Binds hotkeys for Miro's Windows Manager", + "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n\nA configuration example can be:\n``` lua\nlocal mods = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {mods, \"up\"},\n down = {mods, \"down\"},\n left = {mods, \"left\"},\n right = {mods, \"right\"},\n fullscreen = {mods, \"f\"},\n center = {mods, \"c\"},\n move = {mods, \"v\"},\n resize = {mods, \"d\"}\n})\n```", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:bindHotkeys()", + "type" : "Method", + "returns" : [ + + ], + "name" : "bindHotkeys", + "parameters" : [ + " * mapping - A table containing hotkey details for the following items:", + " * left: for the left action (usually `{hyper, \"left\"}`)", + " * right: for the right action (usually `{hyper, \"right\"}`)", + " * up: for the up action (usually {hyper, \"up\"})", + " * down: for the down action (usually `{hyper, \"down\"}`)", + " * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)", + "", + "A configuration example can be:", + "``` lua", + "local mods = {\"ctrl\", \"alt\", \"cmd\"}", + "spoon.MiroWindowsManager:bindHotkeys({", + " up = {mods, \"up\"},", + " down = {mods, \"down\"},", + " left = {mods, \"left\"},", + " right = {mods, \"right\"},", + " fullscreen = {mods, \"f\"},", + " center = {mods, \"c\"},", + " move = {mods, \"v\"},", + " resize = {mods, \"d\"}", + "})", + "```" + ] + }, + { + "def" : "MiroWindowsManager:center()", + "stripped_doc" : [ + "Center", + "" + ], + "desc" : "Center", + "doc" : "Center\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:center()", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "name" : "center", + "parameters" : [ + " * None.", + "" + ] + }, + { + "def" : "MiroWindowsManager:fullscreen()", + "stripped_doc" : [ + "Fullscreen, or cycle to next fullscreen option", + "" + ], + "desc" : "Fullscreen, or cycle to next fullscreen option", + "doc" : "Fullscreen, or cycle to next fullscreen option\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:fullscreen()", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "name" : "fullscreen", + "parameters" : [ + " * None.", + "" + ] + }, + { + "def" : "MiroWindowsManager:go(move)", + "stripped_doc" : [ + "Move to screen edge, or cycle to next horizontal or vertical size if already there. ", + "Tap both directions to go full width \/ height. ", + "" + ], + "desc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there.", + "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \n\nParameters:\n * move - 'up', 'down', 'left', 'right'\n\nReturns:\n * The MiroWindowsManager object", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:go(move)", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "name" : "go", + "parameters" : [ + " * move - 'up', 'down', 'left', 'right'", + "" + ] + }, + { + "def" : "MiroWindowsManager:growFully(growth)", + "stripped_doc" : [ + "Grow the frontmost window to full width \/ height taller, wider. ", + "" + ], + "desc" : "Grow the frontmost window to full width \/ height taller, wider.", + "doc" : "Grow the frontmost window to full width \/ height taller, wider. \n\nParameters:\n * growth - 'taller', or 'wider'\n\nReturns:\n * The MiroWindowsManager object", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:growFully(growth)", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "name" : "growFully", + "parameters" : [ + " * growth - 'taller', or 'wider'", + "" + ] + }, + { + "def" : "MiroWindowsManager:init()", + "stripped_doc" : [ + + ], + "desc" : "UNKNOWN DESC", + "doc" : "", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:init()", + "type" : "Method", + "returns" : [ + + ], + "name" : "init", + "parameters" : [ + + ] + }, + { + "def" : "MiroWindowsManager:move(side)", + "stripped_doc" : [ + "Move the frontmost window up, down, left, right. ", + "" + ], + "desc" : "Move the frontmost window up, down, left, right.", + "doc" : "Move the frontmost window up, down, left, right. \n\nParameters:\n * side - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:move(side)", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "name" : "move", + "parameters" : [ + " * side - 'up', 'down', 'left', or 'right'", + "" + ] + } + ], + "name" : "MiroWindowsManager" + } ] \ No newline at end of file diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index c5f926a..6038865 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -49,40 +49,6 @@ obj._logger = logger -- make logger available so users can turn up the volume! logger.i("Loading ".. obj.name) --- ### Utilities -function string.titleCase(str) - -- ## WARNING: Global change ## - return (str:gsub('^%l', string.upper)) -end -local expect = {} -function expect.argument_to_be_in_table(argument, to_be_in) - assert( - hs.fnutils.contains(to_be_in, argument), - 'Expected "'.. hs.inspect(argument) ..'" to be one of '.. hs.inspect(to_be_in) - ) -end -function expect.argument_to_be_in_table_or_nil(argument, to_be_in) - assert( - hs.fnutils.contains(to_be_in, argument) or argument == nil, - 'Expected "'.. hs.inspect(argument) ..'" to be one of '.. hs.inspect(to_be_in) - ) -end -function expect.truthy(argument, expression) - assert( - argument, - 'Expected truthyness from '.. hs.inspect(expression) - ) -end -local function round(num) - if num >= 0 then - return math.floor(num+.499999999) - else - return math.ceil(num-.499999999) - end -end -obj._round = round - - -- ## Public variables --- MiroWindowsManager.sizes @@ -126,10 +92,11 @@ function obj.GRID.cell() end ---- MiroWindowsManager.pushToNextScreen +--- MiroWindowsManager.moveToNextScreen --- Variable ---- If `move`d past the screen edge, jump to next screen? (true|false) -obj.pushToNextScreen = false +--- Boolean value to decide wether or not to move the window on the next screen +--- if the window is moved the screen edge. +obj.moveToNextScreen = false -- ## Internal @@ -137,38 +104,44 @@ obj.pushToNextScreen = false -- ### Internal configuration -- Window moves and their relationships -local directions = { 'up', 'down', 'left', 'right' } -obj._directions = directions -local directions_rel = { +obj._directions = { 'up', 'down', 'left', 'right' } +obj._directions_rel = { up = { opp = 'down', grow = 'taller', dim = 'h', pos = 'y', home = function() return 0 end }, down = { opp = 'up', grow = 'taller', dim = 'h', pos = 'y', home = function() return obj.GRID.h end }, left = { opp = 'right', grow = 'wider', dim = 'w', pos = 'x', home = function() return 0 end }, right = { opp = 'left', grow = 'wider', dim = 'w', pos = 'x', home = function() return obj.GRID.w end }, } -obj._directions_rel = directions_rel + -- Window growths and their relationships -local growths = { 'taller', 'shorter', 'wider', 'thinner' } -obj._growths = growths -local growths_rel = { - taller = { opp = 'shorter', dim = 'h', pos = 'y', side = 'up', sticky_bound_fix = false }, - shorter = { opp = 'taller', dim = 'h', pos = 'y', side = 'down', sticky_bound_fix = true }, - wider = { opp = 'thinner', dim = 'w', pos = 'x', side = 'left', sticky_bound_fix = false }, - thinner = { opp = 'wider', dim = 'w', pos = 'x', side = 'right', sticky_bound_fix = true }, +obj._growths = { 'taller', 'shorter', 'wider', 'thinner' } +obj._growths_rel = { + taller = { opp = 'shorter', dim = 'h', pos = 'y', side = 'up' }, + shorter = { opp = 'taller', dim = 'h', pos = 'y', side = 'down' }, + wider = { opp = 'thinner', dim = 'w', pos = 'x', side = 'left' }, + thinner = { opp = 'wider', dim = 'w', pos = 'x', side = 'right' }, } -obj._growths_rel = growths_rel + +-- The keys used to move, generally the arrow keys, but they could also be WASD or something else +obj._movingKeys = { } +for _,move in ipairs(obj._directions) do + obj._movingKeys[move] = move +end -- ### Internal state obj._pressed = {} obj._press_timers = {} obj._originalPositionStore = {} -hs.fnutils.each(hs.fnutils.concat(hs.fnutils.concat(directions, growths), {'fullscreen'}), function(move) +obj._lastSeq = {} +obj._lastFullscreenSeq = nil +local function initPressed(move) obj._pressed[move] = false obj._press_timers[move] = hs.timer.doAfter(1, function() obj._pressed[move] = false end) obj._originalPositionStore[move] = {} -end) -obj._lastSeq = {} -obj._lastFullSeq = nil -- this is for you, reader, so you know we're going to use it (Lua doesn't care) +end +hs.fnutils.each(obj._growths, initPressed) +hs.fnutils.each(obj._directions, initPressed) +initPressed('fullscreen') local function register_press(direction) obj._pressed[direction] = true @@ -182,177 +155,77 @@ local function currentlyPressed(direction) return obj._pressed[direction] end --- ### Internal convenience functions --- Accessor for functions on the frontmost window -local frontmost = {} -obj._frontmost = frontmost +-- ### Utilities --- An hs.window for the frontmost window -function frontmost.window() - return hs.window.frontmostWindow() -end --- An hs.grid for the frontmost window -function frontmost.cell() - local win = frontmost.window() - return hs.grid.get(win, win:screen()) +function titleCase(str) + return (str:gsub('^%l', string.upper)) end --- ## Public - ---- MiroWindowsManager:move(side) ---- Method ---- Move the frontmost window up, down, left, right. ---- Also: ---- * MiroWindowsManager:moveUp() ---- * MiroWindowsManager:moveDown() ---- * MiroWindowsManager:moveLeft() ---- * MiroWindowsManager:moveRight() ---- ---- Parameters: ---- * side - up, down, left, right ---- ---- Returns: ---- * The MiroWindowsManager object -function obj:move(side) - expect.argument_to_be_in_table(side, directions) - - register_press(side) - if currentlyPressed(directions_rel[side].opp) then - -- if still keydown moving the in the opposite direction, go full width/height - logger.i("Maximising ".. directions_rel[side].dim .." since ".. - directions_rel[side].opp .." still active.") - self:growFully(directions_rel[side].grow) -- full width/height +function round(num) + if num >= 0 then + return math.floor(num+.499999999) else - if self:currentlyBound(side) and not self.pushToNextScreen then - logger.i("`self.pushToNextScreen` == false so not moving to ".. side .." screen.") - else - logger.i('Moving '.. side) - hs.grid['pushWindow'.. side:titleCase()](frontmost.window()) - end + return math.ceil(num-.499999999) end - return self end -hs.fnutils.each(directions, -- up(), down, left, right - function(move) - obj['move'.. move:titleCase()] = function(self) return self:move(move) end - end ) +-- Accessor for functions on the frontmost window +function frontmostWindow() + return hs.window.frontmostWindow() +end ---- MiroWindowsManager:grow(growth) ---- Method ---- Grow the frontmost window taller, shorter, wider, thinner. ---- Also: ---- * MiroWindowsManager:taller() ---- * MiroWindowsManager:shorter() ---- * MiroWindowsManager:wider() ---- * MiroWindowsManager:thinner() ---- ---- Parameters: ---- * growth - taller, shorter, wider, thinner ---- ---- Returns: ---- * The MiroWindowsManager object -function obj:grow(growth) - expect.argument_to_be_in_table(growth, growths) - - register_press(growth) - if currentlyPressed(growths_rel[growth].opp) then - logger.i("Maximising ".. growths_rel[growth].dim .." since '".. - growths_rel[growth].opp .."' still active.") - return self:growFully(growth) -- full width/height - else - local sticky_bound_fix = growths_rel[growth].sticky_bound_fix and - self:currentlyBound(growths_rel[growth].side) and - not self:currentlyBound(directions_rel[growths_rel[growth].side].opp) - local prev_window_ani - if sticky_bound_fix then - prev_window_ani = hs.window.animationDuration - hs.window.animationDuration = 0 - end - - logger.i('Gowing '.. growth) - hs.grid['resizeWindow'.. growth:titleCase()](frontmost.window()) - - if sticky_bound_fix then - logger.i("Sticking to ".. growths_rel[growth].side .. - " side since we're bound to it.") - hs.window.animationDuration = prev_window_ani - self:move(growths_rel[growth].side) - end - end - return self +function frontmostCell() + local win = frontmostWindow() + return hs.grid.get(win, win:screen()) end -hs.fnutils.each(growths, -- taller(), shorter, wider, thinner - function(growth) - obj[growth] = function(self) return self:grow(growth) end - end ) +-- ## Public --- MiroWindowsManager:growFully(growth) --- Method --- Grow the frontmost window to full width / height taller, wider. ---- Also: ---- * MiroWindowsManager:tallest() ---- * MiroWindowsManager:widest() --- --- Parameters: ---- * growth - taller, wider +--- * growth - 'taller', or 'wider' --- --- Returns: --- * The MiroWindowsManager object function obj:growFully(growth) - expect.argument_to_be_in_table(growth, growths) - - local cell = frontmost.cell() - cell[growths_rel[growth].pos] = 0 - cell[growths_rel[growth].dim] = self.GRID[growths_rel[growth].dim] + local cell = frontmostCell() + cell[self._growths_rel[growth].pos] = 0 + cell[self._growths_rel[growth].dim] = self.GRID[self._growths_rel[growth].dim] self._setPosition(cell) return self end -function obj:growTallest() return self:growFully('taller') end -function obj:growWidest() return self:growFully('wider') end --- MiroWindowsManager:go(move) --- Method --- Move to screen edge, or cycle to next horizontal or vertical size if already there. --- Tap both directions to go full width / height. ---- Also: ---- * MiroWindowsManager:goUp() ---- * MiroWindowsManager:goDown() ---- * MiroWindowsManager:goLeft() ---- * MiroWindowsManager:goRight() --- --- Parameters: ---- * move - up, down, left, right +--- * move - 'up', 'down', 'left', 'right' --- --- Returns: --- * The MiroWindowsManager object function obj:go(move) - if move == 'fullscreen' then return self:goFullscreen() end - - expect.argument_to_be_in_table(move, directions) - expect.truthy(not hs.fnutils.every(self.sizes, - function(x) - return x == 'c' - end), "hunting for something other than 'c' in self.sizes") - register_press(move) - if currentlyPressed(directions_rel[move].opp) then + if currentlyPressed(self._directions_rel[move].opp) then -- if still keydown moving the in the opposite direction, go full width/height - logger.i("Maximising ".. directions_rel[move].dim .." since ".. - directions_rel[move].opp .." still active.") - self:growFully(directions_rel[move].grow) -- full width/height + logger.i("Maximising ".. self._directions_rel[move].dim .." since ".. + self._directions_rel[move].opp .." still active.") + self:growFully(self._directions_rel[move].grow) -- full width/height else - local cell = frontmost.cell() + local cell = frontmostCell() local seq = self:currentSeq(move) -- current sequence index or 0 if out of sequence local log_info = "We're at ".. move .." sequence ".. tostring(seq) .." (".. cell.string .."), so" if hs.fnutils.contains(self.sizes, 'c') and seq == 0 then -- We're out of the sequence, so store the current window position - obj._originalPositionStore[move][frontmost.window():id()] = frontmost.cell() + obj._originalPositionStore[move][frontmostWindow():id()] = frontmostCell() log_info = log_info .." remembering position then" end @@ -365,12 +238,8 @@ function obj:go(move) end return self end -hs.fnutils.each(directions, -- goUp(), goDown, goLeft, goRight - function(move) - obj['go'.. move:titleCase()] = function(self) return self:go(move) end - end) ---- MiroWindowsManager:goFullscreen() +--- MiroWindowsManager:fullscreen() --- Method --- Fullscreen, or cycle to next fullscreen option --- @@ -379,18 +248,15 @@ hs.fnutils.each(directions, -- goUp(), goDown, goLeft, goRight --- --- Returns: --- * The MiroWindowsManager object -function obj:goFullscreen() - expect.truthy(not hs.fnutils.every(self.fullScreenSizes, - function(x) - return x == 'c' - end), "hunting for something other than 'c' in self.fullScreenSizes") +function obj:fullscreen() + logger.i('WFT') - local seq = self:currentFullSeq() -- current sequence index or 0 if out of sequence - local log_info = "We're at fullscreen sequence ".. tostring(seq) .." (".. frontmost.cell().string .."), so" + local seq = self:currentFullscreenSeq() -- current sequence index or 0 if out of sequence + local log_info = "We're at fullscreen sequence ".. tostring(seq) .." (".. frontmostCell().string .."), so" if hs.fnutils.contains(self.fullScreenSizes, 'c') and seq == 0 then -- We're out of the sequence, so store the current window position - obj._originalPositionStore['fullscreen'][frontmost.window():id()] = frontmost.cell() + obj._originalPositionStore['fullscreen'][frontmostWindow():id()] = frontmostCell() log_info = log_info .." remembering position then" end @@ -399,7 +265,7 @@ function obj:goFullscreen() log_info .. " moving to sequence " .. tostring(seq + 1) .." (size: ".. tostring(self.fullScreenSizes[seq + 1]) ..")" logger.i(log_info) - self:setToFullSeq(seq + 1) -- next in sequence + self:setToFullscreenSeq(seq + 1) -- next in sequence return self end @@ -413,48 +279,34 @@ end --- --- Returns: --- * The MiroWindowsManager object -function obj:goCenter() - return self:setToCenter() -end - - --- ## Public undocumented - --- Query whether window is centered -function obj:currentlyCentered() - local cell = frontmost.cell() - return cell.w + 2 * cell.x == self.GRID.w and - cell.h + 2 * cell.y == self.GRID.h -end - -local function snap_to_grid(cell) - hs.fnutils.each({'w','h','x','y'}, function(d) cell[d] = round(cell[d]) end) - return cell +function obj:center() + local cell = frontmostCell() + cell.center = self.GRID.cell().center + self._setPosition(cell) + return self end - -- ### Side methods (up, down, left, right) -- Query sequence for `side` - 0 means out of sequence function obj:currentSeq(side) - expect.argument_to_be_in_table(side, directions) - if self:currentlyBound(side) then - local dim = directions_rel[side].dim - local width = frontmost.cell()[dim] + local dim = self._directions_rel[side].dim + local width = frontmostCell()[dim] local relative_size = self.GRID[dim] / width - local last_matched_seq = + -- TODO + local lastMatchedSeq = self._lastSeq[side] and -- we've recorded a last seq, and self.sizes[self._lastSeq[side]] and -- it's a valid index to sizes self._lastSeq[side] - local last_matched_seq_matches_frontmost = - last_matched_seq and (self.sizes[last_matched_seq] == relative_size) + local lastMatchedSeqMatchesFrontmost = + lastMatchedSeq and (self.sizes[lastMatchedSeq] == relative_size) -- cleanup - if not last_matched_seq_matches_frontmost then self._lastSeq[side] = nil end + if not lastMatchedSeqMatchesFrontmost then self._lastSeq[side] = nil end local seq = - last_matched_seq_matches_frontmost and last_matched_seq or -- return it + lastMatchedSeqMatchesFrontmost and lastMatchedSeq or -- return it -- if another from sizes matches, return it hs.fnutils.indexOf(self.sizes, relative_size) or -- else 0 @@ -468,13 +320,7 @@ end -- Set sequence for `side` function obj:setToSeq(side, seq) - expect.argument_to_be_in_table(side, directions) - expect.truthy(type(seq) == 'number', "type(seq) == 'number'") - expect.truthy(seq ~= 0 and seq <= #self.sizes, "seq ~= 0 and seq <= #self.sizes") - - local cell = self:seqCell(side, seq) - - self._setPosition(cell) + self._setPosition(self:seqCell(side, seq)) self._lastSeq[side] = seq return self end @@ -485,7 +331,7 @@ function obj:seqCell(side, seq) local seq_factor = self.sizes[seq] while seq_factor == 'c' and - obj._originalPositionStore[side][frontmost.window():id()] == nil do + obj._originalPositionStore[side][frontmostWindow():id()] == nil do logger.i("... but nothing stored, so bouncing to the next position.") seq = seq + 1 @@ -493,30 +339,25 @@ function obj:seqCell(side, seq) end if seq_factor == 'c' then - cell = - obj._originalPositionStore[side][frontmost.window():id()] + cell = obj._originalPositionStore[side][frontmostWindow():id()] logger.i('Restoring stored window size ('.. cell.string ..')') else - cell = frontmost.cell() - cell[directions_rel[side].dim] = - self.GRID[directions_rel[side].dim] / self.sizes[seq] + cell = frontmostCell() + cell[self._directions_rel[side].dim] = self.GRID[self._directions_rel[side].dim] / self.sizes[seq] end if hs.fnutils.contains({'left', 'up'}, side) then - cell[directions_rel[side].pos] = directions_rel[side].home() + cell[self._directions_rel[side].pos] = self._directions_rel[side].home() else - cell[directions_rel[side].pos] = - directions_rel[side].home() - cell[directions_rel[side].dim] + cell[self._directions_rel[side].pos] = self._directions_rel[side].home() - cell[self._directions_rel[side].dim] end - return snap_to_grid(cell) + return self:snap_to_grid(cell) end -- Query whether window is bound to `side` (is touching that side of the screen) function obj:currentlyBound(side) - expect.argument_to_be_in_table(side, directions) - - local cell = frontmost.cell() + local cell = frontmostCell() if side == 'up' then return cell.y == 0 elseif side == 'down' then @@ -527,104 +368,85 @@ function obj:currentlyBound(side) return cell.x + cell.w == self.GRID.w end end -hs.fnutils.each( - directions, -- currentlyUpBound(), currentlyDownBound, currentlyLeftBound, currentlyRightBound - on edge? - function(side) - obj['currently'.. side:titleCase() ..'Bound'] = function(self) return self:currentlyBound(side) end - end -) - -- ### Fullscreen methods + +-- Query whether window is centered +function obj:currentlyCentered() + local cell = frontmostCell() + return cell.w + 2 * cell.x == self.GRID.w and + cell.h + 2 * cell.y == self.GRID.h +end + +function obj:snap_to_grid(cell) + hs.fnutils.each({'w','h','x','y'}, function(d) cell[d] = round(cell[d]) end) + return cell +end + -- Query fullscreen sequence - 0 means out of sequence -function obj:currentFullSeq() - local cell = frontmost.cell() +function obj:currentFullscreenSeq() + local cell = frontmostCell() - local last_matched_seq = - self._lastFullSeq and -- we've recorded a last seq, and - self.fullScreenSizes[self._lastFullSeq] and -- it's a valid index to fullScreenSizes - self._lastFullSeq - local last_matched_seq_matches_frontmost = - last_matched_seq and (cell == self:seqFullCell(last_matched_seq)) + local lastMatchedSeq = self._lastFullscreenSeq and -- if there is a saved last matched seq, and + self.fullScreenSizes[self._lastFullscreenSeq] -- it's (still) a valid index to fullScreenSizes - -- cleanup - if not last_matched_seq_matches_frontmost then self._lastFullSeq = nil end + local lastMatchedSeqMatchesFrontmost = lastMatchedSeq and + (cell == self:getFullscreenCell(lastMatchedSeq)) - local seq = - last_matched_seq_matches_frontmost and last_matched_seq or - nil + -- cleanup if the last matched seq doesn't matche the frontmost + if not lastMatchedSeqMatchesFrontmost then self._lastFullscreenSeq = nil end - if seq then - return seq - else - for i = 1,#self.fullScreenSizes do - if cell == self:seqFullCell(i) then - seq = i - if obj._lastFullSeq and i == obj._lastFullSeq then return i end + local seq = lastMatchedSeqMatchesFrontmost and lastMatchedSeq or nil + if seq then + logger.i('seq is true, value: ' .. tostring(seq)) + return seq + end + + for i = 1,#self.fullScreenSizes do + logger.i('analyze i ' .. tostring(i)) + if cell == self:getFullscreenCell(i) then + logger.i('cell == self:getFullscreenCell(i)') + seq = i + if obj._lastFullscreenSeq and i == obj._lastFullscreenSeq then + logger.i('returning loop ' .. tostring(seq)) + return i end end - return seq or 0 end + logger.i('returning ' .. tostring(seq)) + + return seq or 0 + end -- Set fullscreen sequence -function obj:setToFullSeq(seq) - local cell = self:seqFullCell(seq) - - self._setPosition(cell) - self._lastFullSeq = seq +function obj:setToFullscreenSeq(seq) + self._setPosition(self:getFullscreenCell(seq)) + self._lastFullscreenSeq = seq return self end -- hs.grid cell for fullscreen sequence `seq` -function obj:seqFullCell(seq) +function obj:getFullscreenCell(seq) local seq_factor = self.fullScreenSizes[seq] - while seq_factor == 'c' and - obj._originalPositionStore['fullscreen'][frontmost.window():id()] == nil do - logger.i("... but nothing stored, so bouncing to the next position.") - - seq = seq + 1 - seq_factor = self.fullScreenSizes[seq] - end + local pnt, size - local cell, pnt, size - if seq_factor == 'c' then - cell = - obj._originalPositionStore['fullscreen'][frontmost.window():id()] - cell.center = self.GRID.cell().center - else - size = hs.geometry.size( - self.GRID.w / seq_factor, - self.GRID.h / seq_factor - ) - pnt = hs.geometry.point( - (self.GRID.w - size.w) / 2, - (self.GRID.h - size.h) / 2 - ) - cell = hs.geometry(pnt, size) - end - return snap_to_grid(cell) -end - - --- Center window -function obj:setToCenter() - local cell = frontmost.cell() - cell.center = self.GRID.cell().center + size = hs.geometry.size( + self.GRID.w / seq_factor, + self.GRID.h / seq_factor + ) + pnt = hs.geometry.point( + (self.GRID.w - size.w) / 2, + (self.GRID.h - size.h) / 2 + ) - self._setPosition(cell) - return self + return self:snap_to_grid(hs.geometry(pnt, size)) end - -- Set window to cell function obj._setPosition(cell) - expect.truthy( - type(cell) == 'table' and type(cell.type) == 'function' and cell:type() == 'rect', - "type(cell) == 'table' and type(cell.type) == 'function' and cell:type() == 'rect'") - - local win = frontmost.window() + local win = frontmostWindow() hs.grid.set(win, cell, win:screen()) end @@ -655,69 +477,42 @@ obj.hotkeys = {} --- right = {mods, "right"}, --- fullscreen = {mods, "f"}, --- center = {mods, "c"}, ---- moveUp = {{'⌃','⌥'}, "up"}, ---- moveDown = {{'⌃','⌥'}, "down"}, ---- moveLeft = {{'⌃','⌥'}, "left"}, ---- moveRight = {{'⌃','⌥'}, "right"}, ---- taller = {{'⌃','⌥','⇧'}, "down"}, ---- shorter = {{'⌃','⌥','⇧'}, "up"}, ---- wider = {{'⌃','⌥','⇧'}, "right"}, ---- thinner = {{'⌃','⌥','⇧'}, "left"}, --- }) --- ``` function obj:bindHotkeys(mapping) logger.i("Bind Hotkeys for Miro's Windows Manager") - hs.fnutils.each(directions, -- up, down, left, right - function(direction) - -- go - if mapping[direction] then + for _,direction in ipairs(self._directions) do + if mapping[direction] then self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind(mapping[direction][1], mapping[direction][2], function() self:go(direction) end, function() cancel_press(direction) end) - end - - -- move - local move_command = 'move'.. direction:titleCase() - if mapping[move_command] then - self.hotkeys[#self.hotkeys + 1] = - hs.hotkey.bind(mapping[move_command][1], mapping[move_command][2], - function() self:move(direction) end, - function() cancel_press(direction) end) - end - end) - hs.fnutils.each(growths, -- taller, shorter, wider, thinner - function(sense) - -- grow - if mapping[sense] then - self.hotkeys[#self.hotkeys + 1] = - hs.hotkey.bind(mapping[sense][1], mapping[sense][2], - function() self:grow(sense) end, - function() cancel_press(sense) end) + -- save the keys that the user decided to be for directions, + -- generally the arrows keys, but it could be also WASD. + self._movingKeys[direction] = mapping[direction][2] end - end) + end if mapping.fullscreen then self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind(mapping.fullscreen[1], mapping.fullscreen[2], - function() self:goFullscreen() end) + function() self:fullscreen() end) end if mapping.center then self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind(mapping.center[1], mapping.center[2], - function() self:goCenter() end) + function() self:center() end) end end --- MiroWindowsManager:init() --- Method ---- LEGACY: Calling this is not required. function obj:init() - -- Nothing to do here + -- void (but it could be used to initialize the module) end return obj From ffad0d8941555cb6c328e15400183db5bc800998 Mon Sep 17 00:00:00 2001 From: Miro Mannino Date: Sat, 27 Oct 2018 20:24:34 +0400 Subject: [PATCH 24/41] Update MiroWindowsManager.spoon/init.lua move --- MiroWindowsManager.spoon/init.lua | 61 +++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 6038865..1d23a10 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -182,6 +182,60 @@ end -- ## Public +--- MiroWindowsManager:move(side) +--- Method +--- Move the frontmost window up, down, left, right. +--- +--- Parameters: +--- * side - 'up', 'down', 'left', or 'right' +--- +--- Returns: +--- * The MiroWindowsManager object +function obj:move(side) + if self:currentlyBound(side) and not self.pushToNextScreen then + logger.i("`self.pushToNextScreen` == false so not moving to ".. side .." screen.") + else + logger.i('Moving '.. side) + hs.grid['pushWindow'.. titleCase(side)](frontmostWindow()) + end + + return self +end +hs.fnutils.each(obj._directions, -- up(), down, left, right + function(move) + obj['move'.. titleCase(move)] = function(self) return self:move(move) end + end ) + +obj._moveModeKeyWatcher = nil +function obj:_moveModeOn() + logger.i("Move Mode on") + self._moveModeKeyWatcher = hs.eventtap.new({ hs.eventtap.event.types.keyDown }, function(ev) + local keyCode = ev:getKeyCode() + + if keyCode == hs.keycodes.map[self._movingKeys['left']] then + self:move('left') + return true + elseif keyCode == hs.keycodes.map[self._movingKeys['right']] then + self:move('right') + return true + elseif keyCode == hs.keycodes.map[self._movingKeys['down']] then + self:move('down') + return true + elseif keyCode == hs.keycodes.map[self._movingKeys['up']] then + self:move('up') + return true + else + return false + end + end):start() +end +function obj:_moveModeOff() + logger.i("Move Mode off"); + self._moveModeKeyWatcher:stop() +end + + + --- MiroWindowsManager:growFully(growth) --- Method --- Grow the frontmost window to full width / height taller, wider. @@ -477,6 +531,7 @@ obj.hotkeys = {} --- right = {mods, "right"}, --- fullscreen = {mods, "f"}, --- center = {mods, "c"}, +--- move = {mods, "v"} --- }) --- ``` function obj:bindHotkeys(mapping) @@ -507,6 +562,12 @@ function obj:bindHotkeys(mapping) function() self:center() end) end + if mapping.move then + self.hotkeys[#self.hotkeys + 1] = + hs.hotkey.bind(mapping.move[1], mapping.move[2], + function() self:_moveModeOn() end, function() self:_moveModeOff() end) + end + end --- MiroWindowsManager:init() From f6af7e4a3b8553c7e9ac6d06dff6bb6aef4e89b0 Mon Sep 17 00:00:00 2001 From: Miro Mannino Date: Sat, 27 Oct 2018 21:30:45 +0400 Subject: [PATCH 25/41] fullscreen fixes --- MiroWindowsManager.spoon/init.lua | 198 +++++++++++++----------------- 1 file changed, 87 insertions(+), 111 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 1d23a10..b76efc1 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -106,19 +106,19 @@ obj.moveToNextScreen = false -- Window moves and their relationships obj._directions = { 'up', 'down', 'left', 'right' } obj._directions_rel = { - up = { opp = 'down', grow = 'taller', dim = 'h', pos = 'y', home = function() return 0 end }, - down = { opp = 'up', grow = 'taller', dim = 'h', pos = 'y', home = function() return obj.GRID.h end }, - left = { opp = 'right', grow = 'wider', dim = 'w', pos = 'x', home = function() return 0 end }, - right = { opp = 'left', grow = 'wider', dim = 'w', pos = 'x', home = function() return obj.GRID.w end }, +up = { opp = 'down', grow = 'taller', dim = 'h', pos = 'y', home = function() return 0 end }, +down = { opp = 'up', grow = 'taller', dim = 'h', pos = 'y', home = function() return obj.GRID.h end }, +left = { opp = 'right', grow = 'wider', dim = 'w', pos = 'x', home = function() return 0 end }, +right = { opp = 'left', grow = 'wider', dim = 'w', pos = 'x', home = function() return obj.GRID.w end }, } -- Window growths and their relationships obj._growths = { 'taller', 'shorter', 'wider', 'thinner' } obj._growths_rel = { - taller = { opp = 'shorter', dim = 'h', pos = 'y', side = 'up' }, - shorter = { opp = 'taller', dim = 'h', pos = 'y', side = 'down' }, - wider = { opp = 'thinner', dim = 'w', pos = 'x', side = 'left' }, - thinner = { opp = 'wider', dim = 'w', pos = 'x', side = 'right' }, +taller = { opp = 'shorter', dim = 'h', pos = 'y', side = 'up' }, +shorter = { opp = 'taller', dim = 'h', pos = 'y', side = 'down' }, +wider = { opp = 'thinner', dim = 'w', pos = 'x', side = 'left' }, +thinner = { opp = 'wider', dim = 'w', pos = 'x', side = 'right' }, } -- The keys used to move, generally the arrow keys, but they could also be WASD or something else @@ -204,30 +204,30 @@ end hs.fnutils.each(obj._directions, -- up(), down, left, right function(move) obj['move'.. titleCase(move)] = function(self) return self:move(move) end - end ) + end ) obj._moveModeKeyWatcher = nil function obj:_moveModeOn() logger.i("Move Mode on") self._moveModeKeyWatcher = hs.eventtap.new({ hs.eventtap.event.types.keyDown }, function(ev) - local keyCode = ev:getKeyCode() - - if keyCode == hs.keycodes.map[self._movingKeys['left']] then - self:move('left') - return true + local keyCode = ev:getKeyCode() + + if keyCode == hs.keycodes.map[self._movingKeys['left']] then + self:move('left') + return true elseif keyCode == hs.keycodes.map[self._movingKeys['right']] then self:move('right') return true - elseif keyCode == hs.keycodes.map[self._movingKeys['down']] then - self:move('down') - return true - elseif keyCode == hs.keycodes.map[self._movingKeys['up']] then - self:move('up') - return true - else - return false - end - end):start() + elseif keyCode == hs.keycodes.map[self._movingKeys['down']] then + self:move('down') + return true + elseif keyCode == hs.keycodes.map[self._movingKeys['up']] then + self:move('up') + return true + else + return false + end + end):start() end function obj:_moveModeOff() logger.i("Move Mode off"); @@ -268,25 +268,17 @@ function obj:go(move) register_press(move) if currentlyPressed(self._directions_rel[move].opp) then -- if still keydown moving the in the opposite direction, go full width/height - logger.i("Maximising ".. self._directions_rel[move].dim .." since ".. - self._directions_rel[move].opp .." still active.") + logger.i("Maximising " .. self._directions_rel[move].dim .. " since " + .. self._directions_rel[move].opp .." still active.") self:growFully(self._directions_rel[move].grow) -- full width/height else local cell = frontmostCell() local seq = self:currentSeq(move) -- current sequence index or 0 if out of sequence - local log_info = "We're at ".. move .." sequence ".. tostring(seq) .." (".. cell.string .."), so" - - if hs.fnutils.contains(self.sizes, 'c') and seq == 0 then - -- We're out of the sequence, so store the current window position - obj._originalPositionStore[move][frontmostWindow():id()] = frontmostCell() - log_info = log_info .." remembering position then" - end + logger.i("We're at ".. move .." sequence ".. tostring(seq) .." (".. cell.string ..")") seq = seq % #self.sizes -- if at end of #self.sizes then wrap to 0 - log_info = - log_info .. " moving to sequence " .. tostring(seq + 1) .." (size: ".. tostring(self.sizes[seq + 1]) ..")" - logger.i(log_info) + logger.i("Updating seq to " .. tostring(seq + 1) .." (size: ".. tostring(self.sizes[seq + 1]) ..")") self:setToSeq(move, seq + 1) end @@ -303,21 +295,16 @@ end --- Returns: --- * The MiroWindowsManager object function obj:fullscreen() - logger.i('WFT') - local seq = self:currentFullscreenSeq() -- current sequence index or 0 if out of sequence - local log_info = "We're at fullscreen sequence ".. tostring(seq) .." (".. frontmostCell().string .."), so" + logger.i("We're at fullscreen sequence ".. tostring(seq) .." (".. frontmostCell().string ..")") if hs.fnutils.contains(self.fullScreenSizes, 'c') and seq == 0 then - -- We're out of the sequence, so store the current window position + logger.i("Fullscreen with 'c', since we are at seq 0, storing current position") obj._originalPositionStore['fullscreen'][frontmostWindow():id()] = frontmostCell() - log_info = log_info .." remembering position then" end - seq = seq % #self.fullScreenSizes -- if #self.fullScreenSizes then 0 - log_info = - log_info .. " moving to sequence " .. tostring(seq + 1) .." (size: ".. tostring(self.fullScreenSizes[seq + 1]) ..")" - logger.i(log_info) + seq = seq % #self.fullScreenSizes -- if seq = #self.fullScreenSizes then 0 so next seq = 1 (we cycle through sizes) + logger.i("Updating seq to " .. tostring(seq + 1) .." (size: ".. tostring(self.fullScreenSizes[seq + 1]) ..")") self:setToFullscreenSeq(seq + 1) -- next in sequence @@ -353,7 +340,7 @@ function obj:currentSeq(side) self._lastSeq[side] and -- we've recorded a last seq, and self.sizes[self._lastSeq[side]] and -- it's a valid index to sizes self._lastSeq[side] - local lastMatchedSeqMatchesFrontmost = + local lastMatchedSeqMatchesFrontmost = lastMatchedSeq and (self.sizes[lastMatchedSeq] == relative_size) -- cleanup @@ -366,26 +353,18 @@ function obj:currentSeq(side) -- else 0 0 - return seq - else - return 0 + return seq + else + return 0 + end end -end -- Set sequence for `side` function obj:setToSeq(side, seq) - self._setPosition(self:seqCell(side, seq)) - self._lastSeq[side] = seq - return self -end - --- hs.grid cell for sequence `seq` -function obj:seqCell(side, seq) local cell local seq_factor = self.sizes[seq] - while seq_factor == 'c' and - obj._originalPositionStore[side][frontmostWindow():id()] == nil do + while seq_factor == 'c' and obj._originalPositionStore[side][frontmostWindow():id()] == nil do logger.i("... but nothing stored, so bouncing to the next position.") seq = seq + 1 @@ -406,7 +385,11 @@ function obj:seqCell(side, seq) cell[self._directions_rel[side].pos] = self._directions_rel[side].home() - cell[self._directions_rel[side].dim] end - return self:snap_to_grid(cell) + cell = self:snap_to_grid(cell) + + self._setPosition(cell) + self._lastSeq[side] = seq + return self end -- Query whether window is bound to `side` (is touching that side of the screen) @@ -414,14 +397,14 @@ function obj:currentlyBound(side) local cell = frontmostCell() if side == 'up' then return cell.y == 0 - elseif side == 'down' then - return cell.y + cell.h == self.GRID.h - elseif side == 'left' then - return cell.x == 0 - elseif side == 'right' then - return cell.x + cell.w == self.GRID.w - end -end + elseif side == 'down' then + return cell.y + cell.h == self.GRID.h + elseif side == 'left' then + return cell.x == 0 + elseif side == 'right' then + return cell.x + cell.w == self.GRID.w + end + end -- ### Fullscreen methods @@ -429,7 +412,7 @@ end function obj:currentlyCentered() local cell = frontmostCell() return cell.w + 2 * cell.x == self.GRID.w and - cell.h + 2 * cell.y == self.GRID.h + cell.h + 2 * cell.y == self.GRID.h end function obj:snap_to_grid(cell) @@ -441,41 +424,34 @@ end function obj:currentFullscreenSeq() local cell = frontmostCell() - local lastMatchedSeq = self._lastFullscreenSeq and -- if there is a saved last matched seq, and - self.fullScreenSizes[self._lastFullscreenSeq] -- it's (still) a valid index to fullScreenSizes - - local lastMatchedSeqMatchesFrontmost = lastMatchedSeq and - (cell == self:getFullscreenCell(lastMatchedSeq)) - - -- cleanup if the last matched seq doesn't matche the frontmost - if not lastMatchedSeqMatchesFrontmost then self._lastFullscreenSeq = nil end - - local seq = lastMatchedSeqMatchesFrontmost and lastMatchedSeq or nil - if seq then - logger.i('seq is true, value: ' .. tostring(seq)) - return seq + -- optimization, most likely the window is at the same place as the last fullscreen seq + if self._lastFullscreenSeq and -- if there is a saved last matched seq, and + self.fullScreenSizes[self._lastFullscreenSeq] and -- it's (still) a valid index to fullScreenSizes + cell == self:getFullscreenCell(self._lastFullscreenSeq) then -- last matched seq is same as the current fullscreen + logger.i('last matched seq is same as current cell, so returning seq = ' .. tostring(self._lastFullscreenSeq)) + return self._lastFullscreenSeq + else + self._lastFullscreenSeq = nil -- cleanup if the last matched seq doesn't match the frontmost end + -- trying to see which fullscreen size is the current window for i = 1,#self.fullScreenSizes do - logger.i('analyze i ' .. tostring(i)) + logger.i('analyze seq = ' .. tostring(i)) if cell == self:getFullscreenCell(i) then - logger.i('cell == self:getFullscreenCell(i)') - seq = i - if obj._lastFullscreenSeq and i == obj._lastFullscreenSeq then - logger.i('returning loop ' .. tostring(seq)) - return i - end + logger.i('cell == self:getFullscreenCell(seq)') + return i end end - logger.i('returning ' .. tostring(seq)) - - return seq or 0 + -- we cannot find any fullscreen size that match the current window state, so we start with 0 + return 0 end -- Set fullscreen sequence function obj:setToFullscreenSeq(seq) + logger.i('lastMatchedSeq: ' .. tostring(lastMatchedSeq)) + self._setPosition(self:getFullscreenCell(seq)) self._lastFullscreenSeq = seq return self @@ -489,11 +465,11 @@ function obj:getFullscreenCell(seq) size = hs.geometry.size( self.GRID.w / seq_factor, self.GRID.h / seq_factor - ) + ) pnt = hs.geometry.point( (self.GRID.w - size.w) / 2, (self.GRID.h - size.h) / 2 - ) + ) return self:snap_to_grid(hs.geometry(pnt, size)) end @@ -539,36 +515,36 @@ function obj:bindHotkeys(mapping) for _,direction in ipairs(self._directions) do if mapping[direction] then - self.hotkeys[#self.hotkeys + 1] = - hs.hotkey.bind(mapping[direction][1], mapping[direction][2], - function() self:go(direction) end, - function() cancel_press(direction) end) + self.hotkeys[#self.hotkeys + 1] = + hs.hotkey.bind(mapping[direction][1], mapping[direction][2], + function() self:go(direction) end, + function() cancel_press(direction) end) -- save the keys that the user decided to be for directions, -- generally the arrows keys, but it could be also WASD. self._movingKeys[direction] = mapping[direction][2] end - end + end - if mapping.fullscreen then - self.hotkeys[#self.hotkeys + 1] = + if mapping.fullscreen then + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind(mapping.fullscreen[1], mapping.fullscreen[2], - function() self:fullscreen() end) - end + function() self:fullscreen() end) + end - if mapping.center then - self.hotkeys[#self.hotkeys + 1] = + if mapping.center then + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind(mapping.center[1], mapping.center[2], - function() self:center() end) - end + function() self:center() end) + end - if mapping.move then - self.hotkeys[#self.hotkeys + 1] = + if mapping.move then + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind(mapping.move[1], mapping.move[2], function() self:_moveModeOn() end, function() self:_moveModeOff() end) - end + end -end + end --- MiroWindowsManager:init() --- Method From 32ba464157ac5004b7fbecf40f15523822787c67 Mon Sep 17 00:00:00 2001 From: Miro Mannino Date: Fri, 2 Nov 2018 10:30:42 +0400 Subject: [PATCH 26/41] fullscreen completed --- MiroWindowsManager.spoon/init.lua | 167 +++++++++++++++--------------- 1 file changed, 82 insertions(+), 85 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index b76efc1..3f22afb 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -55,10 +55,8 @@ logger.i("Loading ".. obj.name) --- Variable --- The sizes that the window can have. --- The sizes are expressed as dividend of the entire screen's size. ---- For example `{2, 3, 3/2}` means that it can be 1/2, 1/3 and 2/3 of the total ---- screen's size. ---- Ensuring that these numbers all divide both dimensions of ---- MiroWindowsManager.GRID to give integers makes everything work better. +--- For example `{2, 3, 3/2}` means that it can be 1/2, 1/3 and 2/3 of the total screen's size. +--- Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers. obj.sizes = {2, 3, 3/2} --- MiroWindowsManager.fullScreenSizes @@ -67,11 +65,9 @@ obj.sizes = {2, 3, 3/2} --- The sizes are expressed as dividend of the entire screen's size. --- For example `{1, 4/3, 2}` means that it can be 1/1 (hence full screen), 3/4 --- and 1/2 of the total screen's size. ---- Ensuring that these numbers all divide both dimensions of ---- MiroWindowsManager.GRID to give integers makes everything work better. ---- Special: Use 'c' for the original size and shape of the window before ---- starting to move it, but centered. -obj.fullScreenSizes = {1, 4/3, 2} +--- Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers. +--- Use 'c' for the original size and shape of the window before starting to move it. +obj.fullScreenSizes = {1, 4/3, 2, 'c'} -- Comment: Lots of work here to save users a little work. Previous versions -- required users to call MiroWindowsManager:start() every time they changed @@ -84,8 +80,7 @@ require('extend_GRID').extend(obj, logger) --- Variable --- The screen's grid size. --- Ensuring that the numbers in MiroWindowsManager.sizes and ---- MiroWindowsManager.fullScreenSizes divide these numbers to give integers ---- makes everything work better. +--- Make sure that the numbers in MiroWindowsManager.fullScreenSizes divide h and w to give integers. obj.GRID = { w = 24, h = 24, margins = hs.geometry.point(0,0) } function obj.GRID.cell() return hs.geometry(obj.GRID.margins, hs.geometry.size(obj.GRID.w, obj.GRID.h)) @@ -101,24 +96,12 @@ obj.moveToNextScreen = false -- ## Internal --- ### Internal configuration - --- Window moves and their relationships obj._directions = { 'up', 'down', 'left', 'right' } obj._directions_rel = { -up = { opp = 'down', grow = 'taller', dim = 'h', pos = 'y', home = function() return 0 end }, -down = { opp = 'up', grow = 'taller', dim = 'h', pos = 'y', home = function() return obj.GRID.h end }, -left = { opp = 'right', grow = 'wider', dim = 'w', pos = 'x', home = function() return 0 end }, -right = { opp = 'left', grow = 'wider', dim = 'w', pos = 'x', home = function() return obj.GRID.w end }, -} - --- Window growths and their relationships -obj._growths = { 'taller', 'shorter', 'wider', 'thinner' } -obj._growths_rel = { -taller = { opp = 'shorter', dim = 'h', pos = 'y', side = 'up' }, -shorter = { opp = 'taller', dim = 'h', pos = 'y', side = 'down' }, -wider = { opp = 'thinner', dim = 'w', pos = 'x', side = 'left' }, -thinner = { opp = 'wider', dim = 'w', pos = 'x', side = 'right' }, + up = { opp = 'down', dim = 'h', pos = 'y', home = function() return 0 end }, + down = { opp = 'up', dim = 'h', pos = 'y', home = function() return obj.GRID.h end }, + left = { opp = 'right', dim = 'w', pos = 'x', home = function() return 0 end }, + right = { opp = 'left', dim = 'w', pos = 'x', home = function() return obj.GRID.w end } } -- The keys used to move, generally the arrow keys, but they could also be WASD or something else @@ -127,8 +110,6 @@ for _,move in ipairs(obj._directions) do obj._movingKeys[move] = move end --- ### Internal state - obj._pressed = {} obj._press_timers = {} obj._originalPositionStore = {} @@ -139,15 +120,14 @@ local function initPressed(move) obj._press_timers[move] = hs.timer.doAfter(1, function() obj._pressed[move] = false end) obj._originalPositionStore[move] = {} end -hs.fnutils.each(obj._growths, initPressed) hs.fnutils.each(obj._directions, initPressed) initPressed('fullscreen') -local function register_press(direction) +local function registerPress(direction) obj._pressed[direction] = true obj._press_timers[direction]:start() end -local function cancel_press(direction) +local function cancelPress(direction) obj._pressed[direction] = false obj._press_timers[direction]:stop() end @@ -238,17 +218,17 @@ end --- MiroWindowsManager:growFully(growth) --- Method ---- Grow the frontmost window to full width / height taller, wider. +--- Grow the frontmost window to full width / height. --- --- Parameters: ---- * growth - 'taller', or 'wider' +--- * dimension - 'h', or 'w' --- --- Returns: --- * The MiroWindowsManager object -function obj:growFully(growth) +function obj:growFully(dimension) local cell = frontmostCell() - cell[self._growths_rel[growth].pos] = 0 - cell[self._growths_rel[growth].dim] = self.GRID[self._growths_rel[growth].dim] + cell[dimension == 'h' and 'y' or 'x'] = 0 + cell[dimension] = self.GRID[dimension] self._setPosition(cell) return self end @@ -265,12 +245,12 @@ end --- Returns: --- * The MiroWindowsManager object function obj:go(move) - register_press(move) + registerPress(move) if currentlyPressed(self._directions_rel[move].opp) then -- if still keydown moving the in the opposite direction, go full width/height logger.i("Maximising " .. self._directions_rel[move].dim .. " since " .. self._directions_rel[move].opp .." still active.") - self:growFully(self._directions_rel[move].grow) -- full width/height + self:growFully(self._directions_rel[move].dim) -- full width/height else local cell = frontmostCell() local seq = self:currentSeq(move) -- current sequence index or 0 if out of sequence @@ -298,15 +278,24 @@ function obj:fullscreen() local seq = self:currentFullscreenSeq() -- current sequence index or 0 if out of sequence logger.i("We're at fullscreen sequence ".. tostring(seq) .." (".. frontmostCell().string ..")") - if hs.fnutils.contains(self.fullScreenSizes, 'c') and seq == 0 then - logger.i("Fullscreen with 'c', since we are at seq 0, storing current position") - obj._originalPositionStore['fullscreen'][frontmostWindow():id()] = frontmostCell() + if seq == 0 then + if hs.fnutils.contains(self.fullScreenSizes, 'c') then + logger.i("Since we are at seq 0, storing current position to use it with 'c'") + self._originalPositionStore['fullscreen'][frontmostWindow():id()] = frontmostCell() + end end - seq = seq % #self.fullScreenSizes -- if seq = #self.fullScreenSizes then 0 so next seq = 1 (we cycle through sizes) - logger.i("Updating seq to " .. tostring(seq + 1) .." (size: ".. tostring(self.fullScreenSizes[seq + 1]) ..")") + seq = seq % #self.fullScreenSizes + 1 -- if seq = #self.fullScreenSizes then 0 so next seq = 1 (we cycle through sizes) + logger.i("Updating seq to " .. tostring(seq) .." (size: ".. tostring(self.fullScreenSizes[seq]) ..")") - self:setToFullscreenSeq(seq + 1) -- next in sequence + if self.fullScreenSizes[seq] == 'c' then + logger.i("Seq is 'c' but we don't have a saved position, skip to the next one") + if not self._originalPositionStore['fullscreen'][frontmostWindow():id()] then + seq = seq % #self.fullScreenSizes + 1 + end + end + + self:setToFullscreenSeq(seq) -- next in sequence return self end @@ -359,36 +348,22 @@ function obj:currentSeq(side) end end --- Set sequence for `side` -function obj:setToSeq(side, seq) - local cell - local seq_factor = self.sizes[seq] - - while seq_factor == 'c' and obj._originalPositionStore[side][frontmostWindow():id()] == nil do - logger.i("... but nothing stored, so bouncing to the next position.") - - seq = seq + 1 - seq_factor = self.sizes[seq] - end +-- Set sequence for `move` +function obj:setToSeq(move, seq) + local cell = frontmostCell() - if seq_factor == 'c' then - cell = obj._originalPositionStore[side][frontmostWindow():id()] - logger.i('Restoring stored window size ('.. cell.string ..')') - else - cell = frontmostCell() - cell[self._directions_rel[side].dim] = self.GRID[self._directions_rel[side].dim] / self.sizes[seq] - end + cell[self._directions_rel[move].dim] = self.GRID[self._directions_rel[move].dim] / self.sizes[seq] - if hs.fnutils.contains({'left', 'up'}, side) then - cell[self._directions_rel[side].pos] = self._directions_rel[side].home() + if move == 'left' or move == 'up' then + cell[self._directions_rel[move].pos] = self._directions_rel[move].home() else - cell[self._directions_rel[side].pos] = self._directions_rel[side].home() - cell[self._directions_rel[side].dim] + cell[self._directions_rel[move].pos] = self._directions_rel[move].home() - cell[self._directions_rel[move].dim] end cell = self:snap_to_grid(cell) self._setPosition(cell) - self._lastSeq[side] = seq + self._lastSeq[move] = seq return self end @@ -462,6 +437,14 @@ function obj:getFullscreenCell(seq) local seq_factor = self.fullScreenSizes[seq] local pnt, size + if seq_factor == 'c' then + -- we want to use the value only once and then discarge + -- this is in case the window was in one of the full screen position/size + local cell = self._originalPositionStore['fullscreen'][frontmostWindow():id()] + self._originalPositionStore['fullscreen'][frontmostWindow():id()] = nil + return cell + end + size = hs.geometry.size( self.GRID.w / seq_factor, self.GRID.h / seq_factor @@ -496,29 +479,39 @@ obj.hotkeys = {} --- * up: for the up action (usually {hyper, "up"}) --- * down: for the down action (usually `{hyper, "down"}`) --- * fullscreen: for the full-screen action (e.g. `{hyper, "f"}`) +--- * center: for the center action (e.g. `{hyper, "c"}`) +--- * move: for the move action (e.g. `{hyper, "v"}`). The move action is +--- active as soon as the hotkey is pressed. While active the left, +--- right, up or down keys can be used (these are configured by +--- the actions above). --- --- A configuration example can be: --- ``` lua ---- local mods = {"ctrl", "alt", "cmd"} +--- local hyper = {"ctrl", "alt", "cmd"} --- spoon.MiroWindowsManager:bindHotkeys({ ---- up = {mods, "up"}, ---- down = {mods, "down"}, ---- left = {mods, "left"}, ---- right = {mods, "right"}, ---- fullscreen = {mods, "f"}, ---- center = {mods, "c"}, ---- move = {mods, "v"} +--- up = {hyper, "up"}, +--- down = {hyper, "down"}, +--- left = {hyper, "left"}, +--- right = {hyper, "right"}, +--- fullscreen = {hyper, "f"}, +--- center = {hyper, "c"}, +--- move = {hyper, "v"} --- }) +--- +--- In this example ctrl+alt+cmd+up will perform the 'up' action +--- Keeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up,down,left, and right +--- Pressing ctrl+alt+cmd+f the window will be maximized. --- ``` function obj:bindHotkeys(mapping) logger.i("Bind Hotkeys for Miro's Windows Manager") for _,direction in ipairs(self._directions) do if mapping[direction] then - self.hotkeys[#self.hotkeys + 1] = - hs.hotkey.bind(mapping[direction][1], mapping[direction][2], + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( + mapping[direction][1], + mapping[direction][2], function() self:go(direction) end, - function() cancel_press(direction) end) + function() cancelPress(direction) end) -- save the keys that the user decided to be for directions, -- generally the arrows keys, but it could be also WASD. @@ -527,21 +520,25 @@ function obj:bindHotkeys(mapping) end if mapping.fullscreen then - self.hotkeys[#self.hotkeys + 1] = - hs.hotkey.bind(mapping.fullscreen[1], mapping.fullscreen[2], + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( + mapping.fullscreen[1], + mapping.fullscreen[2], function() self:fullscreen() end) end if mapping.center then - self.hotkeys[#self.hotkeys + 1] = - hs.hotkey.bind(mapping.center[1], mapping.center[2], + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( + mapping.center[1], + mapping.center[2], function() self:center() end) end if mapping.move then - self.hotkeys[#self.hotkeys + 1] = - hs.hotkey.bind(mapping.move[1], mapping.move[2], - function() self:_moveModeOn() end, function() self:_moveModeOff() end) + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( + mapping.move[1], + mapping.move[2], + function() self:_moveModeOn() end, + function() self:_moveModeOff() end) end end From 171b3c280ff7274268a8b844c569419bd46a6bff Mon Sep 17 00:00:00 2001 From: Miro Mannino Date: Fri, 2 Nov 2018 11:32:32 +0400 Subject: [PATCH 27/41] resize --- MiroWindowsManager.spoon/init.lua | 141 ++++++++++++++++++++++-------- 1 file changed, 104 insertions(+), 37 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 3f22afb..2512ee7 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -87,22 +87,35 @@ function obj.GRID.cell() end ---- MiroWindowsManager.moveToNextScreen +--- MiroWindowsManager.pushToNextScreen --- Variable --- Boolean value to decide wether or not to move the window on the next screen --- if the window is moved the screen edge. -obj.moveToNextScreen = false +obj.pushToNextScreen = false +--- MiroWindowsManager.resizeRate +--- Variable +--- Float value to decide the the rate to resize windows. With a value of 1.05 it means that +--- everytime the window is made taller/wider by 5% more (or shorter/thinner by 5% less) +obj.resizeRate = 1.05 + -- ## Internal obj._directions = { 'up', 'down', 'left', 'right' } -obj._directions_rel = { +obj._directionsRel = { up = { opp = 'down', dim = 'h', pos = 'y', home = function() return 0 end }, down = { opp = 'up', dim = 'h', pos = 'y', home = function() return obj.GRID.h end }, left = { opp = 'right', dim = 'w', pos = 'x', home = function() return 0 end }, right = { opp = 'left', dim = 'w', pos = 'x', home = function() return obj.GRID.w end } } +obj._growths = { 'taller', 'shorter', 'wider', 'thinner' } +obj._growthsRel = { + taller = { opp = 'shorter', dim = 'h', pos = 'y', growthSign = 1 }, + shorter = { opp = 'taller', dim = 'h', pos = 'y', growthSign = -1 }, + wider = { opp = 'thinner', dim = 'w', pos = 'x', growthSign = 1 }, + thinner = { opp = 'wider', dim = 'w', pos = 'x', growthSign = -1 }, +} -- The keys used to move, generally the arrow keys, but they could also be WASD or something else obj._movingKeys = { } @@ -110,26 +123,26 @@ for _,move in ipairs(obj._directions) do obj._movingKeys[move] = move end +obj._moveModeKeyWatcher = nil +obj._resizeModeKeyWatcher = nil obj._pressed = {} -obj._press_timers = {} +obj._pressTimers = {} obj._originalPositionStore = {} obj._lastSeq = {} obj._lastFullscreenSeq = nil local function initPressed(move) obj._pressed[move] = false - obj._press_timers[move] = hs.timer.doAfter(1, function() obj._pressed[move] = false end) + obj._pressTimers[move] = hs.timer.doAfter(1, function() obj._pressed[move] = false end) obj._originalPositionStore[move] = {} end hs.fnutils.each(obj._directions, initPressed) -initPressed('fullscreen') - local function registerPress(direction) obj._pressed[direction] = true - obj._press_timers[direction]:start() + obj._pressTimers[direction]:start() end local function cancelPress(direction) obj._pressed[direction] = false - obj._press_timers[direction]:stop() + obj._pressTimers[direction]:stop() end local function currentlyPressed(direction) return obj._pressed[direction] @@ -155,6 +168,10 @@ function frontmostWindow() return hs.window.frontmostWindow() end +function frontmostScreen() + return frontmostWindow():screen() +end + function frontmostCell() local win = frontmostWindow() return hs.grid.get(win, win:screen()) @@ -178,15 +195,8 @@ function obj:move(side) logger.i('Moving '.. side) hs.grid['pushWindow'.. titleCase(side)](frontmostWindow()) end - return self end -hs.fnutils.each(obj._directions, -- up(), down, left, right - function(move) - obj['move'.. titleCase(move)] = function(self) return self:move(move) end - end ) - -obj._moveModeKeyWatcher = nil function obj:_moveModeOn() logger.i("Move Mode on") self._moveModeKeyWatcher = hs.eventtap.new({ hs.eventtap.event.types.keyDown }, function(ev) @@ -195,26 +205,75 @@ function obj:_moveModeOn() if keyCode == hs.keycodes.map[self._movingKeys['left']] then self:move('left') return true - elseif keyCode == hs.keycodes.map[self._movingKeys['right']] then - self:move('right') - return true - elseif keyCode == hs.keycodes.map[self._movingKeys['down']] then - self:move('down') - return true - elseif keyCode == hs.keycodes.map[self._movingKeys['up']] then - self:move('up') - return true - else - return false - end - end):start() + elseif keyCode == hs.keycodes.map[self._movingKeys['right']] then + self:move('right') + return true + elseif keyCode == hs.keycodes.map[self._movingKeys['down']] then + self:move('down') + return true + elseif keyCode == hs.keycodes.map[self._movingKeys['up']] then + self:move('up') + return true + else + return false + end + end):start() end function obj:_moveModeOff() logger.i("Move Mode off"); self._moveModeKeyWatcher:stop() end +--- MiroWindowsManager:resize(growth) +--- Method +--- Resize the frontmost window taller, shorter, wider, thinner. +--- +--- Parameters: +--- * growth - 'taller', 'shorter', 'wider', 'thinner' +--- +--- Returns: +--- * The MiroWindowsManager object +function obj:resize(growth) + logger.i('resize ' .. growth) + + local w = frontmostWindow() + local fr = w:frame() + + local growthDiff = fr[self._growthsRel[growth].dim] * (self.resizeRate - 1) + fr[self._growthsRel[growth].pos] = fr[self._growthsRel[growth].pos] - (self._growthsRel[growth].growthSign * growthDiff / 2) + fr[self._growthsRel[growth].dim] = fr[self._growthsRel[growth].dim] + (self._growthsRel[growth].growthSign * growthDiff) + if fr['y'] < 0 then fr['y'] = 0 end + if fr['x'] < 0 then fr['x'] = 0 end + + w:setFrame(fr) + return self +end +function obj:_resizeModeOn() + logger.i("Resize Mode on") + self._resizeModeKeyWatcher = hs.eventtap.new({ hs.eventtap.event.types.keyDown }, function(ev) + local keyCode = ev:getKeyCode() + if keyCode == hs.keycodes.map[self._movingKeys['left']] then + self:resize('thinner') + return true + elseif keyCode == hs.keycodes.map[self._movingKeys['right']] then + self:resize('wider') + return true + elseif keyCode == hs.keycodes.map[self._movingKeys['down']] then + self:resize('shorter') + return true + elseif keyCode == hs.keycodes.map[self._movingKeys['up']] then + self:resize('taller') + return true + else + return false + end + end):start() +end +function obj:_resizeModeOff() + logger.i("Resize Mode off"); + self._resizeModeKeyWatcher:stop() +end --- MiroWindowsManager:growFully(growth) --- Method @@ -246,11 +305,11 @@ end --- * The MiroWindowsManager object function obj:go(move) registerPress(move) - if currentlyPressed(self._directions_rel[move].opp) then + if currentlyPressed(self._directionsRel[move].opp) then -- if still keydown moving the in the opposite direction, go full width/height - logger.i("Maximising " .. self._directions_rel[move].dim .. " since " - .. self._directions_rel[move].opp .." still active.") - self:growFully(self._directions_rel[move].dim) -- full width/height + logger.i("Maximising " .. self._directionsRel[move].dim .. " since " + .. self._directionsRel[move].opp .." still active.") + self:growFully(self._directionsRel[move].dim) -- full width/height else local cell = frontmostCell() local seq = self:currentSeq(move) -- current sequence index or 0 if out of sequence @@ -320,7 +379,7 @@ end -- Query sequence for `side` - 0 means out of sequence function obj:currentSeq(side) if self:currentlyBound(side) then - local dim = self._directions_rel[side].dim + local dim = self._directionsRel[side].dim local width = frontmostCell()[dim] local relative_size = self.GRID[dim] / width @@ -352,12 +411,12 @@ function obj:currentSeq(side) function obj:setToSeq(move, seq) local cell = frontmostCell() - cell[self._directions_rel[move].dim] = self.GRID[self._directions_rel[move].dim] / self.sizes[seq] + cell[self._directionsRel[move].dim] = self.GRID[self._directionsRel[move].dim] / self.sizes[seq] if move == 'left' or move == 'up' then - cell[self._directions_rel[move].pos] = self._directions_rel[move].home() + cell[self._directionsRel[move].pos] = self._directionsRel[move].home() else - cell[self._directions_rel[move].pos] = self._directions_rel[move].home() - cell[self._directions_rel[move].dim] + cell[self._directionsRel[move].pos] = self._directionsRel[move].home() - cell[self._directionsRel[move].dim] end cell = self:snap_to_grid(cell) @@ -541,6 +600,14 @@ function obj:bindHotkeys(mapping) function() self:_moveModeOff() end) end + if mapping.resize then + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( + mapping.resize[1], + mapping.resize[2], + function() self:_resizeModeOn() end, + function() self:_resizeModeOff() end) + end + end --- MiroWindowsManager:init() From fafbbd9e6986b2bedaa42875b61f6d93b2ed09e7 Mon Sep 17 00:00:00 2001 From: Miro Mannino Date: Fri, 2 Nov 2018 12:12:02 +0400 Subject: [PATCH 28/41] fixes and refactoring --- MiroWindowsManager.spoon/init.lua | 33 ++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 2512ee7..d3fe87a 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -67,7 +67,7 @@ obj.sizes = {2, 3, 3/2} --- and 1/2 of the total screen's size. --- Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers. --- Use 'c' for the original size and shape of the window before starting to move it. -obj.fullScreenSizes = {1, 4/3, 2, 'c'} +obj.fullScreenSizes = {1, 2, 'c'} -- Comment: Lots of work here to save users a little work. Previous versions -- required users to call MiroWindowsManager:start() every time they changed @@ -123,17 +123,18 @@ for _,move in ipairs(obj._directions) do obj._movingKeys[move] = move end +obj._originalPositionStore = { fullscreen = {} } + obj._moveModeKeyWatcher = nil obj._resizeModeKeyWatcher = nil + obj._pressed = {} obj._pressTimers = {} -obj._originalPositionStore = {} obj._lastSeq = {} obj._lastFullscreenSeq = nil local function initPressed(move) obj._pressed[move] = false obj._pressTimers[move] = hs.timer.doAfter(1, function() obj._pressed[move] = false end) - obj._originalPositionStore[move] = {} end hs.fnutils.each(obj._directions, initPressed) local function registerPress(direction) @@ -339,7 +340,7 @@ function obj:fullscreen() if seq == 0 then if hs.fnutils.contains(self.fullScreenSizes, 'c') then - logger.i("Since we are at seq 0, storing current position to use it with 'c'") + logger.i("Since we are at seq 0, storing current position to use it with 'c' for window " .. frontmostWindow():id()) self._originalPositionStore['fullscreen'][frontmostWindow():id()] = frontmostCell() end end @@ -487,6 +488,13 @@ function obj:setToFullscreenSeq(seq) logger.i('lastMatchedSeq: ' .. tostring(lastMatchedSeq)) self._setPosition(self:getFullscreenCell(seq)) + + if self.fullScreenSizes[seq] == 'c' then + -- we want to use the value only once and then discarge + -- this is in case the window was in one of the full screen position/size + self._originalPositionStore['fullscreen'][frontmostWindow():id()] = nil + end + self._lastFullscreenSeq = seq return self end @@ -497,13 +505,12 @@ function obj:getFullscreenCell(seq) local pnt, size if seq_factor == 'c' then - -- we want to use the value only once and then discarge - -- this is in case the window was in one of the full screen position/size - local cell = self._originalPositionStore['fullscreen'][frontmostWindow():id()] - self._originalPositionStore['fullscreen'][frontmostWindow():id()] = nil - return cell + return self._originalPositionStore['fullscreen'][frontmostWindow():id()] end + logger.i('window id: ' .. tostring(frontmostWindow():id())) + logger.i('windows: ' .. hs.inspect(self._originalPositionStore['fullscreen'])) + size = hs.geometry.size( self.GRID.w / seq_factor, self.GRID.h / seq_factor @@ -608,6 +615,14 @@ function obj:bindHotkeys(mapping) function() self:_resizeModeOff() end) end + hs.hotkey.bind( + {"ctrl", "alt", "cmd"}, + "l", + function () + logger.i('window id: ' .. tostring(frontmostWindow():id())) + logger.i('windows: ' .. hs.inspect(self._originalPositionStore['fullscreen'])) + end) + end --- MiroWindowsManager:init() From 221bbf77ddf03048649b7e8234b708ac6e8c112c Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Sun, 11 Nov 2018 10:45:24 +1100 Subject: [PATCH 29/41] =?UTF-8?q?housekeeping=20-=20titleCase,=20round,=20?= =?UTF-8?q?frontmost=E2=80=A6=20to=20locals=20(we=20don't=20need=20to=20po?= =?UTF-8?q?llute=20the=20global=20namespace)=20-=20some=20indentation=20an?= =?UTF-8?q?d=20documentation=20fixes=20-=20and=20some=20minor=20cleanup=20?= =?UTF-8?q?to=20satisfy=20the=20lauc=20linter=20(line=20length,=20trailing?= =?UTF-8?q?=20whitespace)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MiroWindowsManager.spoon/init.lua | 150 +++++++++++++++--------------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index d3fe87a..528d4c6 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -17,15 +17,13 @@ --- === MiroWindowsManager === --- ---- With this Spoon you will be able to move the window in halves and in ---- corners using your keyboard and mainly using arrows. You would also be able ---- to resize them by thirds, quarters, or halves. +--- With this Spoon you will be able to move the window in halves and in corners using your keyboard and mainly using +--- arrows. You would also be able to resize them by thirds, quarters, or halves. --- Official homepage for more info and documentation: --- [https://github.com/miromannino/miro-windows-manager](https://github.com/miromannino/miro-windows-manager) --- ---- NOTE: This Spoon sets `hs.grid` globals with `hs.grid.setGrid()`, ---- `hs.grid.MARGINX`, and `hs.grid.MARGINY`. Changing MiroWindowsManager.GRID ---- will change these globals. +--- NOTE: This Spoon sets `hs.grid` globals with `hs.grid.setGrid()`, `hs.grid.MARGINX`, and `hs.grid.MARGINY`. +--- Changing MiroWindowsManager.GRID will change these globals. --- --- Download: --- https://github.com/miromannino/miro-windows-manager/raw/master/MiroWindowsManager.spoon.zip @@ -63,24 +61,22 @@ obj.sizes = {2, 3, 3/2} --- Variable --- The sizes that the window can have in full-screen. --- The sizes are expressed as dividend of the entire screen's size. ---- For example `{1, 4/3, 2}` means that it can be 1/1 (hence full screen), 3/4 ---- and 1/2 of the total screen's size. +--- For example `{1, 4/3, 2}` means that it can be 1/1 (hence full screen), 3/4 and 1/2 of the total screen's size. --- Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers. --- Use 'c' for the original size and shape of the window before starting to move it. obj.fullScreenSizes = {1, 2, 'c'} --- Comment: Lots of work here to save users a little work. Previous versions --- required users to call MiroWindowsManager:start() every time they changed --- GRID. The metatable work here watches for those changes and does the work --- :start() would have done. +-- Comment: Lots of work here to save users a little work. Previous versions required users to call +-- MiroWindowsManager:start() every time they changed GRID. The metatable work here watches for those changes and does +-- the work :start() would have done. package.path = package.path..";Spoons/".. ... ..".spoon/?.lua" require('extend_GRID').extend(obj, logger) --- MiroWindowsManager.GRID --- Variable --- The screen's grid size. ---- Ensuring that the numbers in MiroWindowsManager.sizes and ---- Make sure that the numbers in MiroWindowsManager.fullScreenSizes divide h and w to give integers. +--- Make sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give +--- integers. obj.GRID = { w = 24, h = 24, margins = hs.geometry.point(0,0) } function obj.GRID.cell() return hs.geometry(obj.GRID.margins, hs.geometry.size(obj.GRID.w, obj.GRID.h)) @@ -89,15 +85,14 @@ end --- MiroWindowsManager.pushToNextScreen --- Variable ---- Boolean value to decide wether or not to move the window on the next screen ---- if the window is moved the screen edge. +--- Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge. obj.pushToNextScreen = false --- MiroWindowsManager.resizeRate --- Variable ---- Float value to decide the the rate to resize windows. With a value of 1.05 it means that ---- everytime the window is made taller/wider by 5% more (or shorter/thinner by 5% less) +--- Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made +--- taller/wider (or shorter/thinner) in 5% increments. obj.resizeRate = 1.05 -- ## Internal @@ -117,7 +112,7 @@ obj._growthsRel = { thinner = { opp = 'wider', dim = 'w', pos = 'x', growthSign = -1 }, } --- The keys used to move, generally the arrow keys, but they could also be WASD or something else +-- The keys used to move, generally the arrow keys, but they could also be WASD or something else. obj._movingKeys = { } for _,move in ipairs(obj._directions) do obj._movingKeys[move] = move @@ -152,11 +147,11 @@ end -- ### Utilities -function titleCase(str) +local function titleCase(str) return (str:gsub('^%l', string.upper)) end -function round(num) +local function round(num) if num >= 0 then return math.floor(num+.499999999) else @@ -164,16 +159,16 @@ function round(num) end end --- Accessor for functions on the frontmost window -function frontmostWindow() +-- Accessor for functions on the frontmost window. +local function frontmostWindow() return hs.window.frontmostWindow() end -function frontmostScreen() +local function frontmostScreen() return frontmostWindow():screen() end -function frontmostCell() +local function frontmostCell() local win = frontmostWindow() return hs.grid.get(win, win:screen()) end @@ -227,10 +222,10 @@ end --- MiroWindowsManager:resize(growth) --- Method ---- Resize the frontmost window taller, shorter, wider, thinner. +--- Resize the frontmost window taller, shorter, wider, or thinner. --- --- Parameters: ---- * growth - 'taller', 'shorter', 'wider', 'thinner' +--- * growth - 'taller', 'shorter', 'wider', or 'thinner' --- --- Returns: --- * The MiroWindowsManager object @@ -240,9 +235,11 @@ function obj:resize(growth) local w = frontmostWindow() local fr = w:frame() - local growthDiff = fr[self._growthsRel[growth].dim] * (self.resizeRate - 1) - fr[self._growthsRel[growth].pos] = fr[self._growthsRel[growth].pos] - (self._growthsRel[growth].growthSign * growthDiff / 2) - fr[self._growthsRel[growth].dim] = fr[self._growthsRel[growth].dim] + (self._growthsRel[growth].growthSign * growthDiff) + local growthDiff = fr[self._growthsRel[growth].dim] * (self.resizeRate - 1) + fr[self._growthsRel[growth].pos] = + fr[self._growthsRel[growth].pos] - (self._growthsRel[growth].growthSign * growthDiff / 2) + fr[self._growthsRel[growth].dim] = + fr[self._growthsRel[growth].dim] + (self._growthsRel[growth].growthSign * growthDiff) if fr['y'] < 0 then fr['y'] = 0 end if fr['x'] < 0 then fr['x'] = 0 end @@ -300,16 +297,16 @@ end --- Tap both directions to go full width / height. --- --- Parameters: ---- * move - 'up', 'down', 'left', 'right' +--- * move - 'up', 'down', 'left', or 'right' --- --- Returns: --- * The MiroWindowsManager object function obj:go(move) registerPress(move) if currentlyPressed(self._directionsRel[move].opp) then - -- if still keydown moving the in the opposite direction, go full width/height - logger.i("Maximising " .. self._directionsRel[move].dim .. " since " - .. self._directionsRel[move].opp .." still active.") + -- if still keydown moving in the opposite direction, go full width/height + logger.i("Maximising " .. self._directionsRel[move].dim .. " since " .. self._directionsRel[move].opp .. + " still active.") self:growFully(self._directionsRel[move].dim) -- full width/height else local cell = frontmostCell() @@ -340,17 +337,19 @@ function obj:fullscreen() if seq == 0 then if hs.fnutils.contains(self.fullScreenSizes, 'c') then - logger.i("Since we are at seq 0, storing current position to use it with 'c' for window " .. frontmostWindow():id()) + logger.i("Since we are at seq 0, storing current position to use it with 'c' for window " .. + frontmostWindow():id()) self._originalPositionStore['fullscreen'][frontmostWindow():id()] = frontmostCell() end end - seq = seq % #self.fullScreenSizes + 1 -- if seq = #self.fullScreenSizes then 0 so next seq = 1 (we cycle through sizes) + -- if seq = #self.fullScreenSizes then 0 so next seq = 1 (we cycle through sizes) + seq = seq % #self.fullScreenSizes + 1 logger.i("Updating seq to " .. tostring(seq) .." (size: ".. tostring(self.fullScreenSizes[seq]) ..")") if self.fullScreenSizes[seq] == 'c' then logger.i("Seq is 'c' but we don't have a saved position, skip to the next one") - if not self._originalPositionStore['fullscreen'][frontmostWindow():id()] then + if not self._originalPositionStore['fullscreen'][frontmostWindow():id()] then seq = seq % #self.fullScreenSizes + 1 end end @@ -384,12 +383,11 @@ function obj:currentSeq(side) local width = frontmostCell()[dim] local relative_size = self.GRID[dim] / width - -- TODO local lastMatchedSeq = self._lastSeq[side] and -- we've recorded a last seq, and self.sizes[self._lastSeq[side]] and -- it's a valid index to sizes self._lastSeq[side] - local lastMatchedSeqMatchesFrontmost = + local lastMatchedSeqMatchesFrontmost = lastMatchedSeq and (self.sizes[lastMatchedSeq] == relative_size) -- cleanup @@ -432,22 +430,21 @@ function obj:currentlyBound(side) local cell = frontmostCell() if side == 'up' then return cell.y == 0 - elseif side == 'down' then - return cell.y + cell.h == self.GRID.h - elseif side == 'left' then - return cell.x == 0 - elseif side == 'right' then - return cell.x + cell.w == self.GRID.w - end - end + elseif side == 'down' then + return cell.y + cell.h == self.GRID.h + elseif side == 'left' then + return cell.x == 0 + elseif side == 'right' then + return cell.x + cell.w == self.GRID.w + end +end -- ### Fullscreen methods -- Query whether window is centered function obj:currentlyCentered() local cell = frontmostCell() - return cell.w + 2 * cell.x == self.GRID.w and - cell.h + 2 * cell.y == self.GRID.h + return cell.w + 2 * cell.x == self.GRID.w and cell.h + 2 * cell.y == self.GRID.h end function obj:snap_to_grid(cell) @@ -464,8 +461,8 @@ function obj:currentFullscreenSeq() self.fullScreenSizes[self._lastFullscreenSeq] and -- it's (still) a valid index to fullScreenSizes cell == self:getFullscreenCell(self._lastFullscreenSeq) then -- last matched seq is same as the current fullscreen logger.i('last matched seq is same as current cell, so returning seq = ' .. tostring(self._lastFullscreenSeq)) - return self._lastFullscreenSeq - else + return self._lastFullscreenSeq + else self._lastFullscreenSeq = nil -- cleanup if the last matched seq doesn't match the frontmost end @@ -478,20 +475,18 @@ function obj:currentFullscreenSeq() end end - -- we cannot find any fullscreen size that match the current window state, so we start with 0 + -- we cannot find any fullscreen size that matches the current window state, so we start with 0 return 0 end -- Set fullscreen sequence function obj:setToFullscreenSeq(seq) - logger.i('lastMatchedSeq: ' .. tostring(lastMatchedSeq)) - self._setPosition(self:getFullscreenCell(seq)) if self.fullScreenSizes[seq] == 'c' then - -- we want to use the value only once and then discarge - -- this is in case the window was in one of the full screen position/size + -- we want to use the value only once and then discard it + -- this is in case the window was in one of the full screen positions/sizes self._originalPositionStore['fullscreen'][frontmostWindow():id()] = nil end @@ -546,12 +541,13 @@ obj.hotkeys = {} --- * down: for the down action (usually `{hyper, "down"}`) --- * fullscreen: for the full-screen action (e.g. `{hyper, "f"}`) --- * center: for the center action (e.g. `{hyper, "c"}`) ---- * move: for the move action (e.g. `{hyper, "v"}`). The move action is ---- active as soon as the hotkey is pressed. While active the left, ---- right, up or down keys can be used (these are configured by ---- the actions above). +--- * move: for the move action (e.g. `{hyper, "v"}`). The move action is active as soon as the hotkey is pressed. +--- While active the left, right, up or down keys can be used (these are configured by the actions above). +--- * resize: for the resize action (e.g. `{hyper, "d"}`). The resize action is active as soon as the hotkey is +--- pressed. While active the left, right, up or down keys can be used (these are configured by the actions +--- above). --- ---- A configuration example can be: +--- A configuration example: --- ``` lua --- local hyper = {"ctrl", "alt", "cmd"} --- spoon.MiroWindowsManager:bindHotkeys({ @@ -561,20 +557,23 @@ obj.hotkeys = {} --- right = {hyper, "right"}, --- fullscreen = {hyper, "f"}, --- center = {hyper, "c"}, ---- move = {hyper, "v"} +--- move = {hyper, "v"}, +--- resize = {hyper, "d" } --- }) +--- ``` --- ---- In this example ctrl+alt+cmd+up will perform the 'up' action ---- Keeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up,down,left, and right +--- In this example ctrl+alt+cmd+up will perform the 'up' action. +--- Pressing ctrl+alt+cmd+c the window will be centered. --- Pressing ctrl+alt+cmd+f the window will be maximized. ---- ``` +--- Keeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up, down, left, and right. +--- Keeping ctrl+alt+cmd+d pressed you can resize the window using the arrow keys up, down, left, and right. function obj:bindHotkeys(mapping) logger.i("Bind Hotkeys for Miro's Windows Manager") for _,direction in ipairs(self._directions) do if mapping[direction] then self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping[direction][1], + mapping[direction][1], mapping[direction][2], function() self:go(direction) end, function() cancelPress(direction) end) @@ -587,38 +586,38 @@ function obj:bindHotkeys(mapping) if mapping.fullscreen then self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping.fullscreen[1], + mapping.fullscreen[1], mapping.fullscreen[2], function() self:fullscreen() end) end if mapping.center then self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping.center[1], + mapping.center[1], mapping.center[2], function() self:center() end) end if mapping.move then self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping.move[1], - mapping.move[2], - function() self:_moveModeOn() end, + mapping.move[1], + mapping.move[2], + function() self:_moveModeOn() end, function() self:_moveModeOff() end) end if mapping.resize then self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping.resize[1], - mapping.resize[2], - function() self:_resizeModeOn() end, + mapping.resize[1], + mapping.resize[2], + function() self:_resizeModeOn() end, function() self:_resizeModeOff() end) end hs.hotkey.bind( {"ctrl", "alt", "cmd"}, "l", - function () + function () logger.i('window id: ' .. tostring(frontmostWindow():id())) logger.i('windows: ' .. hs.inspect(self._originalPositionStore['fullscreen'])) end) @@ -627,6 +626,7 @@ function obj:bindHotkeys(mapping) --- MiroWindowsManager:init() --- Method +--- Currently does nothing (implemented so that treating this Spoon like others won't cause errors). function obj:init() -- void (but it could be used to initialize the module) end From 159763fb2cbb57e4e86a19f3e9b6f4afc77f1a2d Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Sun, 11 Nov 2018 14:20:57 +1100 Subject: [PATCH 30/41] Use hs.hotkey.modal for modal move & resize --- MiroWindowsManager.spoon/init.lua | 72 ++++++++----------------------- 1 file changed, 17 insertions(+), 55 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 528d4c6..7247c16 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -193,32 +193,6 @@ function obj:move(side) end return self end -function obj:_moveModeOn() - logger.i("Move Mode on") - self._moveModeKeyWatcher = hs.eventtap.new({ hs.eventtap.event.types.keyDown }, function(ev) - local keyCode = ev:getKeyCode() - - if keyCode == hs.keycodes.map[self._movingKeys['left']] then - self:move('left') - return true - elseif keyCode == hs.keycodes.map[self._movingKeys['right']] then - self:move('right') - return true - elseif keyCode == hs.keycodes.map[self._movingKeys['down']] then - self:move('down') - return true - elseif keyCode == hs.keycodes.map[self._movingKeys['up']] then - self:move('up') - return true - else - return false - end - end):start() -end -function obj:_moveModeOff() - logger.i("Move Mode off"); - self._moveModeKeyWatcher:stop() -end --- MiroWindowsManager:resize(growth) --- Method @@ -247,31 +221,6 @@ function obj:resize(growth) w:setFrame(fr) return self end -function obj:_resizeModeOn() - logger.i("Resize Mode on") - self._resizeModeKeyWatcher = hs.eventtap.new({ hs.eventtap.event.types.keyDown }, function(ev) - local keyCode = ev:getKeyCode() - if keyCode == hs.keycodes.map[self._movingKeys['left']] then - self:resize('thinner') - return true - elseif keyCode == hs.keycodes.map[self._movingKeys['right']] then - self:resize('wider') - return true - elseif keyCode == hs.keycodes.map[self._movingKeys['down']] then - self:resize('shorter') - return true - elseif keyCode == hs.keycodes.map[self._movingKeys['up']] then - self:resize('taller') - return true - else - return false - end - end):start() -end -function obj:_resizeModeOff() - logger.i("Resize Mode off"); - self._resizeModeKeyWatcher:stop() -end --- MiroWindowsManager:growFully(growth) --- Method @@ -599,19 +548,32 @@ function obj:bindHotkeys(mapping) end if mapping.move then + local modal = hs.hotkey.modal.new() + function modal:entered() logger.i("Move Mode on") end + function modal:exited() logger.i("Move Mode off") end + hs.fnutils.each(self._movingKeys, function(move) + modal:bind(mapping.move[1], self._movingKeys[move], function () self:move(move) end) + end) self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.move[1], mapping.move[2], - function() self:_moveModeOn() end, - function() self:_moveModeOff() end) + function() modal:enter() end, + function() modal:exit() end) end if mapping.resize then + local modal = hs.hotkey.modal.new() + function modal:entered() logger.i("Resize Mode on") end + function modal:exited() logger.i("Resize Mode off") end + local map = { left = 'thinner', right = 'wider', down = 'shorter', up = 'taller' } + for move,resize in pairs(map) do + modal:bind(mapping.move[1], move, function () self:resize(resize) end) + end self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.resize[1], mapping.resize[2], - function() self:_resizeModeOn() end, - function() self:_resizeModeOff() end) + function() modal:enter() end, + function() modal:exit() end) end hs.hotkey.bind( From 4b327084be90f902ca76e733fb8ad30576db4b7e Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Sun, 11 Nov 2018 18:10:52 +1100 Subject: [PATCH 31/41] bugfix: don't force windows from higher or lefter screens onto the primary screen --- MiroWindowsManager.spoon/init.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 7247c16..70e1a9e 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -215,8 +215,7 @@ function obj:resize(growth) fr[self._growthsRel[growth].dim] = fr[self._growthsRel[growth].dim] + (self._growthsRel[growth].growthSign * growthDiff) - if fr['y'] < 0 then fr['y'] = 0 end - if fr['x'] < 0 then fr['x'] = 0 end + fr = fr:intersect(frontmostScreen():frame()) -- avoid sizing out of bounds w:setFrame(fr) return self From 295480bf3ff5515382d889032da38b9ad675a411 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Sun, 11 Nov 2018 18:23:05 +1100 Subject: [PATCH 32/41] =?UTF-8?q?Use=20`hs.hotkey.modal`=20for=20`?= =?UTF-8?q?=E2=86=90=20+=20=E2=86=92`=20and=20`=E2=86=91=20+=20=E2=86=93`?= =?UTF-8?q?=20double=20taps,=20and=20retire=20watchers=20&=20etc.=20that?= =?UTF-8?q?=20are=20no=20longer=20needed.=20Extend=20double=20tap=20behavi?= =?UTF-8?q?our=20to=20move=20&=20resize=20states.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MiroWindowsManager.spoon/init.lua | 162 ++++++++++++++++-------------- 1 file changed, 85 insertions(+), 77 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 70e1a9e..3e96790 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -120,29 +120,8 @@ end obj._originalPositionStore = { fullscreen = {} } -obj._moveModeKeyWatcher = nil -obj._resizeModeKeyWatcher = nil - -obj._pressed = {} -obj._pressTimers = {} obj._lastSeq = {} obj._lastFullscreenSeq = nil -local function initPressed(move) - obj._pressed[move] = false - obj._pressTimers[move] = hs.timer.doAfter(1, function() obj._pressed[move] = false end) -end -hs.fnutils.each(obj._directions, initPressed) -local function registerPress(direction) - obj._pressed[direction] = true - obj._pressTimers[direction]:start() -end -local function cancelPress(direction) - obj._pressed[direction] = false - obj._pressTimers[direction]:stop() -end -local function currentlyPressed(direction) - return obj._pressed[direction] -end -- ### Utilities @@ -250,15 +229,8 @@ end --- Returns: --- * The MiroWindowsManager object function obj:go(move) - registerPress(move) - if currentlyPressed(self._directionsRel[move].opp) then - -- if still keydown moving in the opposite direction, go full width/height - logger.i("Maximising " .. self._directionsRel[move].dim .. " since " .. self._directionsRel[move].opp .. - " still active.") - self:growFully(self._directionsRel[move].dim) -- full width/height - else - local cell = frontmostCell() - local seq = self:currentSeq(move) -- current sequence index or 0 if out of sequence + local cell = frontmostCell() + local seq = self:currentSeq(move) -- current sequence index or 0 if out of sequence logger.i("We're at ".. move .." sequence ".. tostring(seq) .." (".. cell.string ..")") @@ -518,62 +490,63 @@ obj.hotkeys = {} function obj:bindHotkeys(mapping) logger.i("Bind Hotkeys for Miro's Windows Manager") + -- `growFully` modals + local growFullyModals = {} for _,direction in ipairs(self._directions) do - if mapping[direction] then - self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping[direction][1], - mapping[direction][2], - function() self:go(direction) end, - function() cancelPress(direction) end) + local modal = hs.hotkey.modal.new() - -- save the keys that the user decided to be for directions, - -- generally the arrows keys, but it could be also WASD. - self._movingKeys[direction] = mapping[direction][2] - end - end + -- primary direction + function modal.entered(_) logger.d(direction..' modal entered.') end + function modal.exited(_) logger.d(direction..' modal exited.') end - if mapping.fullscreen then - self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping.fullscreen[1], - mapping.fullscreen[2], - function() self:fullscreen() end) + -- opposite direction: growFully() + if mapping[direction] and mapping[self._directionsRel[direction].opp] then + modal:bind( + mapping[direction][1], + mapping[self._directionsRel[direction].opp][2], + function() + logger.i('… from '..direction..', `grow`ing.') + self:growFully(self._directionsRel[direction].dim) + end) + growFullyModals[direction] = modal end + end - if mapping.center then + -- `go` hotkeys + for _,direction in ipairs(self._directions) do + if mapping[direction] then self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping.center[1], - mapping.center[2], - function() self:center() end) + mapping[direction][1], + mapping[direction][2], + function() + self:go(direction) + growFullyModals[direction]:enter() + end, + function() + growFullyModals[direction]:exit() + end) + + -- save the keys that the user decided to be for directions, + -- generally the arrows keys, but it could be also WASD. + self._movingKeys[direction] = mapping[direction][2] end + end - if mapping.move then - local modal = hs.hotkey.modal.new() - function modal:entered() logger.i("Move Mode on") end - function modal:exited() logger.i("Move Mode off") end - hs.fnutils.each(self._movingKeys, function(move) - modal:bind(mapping.move[1], self._movingKeys[move], function () self:move(move) end) - end) - self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping.move[1], - mapping.move[2], - function() modal:enter() end, - function() modal:exit() end) - end + -- `fullscreen` hotkey + if mapping.fullscreen then + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( + mapping.fullscreen[1], + mapping.fullscreen[2], + function() self:fullscreen() end) + end - if mapping.resize then - local modal = hs.hotkey.modal.new() - function modal:entered() logger.i("Resize Mode on") end - function modal:exited() logger.i("Resize Mode off") end - local map = { left = 'thinner', right = 'wider', down = 'shorter', up = 'taller' } - for move,resize in pairs(map) do - modal:bind(mapping.move[1], move, function () self:resize(resize) end) - end - self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( - mapping.resize[1], - mapping.resize[2], - function() modal:enter() end, - function() modal:exit() end) - end + -- `center` hotkey + if mapping.center then + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( + mapping.center[1], + mapping.center[2], + function() self:center() end) + end hs.hotkey.bind( {"ctrl", "alt", "cmd"}, @@ -582,9 +555,44 @@ function obj:bindHotkeys(mapping) logger.i('window id: ' .. tostring(frontmostWindow():id())) logger.i('windows: ' .. hs.inspect(self._originalPositionStore['fullscreen'])) end) + -- `move` modifier + if mapping.move then + local modal = hs.hotkey.modal.new() + function modal.entered(_) logger.i("Move Mode on") end + function modal.exited(_) logger.i("Move Mode off") end + hs.fnutils.each(self._movingKeys, function(move) + modal:bind(mapping.move[1], self._movingKeys[move], + function() self:move(move); growFullyModals[move]:enter() end, + function() growFullyModals[move]:exit() end) + end) + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( + mapping.move[1], + mapping.move[2], + function() modal:enter() end, + function() modal:exit() end) + end + -- `resize` modifier + if mapping.resize then + local modal = hs.hotkey.modal.new() + function modal:entered() logger.i("Resize Mode on") end + function modal:exited() logger.i("Resize Mode off") end + local map = { left = 'thinner', right = 'wider', down = 'shorter', up = 'taller' } + local mapR = {}; for k,v in pairs(map) do mapR[v] = k end + for move,resize in pairs(map) do + modal:bind(mapping.move[1], move, + function() self:resize(resize); growFullyModals[mapR[resize]]:enter() end, + function() growFullyModals[mapR[resize]]:exit() end) + end + self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( + mapping.resize[1], + mapping.resize[2], + function() modal:enter() end, + function() modal:exit() end) end +end + --- MiroWindowsManager:init() --- Method --- Currently does nothing (implemented so that treating this Spoon like others won't cause errors). From c74f2f98b68ed4501e1df2cc973f2c4cac570d94 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Sun, 11 Nov 2018 18:35:34 +1100 Subject: [PATCH 33/41] minor: cleanup, docs & logging --- MiroWindowsManager.spoon/docs.json | 296 +++++++++++++++++++---------- MiroWindowsManager.spoon/init.lua | 67 ++++--- README.md | 37 ++-- 3 files changed, 254 insertions(+), 146 deletions(-) diff --git a/MiroWindowsManager.spoon/docs.json b/MiroWindowsManager.spoon/docs.json index c0bbc6c..5a08201 100644 --- a/MiroWindowsManager.spoon/docs.json +++ b/MiroWindowsManager.spoon/docs.json @@ -15,13 +15,11 @@ "stripped_doc" : [ "The sizes that the window can have. ", "The sizes are expressed as dividend of the entire screen's size. ", - "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total", - "screen's size. ", - "Ensuring that these numbers all divide both dimensions of", - "MiroWindowsManager.GRID to give integers makes everything work better." + "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size. ", + "Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers." ], + "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size. \nMake sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.", "desc" : "The sizes that the window can have.", - "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total\nscreen's size. \nEnsuring that these numbers all divide both dimensions of\nMiroWindowsManager.GRID to give integers makes everything work better.", "notes" : [ ], @@ -40,15 +38,12 @@ "stripped_doc" : [ "The sizes that the window can have in full-screen. ", "The sizes are expressed as dividend of the entire screen's size. ", - "For example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4", - "and 1\/2 of the total screen's size. ", - "Ensuring that these numbers all divide both dimensions of", - "MiroWindowsManager.GRID to give integers makes everything work better. ", - "Special: Use 'c' for the original size and shape of the window before", - "starting to move it, but centered." + "For example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4 and 1\/2 of the total screen's size. ", + "Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.", + "Use 'c' for the original size and shape of the window before starting to move it." ], + "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4 and 1\/2 of the total screen's size. \nMake sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.\nUse 'c' for the original size and shape of the window before starting to move it.", "desc" : "The sizes that the window can have in full-screen.", - "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4\nand 1\/2 of the total screen's size. \nEnsuring that these numbers all divide both dimensions of\nMiroWindowsManager.GRID to give integers makes everything work better. \nSpecial: Use 'c' for the original size and shape of the window before\nstarting to move it, but centered.", "notes" : [ ], @@ -66,12 +61,11 @@ "def" : "MiroWindowsManager.GRID", "stripped_doc" : [ "The screen's grid size. ", - "Ensuring that the numbers in MiroWindowsManager.sizes and", - "MiroWindowsManager.fullScreenSizes divide these numbers to give integers", - "makes everything work better." + "Make sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give", + "integers." ], + "doc" : "The screen's grid size. \nMake sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give\nintegers.", "desc" : "The screen's grid size.", - "doc" : "The screen's grid size. \nEnsuring that the numbers in MiroWindowsManager.sizes and\nMiroWindowsManager.fullScreenSizes divide these numbers to give integers\nmakes everything work better.", "notes" : [ ], @@ -86,22 +80,42 @@ ] }, { - "def" : "MiroWindowsManager.moveToNextScreen", + "def" : "MiroWindowsManager.pushToNextScreen", "stripped_doc" : [ - "Boolean value to decide wether or not to move the window on the next screen", - "if the window is moved the screen edge." + "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge." ], - "desc" : "Boolean value to decide wether or not to move the window on the next screen", - "doc" : "Boolean value to decide wether or not to move the window on the next screen\nif the window is moved the screen edge.", + "doc" : "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge.", + "desc" : "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge.", "notes" : [ ], - "signature" : "MiroWindowsManager.moveToNextScreen", + "signature" : "MiroWindowsManager.pushToNextScreen", "type" : "Variable", "returns" : [ ], - "name" : "moveToNextScreen", + "name" : "pushToNextScreen", + "parameters" : [ + + ] + }, + { + "def" : "MiroWindowsManager.resizeRate", + "stripped_doc" : [ + "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made", + "taller\/wider (or shorter\/thinner) in 5% increments." + ], + "doc" : "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made\ntaller\/wider (or shorter\/thinner) in 5% increments.", + "desc" : "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.resizeRate", + "type" : "Variable", + "returns" : [ + + ], + "name" : "resizeRate", "parameters" : [ ] @@ -114,13 +128,11 @@ ], "type" : "Module", - "desc" : "With this Spoon you will be able to move the window in halves and in", + "desc" : "With this Spoon you will be able to move the window in halves and in corners using your keyboard and mainly using", "Constructor" : [ ], - "Field" : [ - - ], + "doc" : "With this Spoon you will be able to move the window in halves and in corners using your keyboard and mainly using\narrows. You would also be able to resize them by thirds, quarters, or halves. \nOfficial homepage for more info and documentation:\n[https:\/\/github.com\/miromannino\/miro-windows-manager](https:\/\/github.com\/miromannino\/miro-windows-manager)\n\nNOTE: This Spoon sets `hs.grid` globals with `hs.grid.setGrid()`, `hs.grid.MARGINX`, and `hs.grid.MARGINY`.\nChanging MiroWindowsManager.GRID will change these globals.\n\nDownload:\nhttps:\/\/github.com\/miromannino\/miro-windows-manager\/raw\/master\/MiroWindowsManager.spoon.zip", "Method" : [ { "def" : "MiroWindowsManager:move(side)", @@ -128,8 +140,8 @@ "Move the frontmost window up, down, left, right. ", "" ], - "desc" : "Move the frontmost window up, down, left, right.", "doc" : "Move the frontmost window up, down, left, right. \n\nParameters:\n * side - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Move the frontmost window up, down, left, right.", "notes" : [ ], @@ -144,14 +156,36 @@ "" ] }, + { + "def" : "MiroWindowsManager:resize(growth)", + "stripped_doc" : [ + "Resize the frontmost window taller, shorter, wider, or thinner.", + "" + ], + "doc" : "Resize the frontmost window taller, shorter, wider, or thinner.\n\nParameters:\n * growth - 'taller', 'shorter', 'wider', or 'thinner'\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Resize the frontmost window taller, shorter, wider, or thinner.", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:resize(growth)", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "name" : "resize", + "parameters" : [ + " * growth - 'taller', 'shorter', 'wider', or 'thinner'", + "" + ] + }, { "def" : "MiroWindowsManager:growFully(growth)", "stripped_doc" : [ - "Grow the frontmost window to full width \/ height taller, wider. ", + "Grow the frontmost window to full width \/ height.", "" ], - "desc" : "Grow the frontmost window to full width \/ height taller, wider.", - "doc" : "Grow the frontmost window to full width \/ height taller, wider. \n\nParameters:\n * growth - 'taller', or 'wider'\n\nReturns:\n * The MiroWindowsManager object", + "doc" : "Grow the frontmost window to full width \/ height.\n\nParameters:\n * dimension - 'h', or 'w'\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Grow the frontmost window to full width \/ height.", "notes" : [ ], @@ -162,7 +196,7 @@ ], "name" : "growFully", "parameters" : [ - " * growth - 'taller', or 'wider'", + " * dimension - 'h', or 'w'", "" ] }, @@ -173,8 +207,8 @@ "Tap both directions to go full width \/ height. ", "" ], + "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \n\nParameters:\n * move - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", "desc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there.", - "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \n\nParameters:\n * move - 'up', 'down', 'left', 'right'\n\nReturns:\n * The MiroWindowsManager object", "notes" : [ ], @@ -185,7 +219,7 @@ ], "name" : "go", "parameters" : [ - " * move - 'up', 'down', 'left', 'right'", + " * move - 'up', 'down', 'left', or 'right'", "" ] }, @@ -195,8 +229,8 @@ "Fullscreen, or cycle to next fullscreen option", "" ], - "desc" : "Fullscreen, or cycle to next fullscreen option", "doc" : "Fullscreen, or cycle to next fullscreen option\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Fullscreen, or cycle to next fullscreen option", "notes" : [ ], @@ -217,8 +251,8 @@ "Center", "" ], - "desc" : "Center", "doc" : "Center\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Center", "notes" : [ ], @@ -239,8 +273,8 @@ "Binds hotkeys for Miro's Windows Manager", "" ], + "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n * center: for the center action (e.g. `{hyper, \"c\"}`)\n * move: for the move action (e.g. `{hyper, \"v\"}`). The move action is active as soon as the hotkey is pressed.\n While active the left, right, up or down keys can be used (these are configured by the actions above). \n * resize: for the resize action (e.g. `{hyper, \"d\"}`). The resize action is active as soon as the hotkey is\n pressed. While active the left, right, up or down keys can be used (these are configured by the actions\n above).\n\nA configuration example:\n``` lua\nlocal hyper = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {hyper, \"up\"},\n down = {hyper, \"down\"},\n left = {hyper, \"left\"},\n right = {hyper, \"right\"},\n fullscreen = {hyper, \"f\"},\n center = {hyper, \"c\"},\n move = {hyper, \"v\"},\n resize = {hyper, \"d\" }\n})\n```\n\nIn this example ctrl+alt+cmd+up will perform the 'up' action.\nPressing ctrl+alt+cmd+c the window will be centered.\nPressing ctrl+alt+cmd+f the window will be maximized.\nKeeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up, down, left, and right.\nKeeping ctrl+alt+cmd+d pressed you can resize the window using the arrow keys up, down, left, and right.", "desc" : "Binds hotkeys for Miro's Windows Manager", - "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n\nA configuration example can be:\n``` lua\nlocal mods = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {mods, \"up\"},\n down = {mods, \"down\"},\n left = {mods, \"left\"},\n right = {mods, \"right\"},\n fullscreen = {mods, \"f\"},\n center = {mods, \"c\"},\n move = {mods, \"v\"},\n resize = {mods, \"d\"}\n})\n```", "notes" : [ ], @@ -257,30 +291,42 @@ " * up: for the up action (usually {hyper, \"up\"})", " * down: for the down action (usually `{hyper, \"down\"}`)", " * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)", + " * center: for the center action (e.g. `{hyper, \"c\"}`)", + " * move: for the move action (e.g. `{hyper, \"v\"}`). The move action is active as soon as the hotkey is pressed.", + " While active the left, right, up or down keys can be used (these are configured by the actions above). ", + " * resize: for the resize action (e.g. `{hyper, \"d\"}`). The resize action is active as soon as the hotkey is", + " pressed. While active the left, right, up or down keys can be used (these are configured by the actions", + " above).", "", - "A configuration example can be:", + "A configuration example:", "``` lua", - "local mods = {\"ctrl\", \"alt\", \"cmd\"}", + "local hyper = {\"ctrl\", \"alt\", \"cmd\"}", "spoon.MiroWindowsManager:bindHotkeys({", - " up = {mods, \"up\"},", - " down = {mods, \"down\"},", - " left = {mods, \"left\"},", - " right = {mods, \"right\"},", - " fullscreen = {mods, \"f\"},", - " center = {mods, \"c\"},", - " move = {mods, \"v\"},", - " resize = {mods, \"d\"}", + " up = {hyper, \"up\"},", + " down = {hyper, \"down\"},", + " left = {hyper, \"left\"},", + " right = {hyper, \"right\"},", + " fullscreen = {hyper, \"f\"},", + " center = {hyper, \"c\"},", + " move = {hyper, \"v\"},", + " resize = {hyper, \"d\" }", "})", - "```" + "```", + "", + "In this example ctrl+alt+cmd+up will perform the 'up' action.", + "Pressing ctrl+alt+cmd+c the window will be centered.", + "Pressing ctrl+alt+cmd+f the window will be maximized.", + "Keeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up, down, left, and right.", + "Keeping ctrl+alt+cmd+d pressed you can resize the window using the arrow keys up, down, left, and right." ] }, { "def" : "MiroWindowsManager:init()", "stripped_doc" : [ - + "Currently does nothing (implemented so that treating this Spoon like others won't cause errors)." ], - "desc" : "UNKNOWN DESC", - "doc" : "", + "doc" : "Currently does nothing (implemented so that treating this Spoon like others won't cause errors).", + "desc" : "Currently does nothing (implemented so that treating this Spoon like others won't cause errors).", "notes" : [ ], @@ -298,18 +344,16 @@ "Command" : [ ], - "doc" : "With this Spoon you will be able to move the window in halves and in\ncorners using your keyboard and mainly using arrows. You would also be able\nto resize them by thirds, quarters, or halves. \nOfficial homepage for more info and documentation:\n[https:\/\/github.com\/miromannino\/miro-windows-manager](https:\/\/github.com\/miromannino\/miro-windows-manager)\n\nNOTE: This Spoon sets `hs.grid` globals with `hs.grid.setGrid()`,\n`hs.grid.MARGINX`, and `hs.grid.MARGINY`. Changing MiroWindowsManager.GRID\nwill change these globals.\n\nDownload:\nhttps:\/\/github.com\/miromannino\/miro-windows-manager\/raw\/master\/MiroWindowsManager.spoon.zip", "items" : [ { "def" : "MiroWindowsManager.GRID", "stripped_doc" : [ "The screen's grid size. ", - "Ensuring that the numbers in MiroWindowsManager.sizes and", - "MiroWindowsManager.fullScreenSizes divide these numbers to give integers", - "makes everything work better." + "Make sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give", + "integers." ], + "doc" : "The screen's grid size. \nMake sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give\nintegers.", "desc" : "The screen's grid size.", - "doc" : "The screen's grid size. \nEnsuring that the numbers in MiroWindowsManager.sizes and\nMiroWindowsManager.fullScreenSizes divide these numbers to give integers\nmakes everything work better.", "notes" : [ ], @@ -328,15 +372,12 @@ "stripped_doc" : [ "The sizes that the window can have in full-screen. ", "The sizes are expressed as dividend of the entire screen's size. ", - "For example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4", - "and 1\/2 of the total screen's size. ", - "Ensuring that these numbers all divide both dimensions of", - "MiroWindowsManager.GRID to give integers makes everything work better. ", - "Special: Use 'c' for the original size and shape of the window before", - "starting to move it, but centered." + "For example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4 and 1\/2 of the total screen's size. ", + "Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.", + "Use 'c' for the original size and shape of the window before starting to move it." ], + "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4 and 1\/2 of the total screen's size. \nMake sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.\nUse 'c' for the original size and shape of the window before starting to move it.", "desc" : "The sizes that the window can have in full-screen.", - "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4\nand 1\/2 of the total screen's size. \nEnsuring that these numbers all divide both dimensions of\nMiroWindowsManager.GRID to give integers makes everything work better. \nSpecial: Use 'c' for the original size and shape of the window before\nstarting to move it, but centered.", "notes" : [ ], @@ -351,22 +392,42 @@ ] }, { - "def" : "MiroWindowsManager.moveToNextScreen", + "def" : "MiroWindowsManager.pushToNextScreen", "stripped_doc" : [ - "Boolean value to decide wether or not to move the window on the next screen", - "if the window is moved the screen edge." + "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge." ], - "desc" : "Boolean value to decide wether or not to move the window on the next screen", - "doc" : "Boolean value to decide wether or not to move the window on the next screen\nif the window is moved the screen edge.", + "doc" : "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge.", + "desc" : "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge.", "notes" : [ ], - "signature" : "MiroWindowsManager.moveToNextScreen", + "signature" : "MiroWindowsManager.pushToNextScreen", "type" : "Variable", "returns" : [ ], - "name" : "moveToNextScreen", + "name" : "pushToNextScreen", + "parameters" : [ + + ] + }, + { + "def" : "MiroWindowsManager.resizeRate", + "stripped_doc" : [ + "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made", + "taller\/wider (or shorter\/thinner) in 5% increments." + ], + "doc" : "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made\ntaller\/wider (or shorter\/thinner) in 5% increments.", + "desc" : "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.resizeRate", + "type" : "Variable", + "returns" : [ + + ], + "name" : "resizeRate", "parameters" : [ ] @@ -376,13 +437,11 @@ "stripped_doc" : [ "The sizes that the window can have. ", "The sizes are expressed as dividend of the entire screen's size. ", - "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total", - "screen's size. ", - "Ensuring that these numbers all divide both dimensions of", - "MiroWindowsManager.GRID to give integers makes everything work better." + "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size. ", + "Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers." ], + "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size. \nMake sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.", "desc" : "The sizes that the window can have.", - "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total\nscreen's size. \nEnsuring that these numbers all divide both dimensions of\nMiroWindowsManager.GRID to give integers makes everything work better.", "notes" : [ ], @@ -402,8 +461,8 @@ "Binds hotkeys for Miro's Windows Manager", "" ], + "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n * center: for the center action (e.g. `{hyper, \"c\"}`)\n * move: for the move action (e.g. `{hyper, \"v\"}`). The move action is active as soon as the hotkey is pressed.\n While active the left, right, up or down keys can be used (these are configured by the actions above). \n * resize: for the resize action (e.g. `{hyper, \"d\"}`). The resize action is active as soon as the hotkey is\n pressed. While active the left, right, up or down keys can be used (these are configured by the actions\n above).\n\nA configuration example:\n``` lua\nlocal hyper = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {hyper, \"up\"},\n down = {hyper, \"down\"},\n left = {hyper, \"left\"},\n right = {hyper, \"right\"},\n fullscreen = {hyper, \"f\"},\n center = {hyper, \"c\"},\n move = {hyper, \"v\"},\n resize = {hyper, \"d\" }\n})\n```\n\nIn this example ctrl+alt+cmd+up will perform the 'up' action.\nPressing ctrl+alt+cmd+c the window will be centered.\nPressing ctrl+alt+cmd+f the window will be maximized.\nKeeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up, down, left, and right.\nKeeping ctrl+alt+cmd+d pressed you can resize the window using the arrow keys up, down, left, and right.", "desc" : "Binds hotkeys for Miro's Windows Manager", - "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n\nA configuration example can be:\n``` lua\nlocal mods = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {mods, \"up\"},\n down = {mods, \"down\"},\n left = {mods, \"left\"},\n right = {mods, \"right\"},\n fullscreen = {mods, \"f\"},\n center = {mods, \"c\"},\n move = {mods, \"v\"},\n resize = {mods, \"d\"}\n})\n```", "notes" : [ ], @@ -420,21 +479,33 @@ " * up: for the up action (usually {hyper, \"up\"})", " * down: for the down action (usually `{hyper, \"down\"}`)", " * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)", + " * center: for the center action (e.g. `{hyper, \"c\"}`)", + " * move: for the move action (e.g. `{hyper, \"v\"}`). The move action is active as soon as the hotkey is pressed.", + " While active the left, right, up or down keys can be used (these are configured by the actions above). ", + " * resize: for the resize action (e.g. `{hyper, \"d\"}`). The resize action is active as soon as the hotkey is", + " pressed. While active the left, right, up or down keys can be used (these are configured by the actions", + " above).", "", - "A configuration example can be:", + "A configuration example:", "``` lua", - "local mods = {\"ctrl\", \"alt\", \"cmd\"}", + "local hyper = {\"ctrl\", \"alt\", \"cmd\"}", "spoon.MiroWindowsManager:bindHotkeys({", - " up = {mods, \"up\"},", - " down = {mods, \"down\"},", - " left = {mods, \"left\"},", - " right = {mods, \"right\"},", - " fullscreen = {mods, \"f\"},", - " center = {mods, \"c\"},", - " move = {mods, \"v\"},", - " resize = {mods, \"d\"}", + " up = {hyper, \"up\"},", + " down = {hyper, \"down\"},", + " left = {hyper, \"left\"},", + " right = {hyper, \"right\"},", + " fullscreen = {hyper, \"f\"},", + " center = {hyper, \"c\"},", + " move = {hyper, \"v\"},", + " resize = {hyper, \"d\" }", "})", - "```" + "```", + "", + "In this example ctrl+alt+cmd+up will perform the 'up' action.", + "Pressing ctrl+alt+cmd+c the window will be centered.", + "Pressing ctrl+alt+cmd+f the window will be maximized.", + "Keeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up, down, left, and right.", + "Keeping ctrl+alt+cmd+d pressed you can resize the window using the arrow keys up, down, left, and right." ] }, { @@ -443,8 +514,8 @@ "Center", "" ], - "desc" : "Center", "doc" : "Center\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Center", "notes" : [ ], @@ -465,8 +536,8 @@ "Fullscreen, or cycle to next fullscreen option", "" ], - "desc" : "Fullscreen, or cycle to next fullscreen option", "doc" : "Fullscreen, or cycle to next fullscreen option\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Fullscreen, or cycle to next fullscreen option", "notes" : [ ], @@ -488,8 +559,8 @@ "Tap both directions to go full width \/ height. ", "" ], + "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \n\nParameters:\n * move - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", "desc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there.", - "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \n\nParameters:\n * move - 'up', 'down', 'left', 'right'\n\nReturns:\n * The MiroWindowsManager object", "notes" : [ ], @@ -500,18 +571,18 @@ ], "name" : "go", "parameters" : [ - " * move - 'up', 'down', 'left', 'right'", + " * move - 'up', 'down', 'left', or 'right'", "" ] }, { "def" : "MiroWindowsManager:growFully(growth)", "stripped_doc" : [ - "Grow the frontmost window to full width \/ height taller, wider. ", + "Grow the frontmost window to full width \/ height.", "" ], - "desc" : "Grow the frontmost window to full width \/ height taller, wider.", - "doc" : "Grow the frontmost window to full width \/ height taller, wider. \n\nParameters:\n * growth - 'taller', or 'wider'\n\nReturns:\n * The MiroWindowsManager object", + "doc" : "Grow the frontmost window to full width \/ height.\n\nParameters:\n * dimension - 'h', or 'w'\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Grow the frontmost window to full width \/ height.", "notes" : [ ], @@ -522,17 +593,17 @@ ], "name" : "growFully", "parameters" : [ - " * growth - 'taller', or 'wider'", + " * dimension - 'h', or 'w'", "" ] }, { "def" : "MiroWindowsManager:init()", "stripped_doc" : [ - + "Currently does nothing (implemented so that treating this Spoon like others won't cause errors)." ], - "desc" : "UNKNOWN DESC", - "doc" : "", + "doc" : "Currently does nothing (implemented so that treating this Spoon like others won't cause errors).", + "desc" : "Currently does nothing (implemented so that treating this Spoon like others won't cause errors).", "notes" : [ ], @@ -552,8 +623,8 @@ "Move the frontmost window up, down, left, right. ", "" ], - "desc" : "Move the frontmost window up, down, left, right.", "doc" : "Move the frontmost window up, down, left, right. \n\nParameters:\n * side - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Move the frontmost window up, down, left, right.", "notes" : [ ], @@ -567,8 +638,33 @@ " * side - 'up', 'down', 'left', or 'right'", "" ] + }, + { + "def" : "MiroWindowsManager:resize(growth)", + "stripped_doc" : [ + "Resize the frontmost window taller, shorter, wider, or thinner.", + "" + ], + "doc" : "Resize the frontmost window taller, shorter, wider, or thinner.\n\nParameters:\n * growth - 'taller', 'shorter', 'wider', or 'thinner'\n\nReturns:\n * The MiroWindowsManager object", + "desc" : "Resize the frontmost window taller, shorter, wider, or thinner.", + "notes" : [ + + ], + "signature" : "MiroWindowsManager:resize(growth)", + "type" : "Method", + "returns" : [ + " * The MiroWindowsManager object" + ], + "name" : "resize", + "parameters" : [ + " * growth - 'taller', 'shorter', 'wider', or 'thinner'", + "" + ] } + ], + "Field" : [ + ], "name" : "MiroWindowsManager" } -] \ No newline at end of file +] diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 3e96790..a9a64aa 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -30,6 +30,7 @@ --- -- ## TODO +-- sticky sides option when shrinking windows -- different sizes lists for specific apps local obj={} @@ -119,6 +120,7 @@ for _,move in ipairs(obj._directions) do end obj._originalPositionStore = { fullscreen = {} } +setmetatable(obj._originalPositionStore, {__mode = 'kv'}) -- weak table, so it doesn't become a memory hog obj._lastSeq = {} obj._lastFullscreenSeq = nil @@ -152,6 +154,12 @@ local function frontmostCell() return hs.grid.get(win, win:screen()) end +-- Set window to cell +local function setPosition(cell) + local win = frontmostWindow() + hs.grid.set(win, cell, win:screen()) +end + -- ## Public --- MiroWindowsManager:move(side) @@ -165,9 +173,10 @@ end --- * The MiroWindowsManager object function obj:move(side) if self:currentlyBound(side) and not self.pushToNextScreen then - logger.i("`self.pushToNextScreen` == false so not moving to ".. side .." screen.") + logger.d("`self.pushToNextScreen` == false so not moving to ".. side .." screen.") else logger.i('Moving '.. side) + hs.grid['pushWindow'.. titleCase(side)](frontmostWindow()) end return self @@ -183,7 +192,7 @@ end --- Returns: --- * The MiroWindowsManager object function obj:resize(growth) - logger.i('resize ' .. growth) + logger.i('Resizing '.. growth) local w = frontmostWindow() local fr = w:frame() @@ -210,10 +219,12 @@ end --- Returns: --- * The MiroWindowsManager object function obj:growFully(dimension) + logger.i('Growing '.. dimension) + local cell = frontmostCell() cell[dimension == 'h' and 'y' or 'x'] = 0 cell[dimension] = self.GRID[dimension] - self._setPosition(cell) + setPosition(cell) return self end @@ -232,13 +243,12 @@ function obj:go(move) local cell = frontmostCell() local seq = self:currentSeq(move) -- current sequence index or 0 if out of sequence - logger.i("We're at ".. move .." sequence ".. tostring(seq) .." (".. cell.string ..")") + logger.d("We're at ".. move .." sequence ".. tostring(seq) .." (".. cell.string ..")") - seq = seq % #self.sizes -- if at end of #self.sizes then wrap to 0 - logger.i("Updating seq to " .. tostring(seq + 1) .." (size: ".. tostring(self.sizes[seq + 1]) ..")") + seq = seq % #self.sizes -- if at end of #self.sizes then wrap to 0 + logger.d("Updating seq to " .. tostring(seq + 1) .." (size: ".. tostring(self.sizes[seq + 1]) ..")") - self:setToSeq(move, seq + 1) - end + self:setToSeq(move, seq + 1) return self end @@ -253,11 +263,11 @@ end --- * The MiroWindowsManager object function obj:fullscreen() local seq = self:currentFullscreenSeq() -- current sequence index or 0 if out of sequence - logger.i("We're at fullscreen sequence ".. tostring(seq) .." (".. frontmostCell().string ..")") + logger.d("We're at fullscreen sequence ".. tostring(seq) .." (".. frontmostCell().string ..")") if seq == 0 then if hs.fnutils.contains(self.fullScreenSizes, 'c') then - logger.i("Since we are at seq 0, storing current position to use it with 'c' for window " .. + logger.d("Since we are at seq 0, storing current position to use it with 'c' for window " .. frontmostWindow():id()) self._originalPositionStore['fullscreen'][frontmostWindow():id()] = frontmostCell() end @@ -265,10 +275,10 @@ function obj:fullscreen() -- if seq = #self.fullScreenSizes then 0 so next seq = 1 (we cycle through sizes) seq = seq % #self.fullScreenSizes + 1 - logger.i("Updating seq to " .. tostring(seq) .." (size: ".. tostring(self.fullScreenSizes[seq]) ..")") + logger.d("Updating seq to " .. tostring(seq) .." (size: ".. tostring(self.fullScreenSizes[seq]) ..")") if self.fullScreenSizes[seq] == 'c' then - logger.i("Seq is 'c' but we don't have a saved position, skip to the next one") + logger.d("Seq is 'c' but we don't have a saved position, skip to the next one") if not self._originalPositionStore['fullscreen'][frontmostWindow():id()] then seq = seq % #self.fullScreenSizes + 1 end @@ -289,9 +299,11 @@ end --- Returns: --- * The MiroWindowsManager object function obj:center() + logger.i('Centering') + local cell = frontmostCell() cell.center = self.GRID.cell().center - self._setPosition(cell) + setPosition(cell) return self end @@ -340,7 +352,7 @@ function obj:setToSeq(move, seq) cell = self:snap_to_grid(cell) - self._setPosition(cell) + setPosition(cell) self._lastSeq[move] = seq return self end @@ -380,7 +392,7 @@ function obj:currentFullscreenSeq() if self._lastFullscreenSeq and -- if there is a saved last matched seq, and self.fullScreenSizes[self._lastFullscreenSeq] and -- it's (still) a valid index to fullScreenSizes cell == self:getFullscreenCell(self._lastFullscreenSeq) then -- last matched seq is same as the current fullscreen - logger.i('last matched seq is same as current cell, so returning seq = ' .. tostring(self._lastFullscreenSeq)) + logger.d('last matched seq is same as current cell, so returning seq = ' .. tostring(self._lastFullscreenSeq)) return self._lastFullscreenSeq else self._lastFullscreenSeq = nil -- cleanup if the last matched seq doesn't match the frontmost @@ -388,9 +400,9 @@ function obj:currentFullscreenSeq() -- trying to see which fullscreen size is the current window for i = 1,#self.fullScreenSizes do - logger.i('analyze seq = ' .. tostring(i)) + logger.d('analyze seq = ' .. tostring(i)) if cell == self:getFullscreenCell(i) then - logger.i('cell == self:getFullscreenCell(seq)') + logger.d('cell == self:getFullscreenCell(seq)') return i end end @@ -402,7 +414,7 @@ end -- Set fullscreen sequence function obj:setToFullscreenSeq(seq) - self._setPosition(self:getFullscreenCell(seq)) + setPosition(self:getFullscreenCell(seq)) if self.fullScreenSizes[seq] == 'c' then -- we want to use the value only once and then discard it @@ -423,8 +435,8 @@ function obj:getFullscreenCell(seq) return self._originalPositionStore['fullscreen'][frontmostWindow():id()] end - logger.i('window id: ' .. tostring(frontmostWindow():id())) - logger.i('windows: ' .. hs.inspect(self._originalPositionStore['fullscreen'])) + logger.d('window id: ' .. tostring(frontmostWindow():id())) + logger.d('windows: ' .. hs.inspect(self._originalPositionStore['fullscreen'])) size = hs.geometry.size( self.GRID.w / seq_factor, @@ -438,12 +450,6 @@ function obj:getFullscreenCell(seq) return self:snap_to_grid(hs.geometry(pnt, size)) end --- Set window to cell -function obj._setPosition(cell) - local win = frontmostWindow() - hs.grid.set(win, cell, win:screen()) -end - -- ## Spoon mechanics (`bind`, `init`) @@ -548,13 +554,6 @@ function obj:bindHotkeys(mapping) function() self:center() end) end - hs.hotkey.bind( - {"ctrl", "alt", "cmd"}, - "l", - function () - logger.i('window id: ' .. tostring(frontmostWindow():id())) - logger.i('windows: ' .. hs.inspect(self._originalPositionStore['fullscreen'])) - end) -- `move` modifier if mapping.move then local modal = hs.hotkey.modal.new() @@ -582,7 +581,7 @@ function obj:bindHotkeys(mapping) for move,resize in pairs(map) do modal:bind(mapping.move[1], move, function() self:resize(resize); growFullyModals[mapR[resize]]:enter() end, - function() growFullyModals[mapR[resize]]:exit() end) + function() growFullyModals[mapR[resize]]:exit() end) end self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.resize[1], diff --git a/README.md b/README.md index 34fd52d..f86c13e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Miro's windows manager -With this script you will be able to move the window in halves and in corners using your keyboard and mainly using arrows. You would also be able to resize them by thirds, quarters, or halves. +With this Hammerspoon Spoon you will be able to move windows in halves and in corners using your keyboard, mainly using arrows. You will also be able to resize them by thirds, quarters, or halves. Other projects (e.g. Spectacle) move windows in halves using arrows, and in corners using other counterintuitive shortcuts, like letters, which makes things confusing. @@ -12,19 +12,20 @@ This script needs Hammerspoon in order to work. - Extract the zip file, containing `MiroWindowsManager.spoon` in `~/.hammerspoon/Spoons` - Now you need to configure Hammerspoon in order to load this spoon in `~/.hammerspoon/Spoons/MiroWindowsManager.spoon` adding the following snippet of code in your `init.lua` file: -``` -local hyper = {"ctrl", "alt", "cmd"} - +``` lua hs.loadSpoon("MiroWindowsManager") - -hs.window.animationDuration = 0.3 +local hyper = {"ctrl", "alt", "cmd"} spoon.MiroWindowsManager:bindHotkeys({ - up = {hyper, "up"}, - right = {hyper, "right"}, - down = {hyper, "down"}, - left = {hyper, "left"}, - fullscreen = {hyper, "f"} + up = {hyper, "up"}, + down = {hyper, "down"}, + left = {hyper, "left"}, + right = {hyper, "right"}, + fullscreen = {hyper, "f"}, + center = {hyper, "c"}, + move = {hyper, "v"}, + resize = {hyper, "d" } }) +hs.window.animationDuration = 0.3 ``` ## Shortcuts @@ -33,7 +34,7 @@ In the snippet above configure Miro'w Windows Manager in the following way: ### Hyper key - The hyper key is defined as `ctrl` + `alt` + `cmd`. This means that each shortcut will start by pressing these three keys. If you consider this too verbose for your personal keyboard interactions, you can also change it, for example replacing it with an unused key (e.g. caps lock key) with [Karabiner](https://pqrs.org/osx/karabiner/) and [Seil](https://pqrs.org/osx/karabiner/seil.html.en) to act as hyper key. + The hyper key is defined as `ctrl` + `alt` + `cmd`. This means that each shortcut will start by pressing these three keys. If you consider this too verbose for your personal keyboard interactions, you can also change it, for example replacing it with an unused modifier key (e.g. caps lock key with [Karabiner Elements](https://pqrs.org/osx/karabiner/)). ### Move in halves @@ -70,6 +71,18 @@ Note that in case the window is resized to be a half of the screen, you can also As the other shortcuts, `hyper` + `f` can be pressed multiple times to obtain a centered window of three fourth and one half of height and width. This behaviour can be customized. +### Center window + + - `hyper` + `c`: center window without changing its size + +### Move Mode + + - Press and hold `hyper` + `v`, then tap the move keys to move the window in increments of 5% of the screen size + +### Resize Mode + + - Press and hold `hyper` + `d`, then tap the move keys to resize the window in increments of 5% of the screen size. + ## Animations The snippet above configures the animation to last `0.3s` with `hs.window.animationDuration = 0.3`. To remove the animations completely change this value to `0`. From e274aefacec129c66f32590de717998d4849cb9f Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Sun, 11 Nov 2018 22:15:34 +1100 Subject: [PATCH 34/41] bugfix: don't let our fullscreen originalPositionStore get garbagecollected. --- MiroWindowsManager.spoon/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index a9a64aa..00675d5 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -120,7 +120,7 @@ for _,move in ipairs(obj._directions) do end obj._originalPositionStore = { fullscreen = {} } -setmetatable(obj._originalPositionStore, {__mode = 'kv'}) -- weak table, so it doesn't become a memory hog +setmetatable(obj._originalPositionStore.fullscreen, {__mode = 'kv'}) -- weak table, so it doesn't become a memory hog obj._lastSeq = {} obj._lastFullscreenSeq = nil From dcf1eafb1d9327647ffee27cbcdcff6eff806fe5 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Wed, 14 Nov 2018 07:32:05 +1100 Subject: [PATCH 35/41] bugfix: sometimes double tapping opposing directions to grow to full width/height didn't quite register; first entering the modal, then performing the action seems to fix this. --- MiroWindowsManager.spoon/init.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 00675d5..39fd6a0 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -525,8 +525,8 @@ function obj:bindHotkeys(mapping) mapping[direction][1], mapping[direction][2], function() - self:go(direction) growFullyModals[direction]:enter() + self:go(direction) end, function() growFullyModals[direction]:exit() @@ -561,8 +561,8 @@ function obj:bindHotkeys(mapping) function modal.exited(_) logger.i("Move Mode off") end hs.fnutils.each(self._movingKeys, function(move) modal:bind(mapping.move[1], self._movingKeys[move], - function() self:move(move); growFullyModals[move]:enter() end, - function() growFullyModals[move]:exit() end) + function() growFullyModals[move]:enter(); self:move(move) end, + function() growFullyModals[move]:exit() end) end) self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.move[1], @@ -580,8 +580,8 @@ function obj:bindHotkeys(mapping) local mapR = {}; for k,v in pairs(map) do mapR[v] = k end for move,resize in pairs(map) do modal:bind(mapping.move[1], move, - function() self:resize(resize); growFullyModals[mapR[resize]]:enter() end, - function() growFullyModals[mapR[resize]]:exit() end) + function() growFullyModals[mapR[resize]]:enter(); self:resize(resize) end, + function() growFullyModals[mapR[resize]]:exit() end) end self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.resize[1], From 9e5de9b3c2bbeeb5e991793080eccee29e8d3ca5 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Fri, 16 Nov 2018 09:00:25 +1100 Subject: [PATCH 36/41] bugfix: support non-arrow keys as moving keys --- MiroWindowsManager.spoon/init.lua | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 39fd6a0..532df05 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -496,6 +496,13 @@ obj.hotkeys = {} function obj:bindHotkeys(mapping) logger.i("Bind Hotkeys for Miro's Windows Manager") + -- movingKeys + for _,direction in ipairs(self._directions) do + -- save the keys that the user decided to be for directions, + -- generally the arrows keys, but it could be also WASD. + self._movingKeys[direction] = mapping[direction][2] + end + -- `growFully` modals local growFullyModals = {} for _,direction in ipairs(self._directions) do @@ -531,10 +538,6 @@ function obj:bindHotkeys(mapping) function() growFullyModals[direction]:exit() end) - - -- save the keys that the user decided to be for directions, - -- generally the arrows keys, but it could be also WASD. - self._movingKeys[direction] = mapping[direction][2] end end @@ -559,11 +562,11 @@ function obj:bindHotkeys(mapping) local modal = hs.hotkey.modal.new() function modal.entered(_) logger.i("Move Mode on") end function modal.exited(_) logger.i("Move Mode off") end - hs.fnutils.each(self._movingKeys, function(move) - modal:bind(mapping.move[1], self._movingKeys[move], + for move,key in pairs(self._movingKeys) do + modal:bind(mapping.move[1], key, function() growFullyModals[move]:enter(); self:move(move) end, function() growFullyModals[move]:exit() end) - end) + end self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.move[1], mapping.move[2], @@ -578,10 +581,11 @@ function obj:bindHotkeys(mapping) function modal:exited() logger.i("Resize Mode off") end local map = { left = 'thinner', right = 'wider', down = 'shorter', up = 'taller' } local mapR = {}; for k,v in pairs(map) do mapR[v] = k end - for move,resize in pairs(map) do - modal:bind(mapping.move[1], move, - function() growFullyModals[mapR[resize]]:enter(); self:resize(resize) end, - function() growFullyModals[mapR[resize]]:exit() end) + for move,key in pairs(self._movingKeys) do + hs.printf("move: %s, map[m]: %s, mapR[m]: %s", move, map[move], mapR[move]) + modal:bind(mapping.move[1], key, + function() growFullyModals[move]:enter(); self:resize(map[move]) end, + function() growFullyModals[move]:exit() end) end self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.resize[1], From dee6c44b2d4c53117de0c684c424b1981b004fab Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Fri, 16 Nov 2018 09:05:46 +1100 Subject: [PATCH 37/41] bugfix: support holding move keys to keep moving or growing --- MiroWindowsManager.spoon/init.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 532df05..acf2746 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -565,7 +565,8 @@ function obj:bindHotkeys(mapping) for move,key in pairs(self._movingKeys) do modal:bind(mapping.move[1], key, function() growFullyModals[move]:enter(); self:move(move) end, - function() growFullyModals[move]:exit() end) + function() growFullyModals[move]:exit() end, + function() self:move(move) end) end self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.move[1], @@ -585,7 +586,8 @@ function obj:bindHotkeys(mapping) hs.printf("move: %s, map[m]: %s, mapR[m]: %s", move, map[move], mapR[move]) modal:bind(mapping.move[1], key, function() growFullyModals[move]:enter(); self:resize(map[move]) end, - function() growFullyModals[move]:exit() end) + function() growFullyModals[move]:exit() end, + function() self:resize(map[move]) end) end self.hotkeys[#self.hotkeys + 1] = hs.hotkey.bind( mapping.resize[1], From dcb3ffc107b07af137b7edac38fc2b25a5794238 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Fri, 16 Nov 2018 09:13:21 +1100 Subject: [PATCH 38/41] minor: cleanup --- MiroWindowsManager.spoon/init.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index acf2746..2e9a688 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -581,9 +581,7 @@ function obj:bindHotkeys(mapping) function modal:entered() logger.i("Resize Mode on") end function modal:exited() logger.i("Resize Mode off") end local map = { left = 'thinner', right = 'wider', down = 'shorter', up = 'taller' } - local mapR = {}; for k,v in pairs(map) do mapR[v] = k end for move,key in pairs(self._movingKeys) do - hs.printf("move: %s, map[m]: %s, mapR[m]: %s", move, map[move], mapR[move]) modal:bind(mapping.move[1], key, function() growFullyModals[move]:enter(); self:resize(map[move]) end, function() growFullyModals[move]:exit() end, From 29fcc3c7f2832dca27f089493f078547199f044a Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Tue, 25 Dec 2018 20:18:19 -0800 Subject: [PATCH 39/41] HS docs are broken and can't do word wrapping. [sad face] --- MiroWindowsManager.spoon/init.lua | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 2e9a688..7309b02 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -17,8 +17,7 @@ --- === MiroWindowsManager === --- ---- With this Spoon you will be able to move the window in halves and in corners using your keyboard and mainly using ---- arrows. You would also be able to resize them by thirds, quarters, or halves. +--- With this Spoon you will be able to move the window in halves and in corners using your keyboard and mainly using arrows. You would also be able to resize them by thirds, quarters, or halves. --- Official homepage for more info and documentation: --- [https://github.com/miromannino/miro-windows-manager](https://github.com/miromannino/miro-windows-manager) --- @@ -68,16 +67,14 @@ obj.sizes = {2, 3, 3/2} obj.fullScreenSizes = {1, 2, 'c'} -- Comment: Lots of work here to save users a little work. Previous versions required users to call --- MiroWindowsManager:start() every time they changed GRID. The metatable work here watches for those changes and does --- the work :start() would have done. +-- MiroWindowsManager:start() every time they changed GRID. The metatable work here watches for those changes and does the work :start() would have done. package.path = package.path..";Spoons/".. ... ..".spoon/?.lua" require('extend_GRID').extend(obj, logger) --- MiroWindowsManager.GRID --- Variable --- The screen's grid size. ---- Make sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give ---- integers. +--- Make sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give integers. obj.GRID = { w = 24, h = 24, margins = hs.geometry.point(0,0) } function obj.GRID.cell() return hs.geometry(obj.GRID.margins, hs.geometry.size(obj.GRID.w, obj.GRID.h)) @@ -86,14 +83,13 @@ end --- MiroWindowsManager.pushToNextScreen --- Variable ---- Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge. +--- Boolean value to decide whether or not to move the window on the next screen if the window is moved the screen edge. obj.pushToNextScreen = false --- MiroWindowsManager.resizeRate --- Variable ---- Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made ---- taller/wider (or shorter/thinner) in 5% increments. +--- Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made taller/wider (or shorter/thinner) in 5% increments. obj.resizeRate = 1.05 -- ## Internal @@ -467,11 +463,8 @@ obj.hotkeys = {} --- * down: for the down action (usually `{hyper, "down"}`) --- * fullscreen: for the full-screen action (e.g. `{hyper, "f"}`) --- * center: for the center action (e.g. `{hyper, "c"}`) ---- * move: for the move action (e.g. `{hyper, "v"}`). The move action is active as soon as the hotkey is pressed. ---- While active the left, right, up or down keys can be used (these are configured by the actions above). ---- * resize: for the resize action (e.g. `{hyper, "d"}`). The resize action is active as soon as the hotkey is ---- pressed. While active the left, right, up or down keys can be used (these are configured by the actions ---- above). +--- * move: for the move action (e.g. `{hyper, "v"}`). The move action is active as soon as the hotkey is pressed. While active the left, right, up or down keys can be used (these are configured by the actions above). +--- * resize: for the resize action (e.g. `{hyper, "d"}`). The resize action is active as soon as the hotkey is pressed. While active the left, right, up or down keys can be used (these are configured by the actions above). --- --- A configuration example: --- ``` lua From 887b6f7120033e0f3cb6bb60ed56fc7faef398cf Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Tue, 25 Dec 2018 20:19:45 -0800 Subject: [PATCH 40/41] Sticky sides (optionally stick to a bound side when shrinking windows). --- MiroWindowsManager.spoon/init.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/MiroWindowsManager.spoon/init.lua b/MiroWindowsManager.spoon/init.lua index 7309b02..c99670f 100644 --- a/MiroWindowsManager.spoon/init.lua +++ b/MiroWindowsManager.spoon/init.lua @@ -87,6 +87,12 @@ end obj.pushToNextScreen = false +--- MiroWindowsManager.stickySides +--- Variable +--- Boolean value to decide whether or not to stick the window to the edge of the screen if shrinking it would detatch it from the screen edge. +obj.stickySides = false + + --- MiroWindowsManager.resizeRate --- Variable --- Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made taller/wider (or shorter/thinner) in 5% increments. @@ -201,6 +207,18 @@ function obj:resize(growth) fr = fr:intersect(frontmostScreen():frame()) -- avoid sizing out of bounds + if self.stickySides then + if growth == 'shorter' and self:currentlyBound('up') then + fr.y = 0 + elseif growth == 'shorter' and self:currentlyBound('down') then + fr.y = fr.y + growthDiff / 2 + elseif growth == 'thinner' and self:currentlyBound('left') then + fr.x = 0 + elseif growth == 'thinner' and self:currentlyBound('right') then + fr.x = fr.x + growthDiff / 2 + end + end + w:setFrame(fr) return self end From fb9ac6f66b0f63186564b3e8fc5d299742c2b546 Mon Sep 17 00:00:00 2001 From: Matthew Fallshaw Date: Thu, 4 Jul 2019 10:25:01 -0700 Subject: [PATCH 41/41] update docs --- MiroWindowsManager.spoon/docs.json | 284 ++++++++++++++++------------- 1 file changed, 157 insertions(+), 127 deletions(-) diff --git a/MiroWindowsManager.spoon/docs.json b/MiroWindowsManager.spoon/docs.json index 5a08201..3b4320a 100644 --- a/MiroWindowsManager.spoon/docs.json +++ b/MiroWindowsManager.spoon/docs.json @@ -11,15 +11,15 @@ ], "Variable" : [ { - "def" : "MiroWindowsManager.sizes", + "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size. \nMake sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.", "stripped_doc" : [ "The sizes that the window can have. ", "The sizes are expressed as dividend of the entire screen's size. ", "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size. ", "Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers." ], - "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size. \nMake sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.", - "desc" : "The sizes that the window can have.", + "def" : "MiroWindowsManager.sizes", + "name" : "sizes", "notes" : [ ], @@ -28,13 +28,13 @@ "returns" : [ ], - "name" : "sizes", + "desc" : "The sizes that the window can have.", "parameters" : [ ] }, { - "def" : "MiroWindowsManager.fullScreenSizes", + "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4 and 1\/2 of the total screen's size. \nMake sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.\nUse 'c' for the original size and shape of the window before starting to move it.", "stripped_doc" : [ "The sizes that the window can have in full-screen. ", "The sizes are expressed as dividend of the entire screen's size. ", @@ -42,8 +42,8 @@ "Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.", "Use 'c' for the original size and shape of the window before starting to move it." ], - "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4 and 1\/2 of the total screen's size. \nMake sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.\nUse 'c' for the original size and shape of the window before starting to move it.", - "desc" : "The sizes that the window can have in full-screen.", + "def" : "MiroWindowsManager.fullScreenSizes", + "name" : "fullScreenSizes", "notes" : [ ], @@ -52,20 +52,19 @@ "returns" : [ ], - "name" : "fullScreenSizes", + "desc" : "The sizes that the window can have in full-screen.", "parameters" : [ ] }, { - "def" : "MiroWindowsManager.GRID", + "doc" : "The screen's grid size. \nMake sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give integers.", "stripped_doc" : [ "The screen's grid size. ", - "Make sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give", - "integers." + "Make sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give integers." ], - "doc" : "The screen's grid size. \nMake sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give\nintegers.", - "desc" : "The screen's grid size.", + "def" : "MiroWindowsManager.GRID", + "name" : "GRID", "notes" : [ ], @@ -74,18 +73,18 @@ "returns" : [ ], - "name" : "GRID", + "desc" : "The screen's grid size.", "parameters" : [ ] }, { - "def" : "MiroWindowsManager.pushToNextScreen", + "doc" : "Boolean value to decide whether or not to move the window on the next screen if the window is moved the screen edge.", "stripped_doc" : [ - "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge." + "Boolean value to decide whether or not to move the window on the next screen if the window is moved the screen edge." ], - "doc" : "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge.", - "desc" : "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge.", + "def" : "MiroWindowsManager.pushToNextScreen", + "name" : "pushToNextScreen", "notes" : [ ], @@ -94,28 +93,47 @@ "returns" : [ ], - "name" : "pushToNextScreen", + "desc" : "Boolean value to decide whether or not to move the window on the next screen if the window is moved the screen edge.", "parameters" : [ ] }, { - "def" : "MiroWindowsManager.resizeRate", + "doc" : "Boolean value to decide whether or not to stick the window to the edge of the screen if shrinking it would detatch it from the screen edge.", "stripped_doc" : [ - "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made", - "taller\/wider (or shorter\/thinner) in 5% increments." + "Boolean value to decide whether or not to stick the window to the edge of the screen if shrinking it would detatch it from the screen edge." ], - "doc" : "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made\ntaller\/wider (or shorter\/thinner) in 5% increments.", - "desc" : "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made", + "def" : "MiroWindowsManager.stickySides", + "name" : "stickySides", "notes" : [ ], - "signature" : "MiroWindowsManager.resizeRate", + "signature" : "MiroWindowsManager.stickySides", "type" : "Variable", "returns" : [ ], + "desc" : "Boolean value to decide whether or not to stick the window to the edge of the screen if shrinking it would detatch it from the screen edge.", + "parameters" : [ + + ] + }, + { + "doc" : "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made taller\/wider (or shorter\/thinner) in 5% increments.", + "stripped_doc" : [ + "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made taller\/wider (or shorter\/thinner) in 5% increments." + ], + "def" : "MiroWindowsManager.resizeRate", "name" : "resizeRate", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.resizeRate", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made taller\/wider (or shorter\/thinner) in 5% increments.", "parameters" : [ ] @@ -124,24 +142,24 @@ "stripped_doc" : [ ], + "desc" : "With this Spoon you will be able to move the window in halves and in corners using your keyboard and mainly using arrows. You would also be able to resize them by thirds, quarters, or halves.", "Deprecated" : [ ], "type" : "Module", - "desc" : "With this Spoon you will be able to move the window in halves and in corners using your keyboard and mainly using", "Constructor" : [ ], - "doc" : "With this Spoon you will be able to move the window in halves and in corners using your keyboard and mainly using\narrows. You would also be able to resize them by thirds, quarters, or halves. \nOfficial homepage for more info and documentation:\n[https:\/\/github.com\/miromannino\/miro-windows-manager](https:\/\/github.com\/miromannino\/miro-windows-manager)\n\nNOTE: This Spoon sets `hs.grid` globals with `hs.grid.setGrid()`, `hs.grid.MARGINX`, and `hs.grid.MARGINY`.\nChanging MiroWindowsManager.GRID will change these globals.\n\nDownload:\nhttps:\/\/github.com\/miromannino\/miro-windows-manager\/raw\/master\/MiroWindowsManager.spoon.zip", + "doc" : "With this Spoon you will be able to move the window in halves and in corners using your keyboard and mainly using arrows. You would also be able to resize them by thirds, quarters, or halves. \nOfficial homepage for more info and documentation:\n[https:\/\/github.com\/miromannino\/miro-windows-manager](https:\/\/github.com\/miromannino\/miro-windows-manager)\n\nNOTE: This Spoon sets `hs.grid` globals with `hs.grid.setGrid()`, `hs.grid.MARGINX`, and `hs.grid.MARGINY`.\nChanging MiroWindowsManager.GRID will change these globals.\n\nDownload:\nhttps:\/\/github.com\/miromannino\/miro-windows-manager\/raw\/master\/MiroWindowsManager.spoon.zip", "Method" : [ { - "def" : "MiroWindowsManager:move(side)", + "doc" : "Move the frontmost window up, down, left, right. \n\nParameters:\n * side - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", "stripped_doc" : [ "Move the frontmost window up, down, left, right. ", "" ], - "doc" : "Move the frontmost window up, down, left, right. \n\nParameters:\n * side - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", - "desc" : "Move the frontmost window up, down, left, right.", + "def" : "MiroWindowsManager:move(side)", + "name" : "move", "notes" : [ ], @@ -150,20 +168,20 @@ "returns" : [ " * The MiroWindowsManager object" ], - "name" : "move", + "desc" : "Move the frontmost window up, down, left, right.", "parameters" : [ " * side - 'up', 'down', 'left', or 'right'", "" ] }, { - "def" : "MiroWindowsManager:resize(growth)", + "doc" : "Resize the frontmost window taller, shorter, wider, or thinner.\n\nParameters:\n * growth - 'taller', 'shorter', 'wider', or 'thinner'\n\nReturns:\n * The MiroWindowsManager object", "stripped_doc" : [ "Resize the frontmost window taller, shorter, wider, or thinner.", "" ], - "doc" : "Resize the frontmost window taller, shorter, wider, or thinner.\n\nParameters:\n * growth - 'taller', 'shorter', 'wider', or 'thinner'\n\nReturns:\n * The MiroWindowsManager object", - "desc" : "Resize the frontmost window taller, shorter, wider, or thinner.", + "def" : "MiroWindowsManager:resize(growth)", + "name" : "resize", "notes" : [ ], @@ -172,20 +190,20 @@ "returns" : [ " * The MiroWindowsManager object" ], - "name" : "resize", + "desc" : "Resize the frontmost window taller, shorter, wider, or thinner.", "parameters" : [ " * growth - 'taller', 'shorter', 'wider', or 'thinner'", "" ] }, { - "def" : "MiroWindowsManager:growFully(growth)", + "doc" : "Grow the frontmost window to full width \/ height.\n\nParameters:\n * dimension - 'h', or 'w'\n\nReturns:\n * The MiroWindowsManager object", "stripped_doc" : [ "Grow the frontmost window to full width \/ height.", "" ], - "doc" : "Grow the frontmost window to full width \/ height.\n\nParameters:\n * dimension - 'h', or 'w'\n\nReturns:\n * The MiroWindowsManager object", - "desc" : "Grow the frontmost window to full width \/ height.", + "def" : "MiroWindowsManager:growFully(growth)", + "name" : "growFully", "notes" : [ ], @@ -194,21 +212,21 @@ "returns" : [ " * The MiroWindowsManager object" ], - "name" : "growFully", + "desc" : "Grow the frontmost window to full width \/ height.", "parameters" : [ " * dimension - 'h', or 'w'", "" ] }, { - "def" : "MiroWindowsManager:go(move)", + "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \n\nParameters:\n * move - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", "stripped_doc" : [ "Move to screen edge, or cycle to next horizontal or vertical size if already there. ", "Tap both directions to go full width \/ height. ", "" ], - "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \n\nParameters:\n * move - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", - "desc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there.", + "def" : "MiroWindowsManager:go(move)", + "name" : "go", "notes" : [ ], @@ -217,20 +235,20 @@ "returns" : [ " * The MiroWindowsManager object" ], - "name" : "go", + "desc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there.", "parameters" : [ " * move - 'up', 'down', 'left', or 'right'", "" ] }, { - "def" : "MiroWindowsManager:fullscreen()", + "doc" : "Fullscreen, or cycle to next fullscreen option\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", "stripped_doc" : [ "Fullscreen, or cycle to next fullscreen option", "" ], - "doc" : "Fullscreen, or cycle to next fullscreen option\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", - "desc" : "Fullscreen, or cycle to next fullscreen option", + "def" : "MiroWindowsManager:fullscreen()", + "name" : "fullscreen", "notes" : [ ], @@ -239,20 +257,20 @@ "returns" : [ " * The MiroWindowsManager object" ], - "name" : "fullscreen", + "desc" : "Fullscreen, or cycle to next fullscreen option", "parameters" : [ " * None.", "" ] }, { - "def" : "MiroWindowsManager:center()", + "doc" : "Center\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", "stripped_doc" : [ "Center", "" ], - "doc" : "Center\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", - "desc" : "Center", + "def" : "MiroWindowsManager:center()", + "name" : "center", "notes" : [ ], @@ -261,20 +279,20 @@ "returns" : [ " * The MiroWindowsManager object" ], - "name" : "center", + "desc" : "Center", "parameters" : [ " * None.", "" ] }, { - "def" : "MiroWindowsManager:bindHotkeys()", + "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n * center: for the center action (e.g. `{hyper, \"c\"}`)\n * move: for the move action (e.g. `{hyper, \"v\"}`). The move action is active as soon as the hotkey is pressed. While active the left, right, up or down keys can be used (these are configured by the actions above). \n * resize: for the resize action (e.g. `{hyper, \"d\"}`). The resize action is active as soon as the hotkey is pressed. While active the left, right, up or down keys can be used (these are configured by the actions above).\n\nA configuration example:\n``` lua\nlocal hyper = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {hyper, \"up\"},\n down = {hyper, \"down\"},\n left = {hyper, \"left\"},\n right = {hyper, \"right\"},\n fullscreen = {hyper, \"f\"},\n center = {hyper, \"c\"},\n move = {hyper, \"v\"},\n resize = {hyper, \"d\" }\n})\n```\n\nIn this example ctrl+alt+cmd+up will perform the 'up' action.\nPressing ctrl+alt+cmd+c the window will be centered.\nPressing ctrl+alt+cmd+f the window will be maximized.\nKeeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up, down, left, and right.\nKeeping ctrl+alt+cmd+d pressed you can resize the window using the arrow keys up, down, left, and right.", "stripped_doc" : [ "Binds hotkeys for Miro's Windows Manager", "" ], - "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n * center: for the center action (e.g. `{hyper, \"c\"}`)\n * move: for the move action (e.g. `{hyper, \"v\"}`). The move action is active as soon as the hotkey is pressed.\n While active the left, right, up or down keys can be used (these are configured by the actions above). \n * resize: for the resize action (e.g. `{hyper, \"d\"}`). The resize action is active as soon as the hotkey is\n pressed. While active the left, right, up or down keys can be used (these are configured by the actions\n above).\n\nA configuration example:\n``` lua\nlocal hyper = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {hyper, \"up\"},\n down = {hyper, \"down\"},\n left = {hyper, \"left\"},\n right = {hyper, \"right\"},\n fullscreen = {hyper, \"f\"},\n center = {hyper, \"c\"},\n move = {hyper, \"v\"},\n resize = {hyper, \"d\" }\n})\n```\n\nIn this example ctrl+alt+cmd+up will perform the 'up' action.\nPressing ctrl+alt+cmd+c the window will be centered.\nPressing ctrl+alt+cmd+f the window will be maximized.\nKeeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up, down, left, and right.\nKeeping ctrl+alt+cmd+d pressed you can resize the window using the arrow keys up, down, left, and right.", - "desc" : "Binds hotkeys for Miro's Windows Manager", + "def" : "MiroWindowsManager:bindHotkeys()", + "name" : "bindHotkeys", "notes" : [ ], @@ -283,7 +301,7 @@ "returns" : [ ], - "name" : "bindHotkeys", + "desc" : "Binds hotkeys for Miro's Windows Manager", "parameters" : [ " * mapping - A table containing hotkey details for the following items:", " * left: for the left action (usually `{hyper, \"left\"}`)", @@ -292,11 +310,8 @@ " * down: for the down action (usually `{hyper, \"down\"}`)", " * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)", " * center: for the center action (e.g. `{hyper, \"c\"}`)", - " * move: for the move action (e.g. `{hyper, \"v\"}`). The move action is active as soon as the hotkey is pressed.", - " While active the left, right, up or down keys can be used (these are configured by the actions above). ", - " * resize: for the resize action (e.g. `{hyper, \"d\"}`). The resize action is active as soon as the hotkey is", - " pressed. While active the left, right, up or down keys can be used (these are configured by the actions", - " above).", + " * move: for the move action (e.g. `{hyper, \"v\"}`). The move action is active as soon as the hotkey is pressed. While active the left, right, up or down keys can be used (these are configured by the actions above). ", + " * resize: for the resize action (e.g. `{hyper, \"d\"}`). The resize action is active as soon as the hotkey is pressed. While active the left, right, up or down keys can be used (these are configured by the actions above).", "", "A configuration example:", "``` lua", @@ -321,12 +336,12 @@ ] }, { - "def" : "MiroWindowsManager:init()", + "doc" : "Currently does nothing (implemented so that treating this Spoon like others won't cause errors).", "stripped_doc" : [ "Currently does nothing (implemented so that treating this Spoon like others won't cause errors)." ], - "doc" : "Currently does nothing (implemented so that treating this Spoon like others won't cause errors).", - "desc" : "Currently does nothing (implemented so that treating this Spoon like others won't cause errors).", + "def" : "MiroWindowsManager:init()", + "name" : "init", "notes" : [ ], @@ -335,7 +350,7 @@ "returns" : [ ], - "name" : "init", + "desc" : "Currently does nothing (implemented so that treating this Spoon like others won't cause errors).", "parameters" : [ ] @@ -346,14 +361,13 @@ ], "items" : [ { - "def" : "MiroWindowsManager.GRID", + "doc" : "The screen's grid size. \nMake sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give integers.", "stripped_doc" : [ "The screen's grid size. ", - "Make sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give", - "integers." + "Make sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give integers." ], - "doc" : "The screen's grid size. \nMake sure that the numbers in MiroWindowsManager.sizes and MiroWindowsManager.fullScreenSizes divide h and w to give\nintegers.", - "desc" : "The screen's grid size.", + "def" : "MiroWindowsManager.GRID", + "name" : "GRID", "notes" : [ ], @@ -362,13 +376,13 @@ "returns" : [ ], - "name" : "GRID", + "desc" : "The screen's grid size.", "parameters" : [ ] }, { - "def" : "MiroWindowsManager.fullScreenSizes", + "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4 and 1\/2 of the total screen's size. \nMake sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.\nUse 'c' for the original size and shape of the window before starting to move it.", "stripped_doc" : [ "The sizes that the window can have in full-screen. ", "The sizes are expressed as dividend of the entire screen's size. ", @@ -376,8 +390,8 @@ "Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.", "Use 'c' for the original size and shape of the window before starting to move it." ], - "doc" : "The sizes that the window can have in full-screen. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{1, 4\/3, 2}` means that it can be 1\/1 (hence full screen), 3\/4 and 1\/2 of the total screen's size. \nMake sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.\nUse 'c' for the original size and shape of the window before starting to move it.", - "desc" : "The sizes that the window can have in full-screen.", + "def" : "MiroWindowsManager.fullScreenSizes", + "name" : "fullScreenSizes", "notes" : [ ], @@ -386,18 +400,18 @@ "returns" : [ ], - "name" : "fullScreenSizes", + "desc" : "The sizes that the window can have in full-screen.", "parameters" : [ ] }, { - "def" : "MiroWindowsManager.pushToNextScreen", + "doc" : "Boolean value to decide whether or not to move the window on the next screen if the window is moved the screen edge.", "stripped_doc" : [ - "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge." + "Boolean value to decide whether or not to move the window on the next screen if the window is moved the screen edge." ], - "doc" : "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge.", - "desc" : "Boolean value to decide wether or not to move the window on the next screen if the window is moved the screen edge.", + "def" : "MiroWindowsManager.pushToNextScreen", + "name" : "pushToNextScreen", "notes" : [ ], @@ -406,19 +420,18 @@ "returns" : [ ], - "name" : "pushToNextScreen", + "desc" : "Boolean value to decide whether or not to move the window on the next screen if the window is moved the screen edge.", "parameters" : [ ] }, { - "def" : "MiroWindowsManager.resizeRate", + "doc" : "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made taller\/wider (or shorter\/thinner) in 5% increments.", "stripped_doc" : [ - "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made", - "taller\/wider (or shorter\/thinner) in 5% increments." + "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made taller\/wider (or shorter\/thinner) in 5% increments." ], - "doc" : "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made\ntaller\/wider (or shorter\/thinner) in 5% increments.", - "desc" : "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made", + "def" : "MiroWindowsManager.resizeRate", + "name" : "resizeRate", "notes" : [ ], @@ -427,21 +440,21 @@ "returns" : [ ], - "name" : "resizeRate", + "desc" : "Float value to decide the rate at which to resize windows. A value of 1.05 means that the window is made taller\/wider (or shorter\/thinner) in 5% increments.", "parameters" : [ ] }, { - "def" : "MiroWindowsManager.sizes", + "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size. \nMake sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.", "stripped_doc" : [ "The sizes that the window can have. ", "The sizes are expressed as dividend of the entire screen's size. ", "For example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size. ", "Make sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers." ], - "doc" : "The sizes that the window can have. \nThe sizes are expressed as dividend of the entire screen's size. \nFor example `{2, 3, 3\/2}` means that it can be 1\/2, 1\/3 and 2\/3 of the total screen's size. \nMake sure that these numbers divide both dimensions of MiroWindowsManager.GRID to give integers.", - "desc" : "The sizes that the window can have.", + "def" : "MiroWindowsManager.sizes", + "name" : "sizes", "notes" : [ ], @@ -450,19 +463,39 @@ "returns" : [ ], - "name" : "sizes", + "desc" : "The sizes that the window can have.", "parameters" : [ ] }, { - "def" : "MiroWindowsManager:bindHotkeys()", + "doc" : "Boolean value to decide whether or not to stick the window to the edge of the screen if shrinking it would detatch it from the screen edge.", + "stripped_doc" : [ + "Boolean value to decide whether or not to stick the window to the edge of the screen if shrinking it would detatch it from the screen edge." + ], + "def" : "MiroWindowsManager.stickySides", + "name" : "stickySides", + "notes" : [ + + ], + "signature" : "MiroWindowsManager.stickySides", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "Boolean value to decide whether or not to stick the window to the edge of the screen if shrinking it would detatch it from the screen edge.", + "parameters" : [ + + ] + }, + { + "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n * center: for the center action (e.g. `{hyper, \"c\"}`)\n * move: for the move action (e.g. `{hyper, \"v\"}`). The move action is active as soon as the hotkey is pressed. While active the left, right, up or down keys can be used (these are configured by the actions above). \n * resize: for the resize action (e.g. `{hyper, \"d\"}`). The resize action is active as soon as the hotkey is pressed. While active the left, right, up or down keys can be used (these are configured by the actions above).\n\nA configuration example:\n``` lua\nlocal hyper = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {hyper, \"up\"},\n down = {hyper, \"down\"},\n left = {hyper, \"left\"},\n right = {hyper, \"right\"},\n fullscreen = {hyper, \"f\"},\n center = {hyper, \"c\"},\n move = {hyper, \"v\"},\n resize = {hyper, \"d\" }\n})\n```\n\nIn this example ctrl+alt+cmd+up will perform the 'up' action.\nPressing ctrl+alt+cmd+c the window will be centered.\nPressing ctrl+alt+cmd+f the window will be maximized.\nKeeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up, down, left, and right.\nKeeping ctrl+alt+cmd+d pressed you can resize the window using the arrow keys up, down, left, and right.", "stripped_doc" : [ "Binds hotkeys for Miro's Windows Manager", "" ], - "doc" : "Binds hotkeys for Miro's Windows Manager\n\nParameters:\n * mapping - A table containing hotkey details for the following items:\n * left: for the left action (usually `{hyper, \"left\"}`)\n * right: for the right action (usually `{hyper, \"right\"}`)\n * up: for the up action (usually {hyper, \"up\"})\n * down: for the down action (usually `{hyper, \"down\"}`)\n * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)\n * center: for the center action (e.g. `{hyper, \"c\"}`)\n * move: for the move action (e.g. `{hyper, \"v\"}`). The move action is active as soon as the hotkey is pressed.\n While active the left, right, up or down keys can be used (these are configured by the actions above). \n * resize: for the resize action (e.g. `{hyper, \"d\"}`). The resize action is active as soon as the hotkey is\n pressed. While active the left, right, up or down keys can be used (these are configured by the actions\n above).\n\nA configuration example:\n``` lua\nlocal hyper = {\"ctrl\", \"alt\", \"cmd\"}\nspoon.MiroWindowsManager:bindHotkeys({\n up = {hyper, \"up\"},\n down = {hyper, \"down\"},\n left = {hyper, \"left\"},\n right = {hyper, \"right\"},\n fullscreen = {hyper, \"f\"},\n center = {hyper, \"c\"},\n move = {hyper, \"v\"},\n resize = {hyper, \"d\" }\n})\n```\n\nIn this example ctrl+alt+cmd+up will perform the 'up' action.\nPressing ctrl+alt+cmd+c the window will be centered.\nPressing ctrl+alt+cmd+f the window will be maximized.\nKeeping ctrl+alt+cmd+v pressed you can move the window using the arrow keys up, down, left, and right.\nKeeping ctrl+alt+cmd+d pressed you can resize the window using the arrow keys up, down, left, and right.", - "desc" : "Binds hotkeys for Miro's Windows Manager", + "def" : "MiroWindowsManager:bindHotkeys()", + "name" : "bindHotkeys", "notes" : [ ], @@ -471,7 +504,7 @@ "returns" : [ ], - "name" : "bindHotkeys", + "desc" : "Binds hotkeys for Miro's Windows Manager", "parameters" : [ " * mapping - A table containing hotkey details for the following items:", " * left: for the left action (usually `{hyper, \"left\"}`)", @@ -480,11 +513,8 @@ " * down: for the down action (usually `{hyper, \"down\"}`)", " * fullscreen: for the full-screen action (e.g. `{hyper, \"f\"}`)", " * center: for the center action (e.g. `{hyper, \"c\"}`)", - " * move: for the move action (e.g. `{hyper, \"v\"}`). The move action is active as soon as the hotkey is pressed.", - " While active the left, right, up or down keys can be used (these are configured by the actions above). ", - " * resize: for the resize action (e.g. `{hyper, \"d\"}`). The resize action is active as soon as the hotkey is", - " pressed. While active the left, right, up or down keys can be used (these are configured by the actions", - " above).", + " * move: for the move action (e.g. `{hyper, \"v\"}`). The move action is active as soon as the hotkey is pressed. While active the left, right, up or down keys can be used (these are configured by the actions above). ", + " * resize: for the resize action (e.g. `{hyper, \"d\"}`). The resize action is active as soon as the hotkey is pressed. While active the left, right, up or down keys can be used (these are configured by the actions above).", "", "A configuration example:", "``` lua", @@ -509,13 +539,13 @@ ] }, { - "def" : "MiroWindowsManager:center()", + "doc" : "Center\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", "stripped_doc" : [ "Center", "" ], - "doc" : "Center\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", - "desc" : "Center", + "def" : "MiroWindowsManager:center()", + "name" : "center", "notes" : [ ], @@ -524,20 +554,20 @@ "returns" : [ " * The MiroWindowsManager object" ], - "name" : "center", + "desc" : "Center", "parameters" : [ " * None.", "" ] }, { - "def" : "MiroWindowsManager:fullscreen()", + "doc" : "Fullscreen, or cycle to next fullscreen option\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", "stripped_doc" : [ "Fullscreen, or cycle to next fullscreen option", "" ], - "doc" : "Fullscreen, or cycle to next fullscreen option\n\nParameters:\n * None.\n\nReturns:\n * The MiroWindowsManager object", - "desc" : "Fullscreen, or cycle to next fullscreen option", + "def" : "MiroWindowsManager:fullscreen()", + "name" : "fullscreen", "notes" : [ ], @@ -546,21 +576,21 @@ "returns" : [ " * The MiroWindowsManager object" ], - "name" : "fullscreen", + "desc" : "Fullscreen, or cycle to next fullscreen option", "parameters" : [ " * None.", "" ] }, { - "def" : "MiroWindowsManager:go(move)", + "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \n\nParameters:\n * move - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", "stripped_doc" : [ "Move to screen edge, or cycle to next horizontal or vertical size if already there. ", "Tap both directions to go full width \/ height. ", "" ], - "doc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there. \nTap both directions to go full width \/ height. \n\nParameters:\n * move - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", - "desc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there.", + "def" : "MiroWindowsManager:go(move)", + "name" : "go", "notes" : [ ], @@ -569,20 +599,20 @@ "returns" : [ " * The MiroWindowsManager object" ], - "name" : "go", + "desc" : "Move to screen edge, or cycle to next horizontal or vertical size if already there.", "parameters" : [ " * move - 'up', 'down', 'left', or 'right'", "" ] }, { - "def" : "MiroWindowsManager:growFully(growth)", + "doc" : "Grow the frontmost window to full width \/ height.\n\nParameters:\n * dimension - 'h', or 'w'\n\nReturns:\n * The MiroWindowsManager object", "stripped_doc" : [ "Grow the frontmost window to full width \/ height.", "" ], - "doc" : "Grow the frontmost window to full width \/ height.\n\nParameters:\n * dimension - 'h', or 'w'\n\nReturns:\n * The MiroWindowsManager object", - "desc" : "Grow the frontmost window to full width \/ height.", + "def" : "MiroWindowsManager:growFully(growth)", + "name" : "growFully", "notes" : [ ], @@ -591,19 +621,19 @@ "returns" : [ " * The MiroWindowsManager object" ], - "name" : "growFully", + "desc" : "Grow the frontmost window to full width \/ height.", "parameters" : [ " * dimension - 'h', or 'w'", "" ] }, { - "def" : "MiroWindowsManager:init()", + "doc" : "Currently does nothing (implemented so that treating this Spoon like others won't cause errors).", "stripped_doc" : [ "Currently does nothing (implemented so that treating this Spoon like others won't cause errors)." ], - "doc" : "Currently does nothing (implemented so that treating this Spoon like others won't cause errors).", - "desc" : "Currently does nothing (implemented so that treating this Spoon like others won't cause errors).", + "def" : "MiroWindowsManager:init()", + "name" : "init", "notes" : [ ], @@ -612,19 +642,19 @@ "returns" : [ ], - "name" : "init", + "desc" : "Currently does nothing (implemented so that treating this Spoon like others won't cause errors).", "parameters" : [ ] }, { - "def" : "MiroWindowsManager:move(side)", + "doc" : "Move the frontmost window up, down, left, right. \n\nParameters:\n * side - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", "stripped_doc" : [ "Move the frontmost window up, down, left, right. ", "" ], - "doc" : "Move the frontmost window up, down, left, right. \n\nParameters:\n * side - 'up', 'down', 'left', or 'right'\n\nReturns:\n * The MiroWindowsManager object", - "desc" : "Move the frontmost window up, down, left, right.", + "def" : "MiroWindowsManager:move(side)", + "name" : "move", "notes" : [ ], @@ -633,20 +663,20 @@ "returns" : [ " * The MiroWindowsManager object" ], - "name" : "move", + "desc" : "Move the frontmost window up, down, left, right.", "parameters" : [ " * side - 'up', 'down', 'left', or 'right'", "" ] }, { - "def" : "MiroWindowsManager:resize(growth)", + "doc" : "Resize the frontmost window taller, shorter, wider, or thinner.\n\nParameters:\n * growth - 'taller', 'shorter', 'wider', or 'thinner'\n\nReturns:\n * The MiroWindowsManager object", "stripped_doc" : [ "Resize the frontmost window taller, shorter, wider, or thinner.", "" ], - "doc" : "Resize the frontmost window taller, shorter, wider, or thinner.\n\nParameters:\n * growth - 'taller', 'shorter', 'wider', or 'thinner'\n\nReturns:\n * The MiroWindowsManager object", - "desc" : "Resize the frontmost window taller, shorter, wider, or thinner.", + "def" : "MiroWindowsManager:resize(growth)", + "name" : "resize", "notes" : [ ], @@ -655,7 +685,7 @@ "returns" : [ " * The MiroWindowsManager object" ], - "name" : "resize", + "desc" : "Resize the frontmost window taller, shorter, wider, or thinner.", "parameters" : [ " * growth - 'taller', 'shorter', 'wider', or 'thinner'", "" @@ -667,4 +697,4 @@ ], "name" : "MiroWindowsManager" } -] +] \ No newline at end of file