├── .envrc ├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── mpv-shot0001.jpg ├── shell.nix └── src ├── assdraw.js └── index.js /.envrc: -------------------------------------------------------------------------------- 1 | use_nix 2 | 3 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | container: nixos/nix:2.3 10 | 11 | steps: 12 | - uses: actions/checkout@v1 13 | - run: nix-shell --run "make" 14 | - uses: actions/upload-artifact@master 15 | with: 16 | name: deliverable 17 | path: dist/cheatsheet.js 18 | - id: get_version 19 | run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/} 20 | if: startsWith(github.ref, 'refs/tags/') 21 | - run: mv dist/cheatsheet.js dist/mpv-cheatsheet-${VERSION}.js 22 | if: startsWith(github.ref, 'refs/tags/') 23 | env: 24 | VERSION: ${{ steps.get_version.outputs.VERSION }} 25 | - name: Release 26 | uses: softprops/action-gh-release@v1 27 | if: startsWith(github.ref, 'refs/tags/') 28 | with: 29 | files: dist/mpv-cheatsheet-${{ steps.get_version.outputs.VERSION }}.js 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | .direnv/ 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # TypeScript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | # next.js build output 64 | .next 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Marica Odagaki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET := dist/cheatsheet.js 2 | COMPILER := browserify 3 | 4 | $(TARGET): $(wildcard src/*.js) 5 | mkdir -p $(@D) 6 | $(COMPILER) --bare src/index.js > $@ 7 | 8 | .PHONY: install 9 | install: $(TARGET) 10 | mkdir -p ~/.config/mpv/scripts/ 11 | cp $< ~/.config/mpv/scripts/ 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mpv-cheatsheet 2 | 3 | ## Installation 4 | 5 | Download the latest release from the [releases page](https://github.com/ento/mpv-cheatsheet/releases/latest). 6 | 7 | It can be passed directly using the `--script` option when launching mpv. 8 | ```sh 9 | mpv video.mp4 --script=mpv-cheatsheet.js 10 | ``` 11 | 12 | or you can save the file in a `scripts` subdirectory in the mpv configuration directory (usually `~/.config/mpv/scripts/` or `C:\Users\\AppData\Roaming\mpv\scripts`; create one if it doesn't exist) and mpv will launch the script every time. [Learn more](https://mpv.io/manual/stable/#script-location). 13 | 14 | ## Usage 15 | 16 | Press `?` to bring up the cheatsheet. 17 | 18 | ![Screenshot](./mpv-shot0001.jpg) 19 | 20 | ## Development 21 | 22 | ### Building locally 23 | 24 | ``` 25 | direnv allow 26 | make 27 | make install # copies built script to ~/.config/mpv/scripts/ 28 | ``` 29 | 30 | ### Versioning 31 | 32 | Version numbers follow the format `v${mpv-version}.${script-version}` 33 | where `script-version` is a single number that resets to 0 when `mpv-version` 34 | is bumped. 35 | -------------------------------------------------------------------------------- /mpv-shot0001.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ento/mpv-cheatsheet/c5f3a6a3c17bf2158283d98b631abe867e99d559/mpv-shot0001.jpg -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} 2 | }: 3 | pkgs.mkShell { 4 | buildInputs = [ 5 | pkgs.nodePackages.browserify 6 | ]; 7 | } 8 | -------------------------------------------------------------------------------- /src/assdraw.js: -------------------------------------------------------------------------------- 1 | // referene: http://docs.aegisub.org/3.2/ASS_Tags/ 2 | // based on mpv's assdraw.lua 3 | 4 | var c = 0.551915024494 // circle approximation 5 | 6 | function Assdraw() { 7 | this.scale = 4; 8 | this.text = "" 9 | } 10 | 11 | Assdraw.SMART_WRAPPING = 0 12 | Assdraw.EOL_WRAPPING = 1 13 | Assdraw.NO_WORD_WRAPPING = 2 14 | Assdraw.SMART_WRAPPING_WIDER = 3 15 | Assdraw.BOTTOM_LEFT = 1 16 | Assdraw.BOTTOM_CENTER = 2 17 | Assdraw.BOTTOM_RIGHT = 3 18 | Assdraw.MIDDLE_LEFT = 4 19 | Assdraw.MIDDLE_CENTER = 5 20 | Assdraw.MIDDLE_RIGHT = 6 21 | Assdraw.TOP_LEFT = 7 22 | Assdraw.TOP_CENTER = 8 23 | Assdraw.TOP_RIGHT = 9 24 | 25 | Assdraw.escape = function(s) { 26 | return s.replace(/([{}])/g, "\\$1") 27 | } 28 | 29 | Assdraw.bolden = function(s) { 30 | return '{\\b1}' + s + '{\\b0}' 31 | } 32 | 33 | Assdraw.prototype.newEvent = function() { 34 | // osd_libass.c adds an event per line 35 | if (this.text.length > 0) { 36 | this.text = this.text + "\n" 37 | } 38 | } 39 | 40 | Assdraw.prototype.override = function(callback) { 41 | this.append('{') 42 | callback.call(this); 43 | this.append('}') 44 | } 45 | 46 | Assdraw.prototype.primaryFillColor = function(hex) { 47 | this.append('\\1c&H' + hex + '&') 48 | return this; 49 | } 50 | 51 | Assdraw.prototype.secondaryFillColor = function(hex) { 52 | this.append('\\2c&H' + hex + '&') 53 | return this; 54 | } 55 | 56 | Assdraw.prototype.borderColor = function(hex) { 57 | this.append('\\3c&H' + hex + '&') 58 | return this; 59 | } 60 | 61 | Assdraw.prototype.shadowColor = function(hex) { 62 | this.append('\\4c&H' + hex + '&') 63 | return this; 64 | } 65 | 66 | Assdraw.prototype.primaryFillAlpha = function(hex) { 67 | this.append('\\1a&H' + hex + '&') 68 | return this; 69 | } 70 | 71 | Assdraw.prototype.secondaryFillAlpha = function(hex) { 72 | this.append('\\2a&H' + hex + '&') 73 | return this; 74 | } 75 | 76 | Assdraw.prototype.borderAlpha = function(hex) { 77 | this.append('\\3a&H' + hex + '&') 78 | return this; 79 | } 80 | 81 | Assdraw.prototype.shadowAlpha = function(hex) { 82 | this.append('\\4a&H' + hex + '&') 83 | return this; 84 | } 85 | 86 | Assdraw.prototype.fontName = function(s) { 87 | this.append('\\fn' + s) 88 | return this; 89 | } 90 | 91 | Assdraw.prototype.fontSize = function(s) { 92 | this.append('\\fs' + s) 93 | return this; 94 | } 95 | 96 | Assdraw.prototype.borderSize = function(n) { 97 | this.append('\\bord' + n) 98 | return this; 99 | } 100 | 101 | Assdraw.prototype.xShadowDistance = function(n) { 102 | this.append('\\xshad' + n) 103 | return this; 104 | } 105 | 106 | Assdraw.prototype.yShadowDistance = function(n) { 107 | this.append('\\yshad' + n) 108 | return this; 109 | } 110 | 111 | Assdraw.prototype.letterSpacing = function(n) { 112 | this.append('\\fsp' + n) 113 | return this; 114 | } 115 | 116 | Assdraw.prototype.wrapStyle = function(n) { 117 | this.append('\\q' + n) 118 | return this; 119 | } 120 | 121 | Assdraw.prototype.drawStart = function() { 122 | this.text = this.text + "{\\p"+ this.scale + "}" 123 | } 124 | 125 | Assdraw.prototype.drawStop = function() { 126 | this.text = this.text + "{\\p0}" 127 | } 128 | 129 | Assdraw.prototype.coord = function(x, y) { 130 | var scale = Math.pow(2, (this.scale - 1)) 131 | var ix = Math.ceil(x * scale) 132 | var iy = Math.ceil(y * scale) 133 | this.text = this.text + " " + ix + " " + iy 134 | } 135 | 136 | Assdraw.prototype.append = function(s) { 137 | this.text = this.text + s 138 | } 139 | 140 | Assdraw.prototype.appendLn = function(s) { 141 | this.append(s + '\\n') 142 | } 143 | 144 | Assdraw.prototype.appendLN = function(s) { 145 | this.append(s + '\\N') 146 | } 147 | 148 | Assdraw.prototype.merge = function(other) { 149 | this.text = this.text + other.text 150 | } 151 | 152 | Assdraw.prototype.pos = function(x, y) { 153 | this.append("\\pos(" + x.toFixed(0) + "," + y.toFixed(0) + ")") 154 | } 155 | 156 | Assdraw.prototype.lineAlignment = function(an) { 157 | this.append("\\an" + an) 158 | } 159 | 160 | Assdraw.prototype.moveTo = function(x, y) { 161 | this.append(" m") 162 | this.coord(x, y) 163 | } 164 | 165 | Assdraw.prototype.lineTo = function(x, y) { 166 | this.append(" l") 167 | this.coord(x, y) 168 | } 169 | 170 | Assdraw.prototype.bezierCurve = function(x1, y1, x2, y2, x3, y3) { 171 | this.append(" b") 172 | this.coord(x1, y1) 173 | this.coord(x2, y2) 174 | this.coord(x3, y3) 175 | } 176 | 177 | 178 | Assdraw.prototype.rectCcw = function(x0, y0, x1, y1) { 179 | this.moveTo(x0, y0) 180 | this.lineTo(x0, y1) 181 | this.lineTo(x1, y1) 182 | this.lineTo(x1, y0) 183 | } 184 | 185 | Assdraw.prototype.rectCw = function(x0, y0, x1, y1) { 186 | this.moveTo(x0, y0) 187 | this.lineTo(x1, y0) 188 | this.lineTo(x1, y1) 189 | this.lineTo(x0, y1) 190 | } 191 | 192 | Assdraw.prototype.hexagonCw = function(x0, y0, x1, y1, r1, r2) { 193 | if (typeof r2 === 'undefined') { 194 | r2 = r1 195 | } 196 | this.moveTo(x0 + r1, y0) 197 | if (x0 != x1) { 198 | this.lineTo(x1 - r2, y0) 199 | } 200 | this.lineTo(x1, y0 + r2) 201 | if (x0 != x1) { 202 | this.lineTo(x1 - r2, y1) 203 | } 204 | this.lineTo(x0 + r1, y1) 205 | this.lineTo(x0, y0 + r1) 206 | } 207 | 208 | Assdraw.prototype.hexagonCcw = function(x0, y0, x1, y1, r1, r2) { 209 | if (typeof r2 === 'undefined') { 210 | r2 = r1 211 | } 212 | this.moveTo(x0 + r1, y0) 213 | this.lineTo(x0, y0 + r1) 214 | this.lineTo(x0 + r1, y1) 215 | if (x0 != x1) { 216 | this.lineTo(x1 - r2, y1) 217 | } 218 | this.lineTo(x1, y0 + r2) 219 | if (x0 != x1) { 220 | this.lineTo(x1 - r2, y0) 221 | } 222 | } 223 | 224 | Assdraw.prototype.roundRectCw = function(ass, x0, y0, x1, y1, r1, r2) { 225 | if (typeof r2 === 'undefined') { 226 | r2 = r1 227 | } 228 | var c1 = c * r1 // circle approximation 229 | var c2 = c * r2 // circle approximation 230 | this.moveTo(x0 + r1, y0) 231 | this.lineTo(x1 - r2, y0) // top line 232 | if (r2 > 0) { 233 | this.bezierCurve(x1 - r2 + c2, y0, x1, y0 + r2 - c2, x1, y0 + r2) // top right corner 234 | } 235 | this.lineTo(x1, y1 - r2) // right line 236 | if (r2 > 0) { 237 | this.bezierCurve(x1, y1 - r2 + c2, x1 - r2 + c2, y1, x1 - r2, y1) // bottom right corner 238 | } 239 | this.lineTo(x0 + r1, y1) // bottom line 240 | if (r1 > 0) { 241 | this.bezierCurve(x0 + r1 - c1, y1, x0, y1 - r1 + c1, x0, y1 - r1) // bottom left corner 242 | } 243 | this.lineTo(x0, y0 + r1) // left line 244 | if (r1 > 0) { 245 | this.bezierCurve(x0, y0 + r1 - c1, x0 + r1 - c1, y0, x0 + r1, y0) // top left corner 246 | } 247 | } 248 | 249 | Assdraw.prototype.roundRectCcw = function(ass, x0, y0, x1, y1, r1, r2) { 250 | if (typeof r2 === 'undefined') { 251 | r2 = r1 252 | } 253 | var c1 = c * r1 // circle approximation 254 | var c2 = c * r2 // circle approximation 255 | this.moveTo(x0 + r1, y0) 256 | if (r1 > 0) { 257 | this.bezierCurve(x0 + r1 - c1, y0, x0, y0 + r1 - c1, x0, y0 + r1) // top left corner 258 | } 259 | this.lineTo(x0, y1 - r1) // left line 260 | if (r1 > 0) { 261 | this.bezierCurve(x0, y1 - r1 + c1, x0 + r1 - c1, y1, x0 + r1, y1) // bottom left corner 262 | } 263 | this.lineTo(x1 - r2, y1) // bottom line 264 | if (r2 > 0) { 265 | this.bezierCurve(x1 - r2 + c2, y1, x1, y1 - r2 + c2, x1, y1 - r2) // bottom right corner 266 | } 267 | this.lineTo(x1, y0 + r2) // right line 268 | if (r2 > 0) { 269 | this.bezierCurve(x1, y0 + r2 - c2, x1 - r2 + c2, y0, x1 - r2, y0) // top right corner 270 | } 271 | } 272 | 273 | module.exports = Assdraw 274 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | var assdraw = require('./assdraw.js') 2 | var shortcuts = [ 3 | { 4 | category: 'Navigation', 5 | shortcuts: [ 6 | {keys: ', / .', effect: 'Seek by frame'}, 7 | {keys: '← / →', effect: 'Seek by 5 seconds'}, 8 | {keys: '↓ / ↑', effect: 'Seek by 1 minute'}, 9 | {keys: '[Shift] PGDWN / PGUP', effect: 'Seek by 10 minutes'}, 10 | {keys: '[Shift] ← / →', effect: 'Seek by 1 second (exact)'}, 11 | {keys: '[Shift] ↓ / ↑', effect: 'Seek by 5 seconds (exact)'}, 12 | {keys: '[Ctrl] ← / →', effect: 'Seek by subtitle'}, 13 | {keys: '[Shift] BACKSPACE', effect: 'Undo last seek'}, 14 | {keys: '[Ctrl+Shift] BACKSPACE', effect: 'Mark current position'}, 15 | {keys: 'l', effect: 'Set/clear A-B loop points'}, 16 | {keys: 'L', effect: 'Toggle infinite looping'}, 17 | {keys: 'PGDWN / PGUP', effect: 'Previous/next chapter'}, 18 | {keys: '< / >', effect: 'Go backward/forward in the playlist'}, 19 | {keys: 'ENTER', effect: 'Go forward in the playlist'}, 20 | {keys: 'F8', effect: 'Show playlist [UI]'}, 21 | ] 22 | }, 23 | { 24 | category: 'Playback', 25 | shortcuts: [ 26 | {keys: 'p / SPACE', effect: 'Pause/unpause'}, 27 | {keys: '[ / ]', effect: 'Decrease/increase speed [10%]'}, 28 | {keys: '{ / }', effect: 'Halve/double speed'}, 29 | {keys: 'BACKSPACE', effect: 'Reset speed'}, 30 | {keys: 'o / P', effect: 'Show progress'}, 31 | {keys: 'O', effect: 'Toggle progress'}, 32 | {keys: 'i / I', effect: 'Show/toggle stats'}, 33 | ] 34 | }, 35 | { 36 | category: 'Subtitle', 37 | shortcuts: [ 38 | {keys: '[Ctrl+Shift] ← / →', effect: 'Adjust subtitle delay [subtitle]'}, 39 | {keys: '[Shift] f / g', effect: 'Adjust subtitle size [0.100]'}, 40 | {keys: 'z / Z', effect: 'Adjust subtitle delay [0.1sec]'}, 41 | {keys: 'v', effect: 'Toggle subtitle visibility'}, 42 | {keys: 'u', effect: 'Toggle subtitle style overrides'}, 43 | {keys: 'V', effect: 'Toggle subtitle VSFilter aspect compatibility mode'}, 44 | {keys: 'r / R', effect: 'Move subtitles up/down'}, 45 | {keys: 'j / J', effect: 'Cycle subtitle'}, 46 | {keys: 'F9', effect: 'Show audio/subtitle list [UI]'}, 47 | ] 48 | }, 49 | { 50 | category: 'Audio', 51 | shortcuts: [ 52 | {keys: 'm', effect: 'Mute sound'}, 53 | {keys: '#', effect: 'Cycle audio track'}, 54 | {keys: '/ / *', effect: 'Decrease/increase volume'}, 55 | {keys: '9 / 0', effect: 'Decrease/increase volume'}, 56 | {keys: '[Ctrl] - / +', effect: 'Decrease/increase audio delay [0.1sec]'}, 57 | {keys: 'F9', effect: 'Show audio/subtitle list [UI]'}, 58 | ] 59 | }, 60 | { 61 | category: 'Video', 62 | shortcuts: [ 63 | {keys: '_', effect: 'Cycle video track'}, 64 | {keys: 'A', effect: 'Cycle aspect ratio'}, 65 | {keys: 'd', effect: 'Toggle deinterlacer'}, 66 | {keys: '[Ctrl] h', effect: 'Toggle hardware video decoding'}, 67 | {keys: 'w / W', effect: 'Decrease/increase pan-and-scan range'}, 68 | {keys: '[Alt] - / +', effect: 'Zoom out/in'}, 69 | {keys: '[Alt] ARROWS', effect: 'Move the video rectangle'}, 70 | {keys: '[Alt] BACKSPACE', effect: 'Reset pan/zoom'}, 71 | {keys: '1 / 2', effect: 'Decrease/increase contrast'}, 72 | {keys: '3 / 4', effect: 'Decrease/increase brightness'}, 73 | {keys: '5 / 6', effect: 'Decrease/increase gamma'}, 74 | {keys: '7 / 8', effect: 'Decrease/increase saturation'}, 75 | ] 76 | }, 77 | { 78 | category: 'Application', 79 | shortcuts: [ 80 | {keys: 'q', effect: 'Quit'}, 81 | {keys: 'Q', effect: 'Save position and quit'}, 82 | {keys: 's', effect: 'Take a screenshot'}, 83 | {keys: 'S', effect: 'Take a screenshot without subtitles'}, 84 | {keys: '[Ctrl] s', effect: 'Take a screenshot as rendered'}, 85 | ] 86 | }, 87 | { 88 | category: 'Window', 89 | shortcuts: [ 90 | {keys: 'f', effect: 'Toggle fullscreen'}, 91 | {keys: '[Command] f', effect: 'Toggle fullscreen [macOS]'}, 92 | {keys: 'ESC', effect: 'Exit fullscreen'}, 93 | {keys: 'T', effect: 'Toggle stay-on-top'}, 94 | {keys: '[Alt] 0', effect: 'Resize window to 0.5x [macOS]'}, 95 | {keys: '[Alt] 1', effect: 'Reset window size [macOS]'}, 96 | {keys: '[Alt] 2', effect: 'Resize window to 2x [macOS]'}, 97 | ] 98 | }, 99 | { 100 | category: 'Multimedia keys', 101 | shortcuts: [ 102 | {keys: 'PAUSE', effect: 'Pause'}, // keyboard with multimedia keys 103 | {keys: 'STOP', effect: 'Quit'}, // keyboard with multimedia keys 104 | {keys: 'PREVIOUS / NEXT', effect: 'Seek 1 minute'}, // keyboard with multimedia keys 105 | ] 106 | }, 107 | ] 108 | 109 | var State = { 110 | active: false, 111 | startLine: 0, 112 | startCategory: 0 113 | } 114 | 115 | var opts = { 116 | font: 'monospace', 117 | 'font-size': 8, 118 | 'usage-font-size': 6, 119 | } 120 | 121 | function repeat(s, num) { 122 | var ret = ''; 123 | for (var i = 0; i < num; i++) { 124 | ret = ret + s; 125 | } 126 | return ret; 127 | } 128 | 129 | function renderCategory(category) { 130 | var lines = [] 131 | lines.push(assdraw.bolden(category.category)) 132 | var maxKeysLength = 0; 133 | category.shortcuts.forEach(function(shortcut) { 134 | if (shortcut.keys.length > maxKeysLength) maxKeysLength = shortcut.keys.length 135 | }) 136 | category.shortcuts.forEach(function(shortcut) { 137 | var padding = repeat(" ", maxKeysLength - shortcut.keys.length) 138 | lines.push(assdraw.escape(shortcut.keys + padding + " " + shortcut.effect)) 139 | }) 140 | return lines 141 | } 142 | 143 | function render() { 144 | var screen = mp.get_osd_size() 145 | if (!State.active) { 146 | mp.set_osd_ass(0, 0, '{}') 147 | return 148 | } 149 | var ass = new assdraw() 150 | ass.newEvent() 151 | ass.override(function() { 152 | this.lineAlignment(assdraw.TOP_LEFT) 153 | this.primaryFillAlpha('00') 154 | this.borderAlpha('00') 155 | this.shadowAlpha('99') 156 | this.primaryFillColor('eeeeee') 157 | this.borderColor('111111') 158 | this.shadowColor('000000') 159 | this.fontName(opts.font) 160 | this.fontSize(opts['font-size']) 161 | this.borderSize(1) 162 | this.xShadowDistance(0) 163 | this.yShadowDistance(1) 164 | this.letterSpacing(0) 165 | this.wrapStyle(assdraw.EOL_WRAPPING) 166 | }) 167 | var mainLines = []; 168 | var pushedCategory = false 169 | shortcuts.forEach(function(category, i) { 170 | if (i < State.startCategory) { 171 | return; 172 | } 173 | pushedCategory = true; 174 | if (pushedCategory) { 175 | mainLines.push("") 176 | } 177 | mainLines.push.apply(mainLines, renderCategory(category)) 178 | }) 179 | mainLines.slice(State.startLine).forEach(function(line) { 180 | ass.appendLN(line); 181 | }) 182 | 183 | ass.newEvent() 184 | var sideLines = renderCategory({ 185 | category: 'usage', 186 | shortcuts: Keybindings 187 | }) 188 | ass.override(function() { 189 | this.lineAlignment(assdraw.TOP_RIGHT) 190 | this.fontSize(opts['usage-font-size']) 191 | }) 192 | sideLines.forEach(function(line) { 193 | ass.appendLN(line); 194 | }) 195 | 196 | mp.set_osd_ass(0, 0, ass.text) 197 | } 198 | 199 | function setActive(active) { 200 | if (active == State.active) return 201 | if (active) { 202 | State.active = true 203 | updateBindings(Keybindings, true) 204 | } else { 205 | State.active = false 206 | updateBindings(Keybindings, false) 207 | } 208 | render() 209 | } 210 | 211 | function updateBindings(bindings, enable) { 212 | bindings.forEach(function(binding, i) { 213 | var name = '__cheatsheet_binding_' + i 214 | if (enable) { 215 | mp.add_forced_key_binding(binding.keys, name, binding.callback, binding.options) 216 | } else { 217 | mp.remove_key_binding(name) 218 | } 219 | }) 220 | } 221 | 222 | var Keybindings = [ 223 | { 224 | keys: 'esc', 225 | effect: 'close', 226 | callback: function() { setActive(false) } 227 | }, 228 | { 229 | keys: '?', 230 | effect: 'close', 231 | callback: function() { setActive(false) } 232 | }, 233 | { 234 | keys: 'j', 235 | effect: 'next line', 236 | callback: function() { 237 | State.startLine += 1 238 | render() 239 | }, 240 | options: 'repeatable' 241 | }, 242 | { 243 | keys: 'k', 244 | effect: 'prev line', 245 | callback: function() { 246 | State.startLine = Math.max(0, State.startLine - 1) 247 | render() 248 | }, 249 | options: 'repeatable' 250 | }, 251 | { 252 | keys: 'n', 253 | effect: 'next category', 254 | callback: function() { 255 | State.startCategory += 1 256 | State.startLine = 0 257 | render() 258 | }, 259 | options: 'repeatable' 260 | }, 261 | { 262 | keys: 'p', 263 | effect: 'prev category', 264 | callback: function() { 265 | State.startCategory = Math.max(0, State.startCategory - 1) 266 | State.startLine = 0 267 | render() 268 | }, 269 | options: 'repeatable' 270 | }, 271 | ] 272 | 273 | mp.add_key_binding('?', 'cheatsheet-enable', function() { setActive(true) }) 274 | 275 | mp.observe_property('osd-width', 'native', render) 276 | mp.observe_property('osd-height', 'native', render) 277 | --------------------------------------------------------------------------------