├── .github └── workflows │ ├── release.yml │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── _config.yml ├── assets ├── Amble-Bold.ttf ├── Amble-Italic.ttf ├── Amble-Regular.ttf ├── Hack-Regular.ttf ├── advent-of-code.md ├── agical.json ├── agical.png ├── aoc2022 │ └── input1.txt ├── application-events.md ├── asterisk.md ├── background.png ├── continuous-deployment.md ├── default-theme.json ├── event-sourcing.md ├── exalted-beer.json ├── exaltedbeer.png ├── ferris.png ├── homebrewing.md ├── raku.md ├── rust.json ├── rust.png ├── rusty-aquarium.md ├── rusty-slider.md ├── rusty.json ├── screenshot.png ├── transitions │ ├── bignoise.png │ ├── blobs.png │ ├── checkerboard.png │ ├── circleswipe.png │ ├── cubicnoise.png │ ├── curtainsclose.png │ ├── curtainsopen.png │ ├── diagonalleft.png │ ├── diagonalright.png │ ├── fade.png │ ├── fan.png │ ├── halftone.png │ ├── implode.png │ ├── lines.png │ ├── maze.png │ ├── mosaic.png │ ├── noise.png │ ├── plasma.png │ ├── radialin.png │ ├── radialout.png │ ├── slide.png │ ├── smoke.png │ ├── split.png │ ├── starburst.png │ ├── stripes.png │ ├── swipedown.png │ ├── swipeleft.png │ ├── swiperight.png │ ├── swipeup.png │ ├── swirl.png │ ├── triangles.png │ ├── vortex.png │ ├── waves.png │ ├── weave.png │ └── zebra.png ├── uu.json ├── uu.png └── vim-emacs.md ├── bin ├── build-apk ├── build-linux ├── build-web ├── build-win └── serve-web ├── demo ├── assets │ ├── Amble-Bold.ttf │ ├── Amble-Italic.ttf │ ├── Amble-Regular.ttf │ ├── Amble.woff │ ├── Hack-Regular.ttf │ ├── advent-of-code.md │ ├── agical-advent-of-code.png │ ├── agical-application-events.png │ ├── agical-asterisk.png │ ├── agical-continuous-deployment.png │ ├── agical-event-sourcing.png │ ├── agical-homebrewing.png │ ├── agical-macroquad.png │ ├── agical-raku.png │ ├── agical-rusty-aquarium.png │ ├── agical-rusty-slider.png │ ├── agical-vim-emacs.png │ ├── agical.json │ ├── agical.png │ ├── aoc2022 │ │ └── input1.txt │ ├── application-events.md │ ├── application-events.png │ ├── asterisk.md │ ├── asterisk.png │ ├── background.png │ ├── continuous-deployment.md │ ├── continuous-deployment.png │ ├── default-theme-advent-of-code.png │ ├── default-theme-application-events.png │ ├── default-theme-asterisk.png │ ├── default-theme-continuous-deployment.png │ ├── default-theme-event-sourcing.png │ ├── default-theme-homebrewing.png │ ├── default-theme-macroquad.png │ ├── default-theme-raku.png │ ├── default-theme-rusty-aquarium.png │ ├── default-theme-rusty-slider.png │ ├── default-theme-vim-emacs.png │ ├── default-theme.json │ ├── default-theme.png │ ├── event-sourcing.md │ ├── event-sourcing.png │ ├── exalted-beer-advent-of-code.png │ ├── exalted-beer-application-events.png │ ├── exalted-beer-asterisk.png │ ├── exalted-beer-continuous-deployment.png │ ├── exalted-beer-event-sourcing.png │ ├── exalted-beer-homebrewing.png │ ├── exalted-beer-macroquad.png │ ├── exalted-beer-raku.png │ ├── exalted-beer-rusty-aquarium.png │ ├── exalted-beer-rusty-slider.png │ ├── exalted-beer-vim-emacs.png │ ├── exalted-beer.json │ ├── exalted-beer.png │ ├── exaltedbeer.png │ ├── ferris.png │ ├── homebrewing.md │ ├── homebrewing.png │ ├── macroquad.md │ ├── raku.md │ ├── raku.png │ ├── rust-advent-of-code.png │ ├── rust-application-events.png │ ├── rust-asterisk.png │ ├── rust-continuous-deployment.png │ ├── rust-event-sourcing.png │ ├── rust-homebrewing.png │ ├── rust-macroquad.png │ ├── rust-raku.png │ ├── rust-rusty-aquarium.png │ ├── rust-rusty-slider.png │ ├── rust-vim-emacs.png │ ├── rust.json │ ├── rust.png │ ├── rusty-advent-of-code.png │ ├── rusty-application-events.png │ ├── rusty-aquarium.md │ ├── rusty-aquarium.png │ ├── rusty-asterisk.png │ ├── rusty-continuous-deployment.png │ ├── rusty-event-sourcing.png │ ├── rusty-homebrewing.png │ ├── rusty-macroquad.png │ ├── rusty-raku.png │ ├── rusty-rusty-aquarium.png │ ├── rusty-rusty-slider.png │ ├── rusty-slider-card.jpg │ ├── rusty-slider.md │ ├── rusty-slider.png │ ├── rusty-vim-emacs.png │ ├── rusty.json │ ├── rusty.png │ ├── screenshot.png │ ├── theme.json │ ├── thumbnail-agical.png │ ├── thumbnail-default-theme.png │ ├── thumbnail-exalted-beer.png │ ├── thumbnail-rust.png │ ├── thumbnail-rusty.png │ ├── thumbnail-uu.png │ ├── transition-demo.gif │ ├── transition-demo.mp4 │ ├── transition_starburst.jpg │ ├── transitions │ │ ├── bignoise.png │ │ ├── blobs.png │ │ ├── checkerboard.png │ │ ├── circleswipe.png │ │ ├── cubicnoise.png │ │ ├── curtains.png │ │ ├── curtainsclose.png │ │ ├── curtainsopen.png │ │ ├── diagonalleft.png │ │ ├── diagonalright.png │ │ ├── fade.png │ │ ├── fan.png │ │ ├── halftone.png │ │ ├── implode.png │ │ ├── lines.png │ │ ├── maze.png │ │ ├── mosaic.png │ │ ├── noise.png │ │ ├── plasma.png │ │ ├── radial.png │ │ ├── radialin.png │ │ ├── radialout.png │ │ ├── slide.png │ │ ├── smoke.png │ │ ├── split.png │ │ ├── starburst.png │ │ ├── stripes.png │ │ ├── swipedown.png │ │ ├── swipeleft.png │ │ ├── swiperight.png │ │ ├── swipeup.png │ │ ├── swirl.png │ │ ├── triangles.png │ │ ├── vortex.png │ │ ├── waves.png │ │ ├── weave.png │ │ └── zebra.png │ ├── uu-advent-of-code.png │ ├── uu-application-events.png │ ├── uu-asterisk.png │ ├── uu-continuous-deployment.png │ ├── uu-event-sourcing.png │ ├── uu-homebrewing.png │ ├── uu-macroquad.png │ ├── uu-raku.png │ ├── uu-rusty-aquarium.png │ ├── uu-rusty-slider.png │ ├── uu-vim-emacs.png │ ├── uu.json │ ├── uu.png │ ├── vim-emacs.md │ └── vim-emacs.png ├── example-slideshows.html ├── gl.js ├── index.html ├── manifest.json ├── mq_js_bundle.js ├── quad-url.js └── rusty_slider.wasm └── src ├── app_options.rs ├── bin └── generate-slide-list.rs ├── clipboard.rs ├── code_box_builder.rs ├── codebox.rs ├── drawbox.rs ├── executable_code.rs ├── helptext.txt ├── hex_color.rs ├── imagebox.rs ├── lib.rs ├── main.rs ├── markdowntoslides.rs ├── prelude.rs ├── shaders.rs ├── show_help.rs ├── slider.rs ├── textbox.rs ├── theme.rs ├── transition.rs └── transitioner.rs /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | publish: 10 | name: Release for ${{ matrix.config.target }} / ${{ matrix.config.os }} 11 | runs-on: ${{ matrix.config.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | config: 16 | - os: ubuntu-latest 17 | artifact_name: rusty_slider 18 | zip_file: rusty-slider-linux.tar.gz 19 | asset_name: rusty-slider-linux-$tag.tar.gz 20 | directory: rusty-slider-linux-${{ github.ref_name }} 21 | target: 'x86_64-unknown-linux-gnu' 22 | - os: windows-latest 23 | artifact_name: rusty_slider.exe 24 | zip_file: rusty-slider-windows.zip 25 | asset_name: rusty-slider-windows-$tag.zip 26 | directory: rusty-slider-windows-${{ github.ref_name }} 27 | target: 'x86_64-pc-windows-msvc' 28 | - os: macos-latest 29 | artifact_name: rusty_slider 30 | zip_file: rusty-slider-macos.zip 31 | asset_name: rusty-slider-macos-$tag.zip 32 | directory: rusty-slider-macos-${{ github.ref_name }} 33 | target: 'x86_64-apple-darwin' 34 | - os: ubuntu-latest 35 | artifact_name: rusty_slider.wasm 36 | zip_file: rusty-slider-wasm.zip 37 | asset_name: rusty-slider-wasm-$tag.zip 38 | directory: rusty-slider-wasm-${{ github.ref_name }} 39 | target: 'wasm32-unknown-unknown' 40 | include: 41 | - os: ubuntu-latest 42 | packages: libx11-dev libxi-dev libgl1-mesa-dev gcc-mingw-w64 libasound2-dev 43 | 44 | steps: 45 | - uses: actions/checkout@v2 46 | - name: Install packages (Linux) 47 | if: runner.os == 'Linux' 48 | run: | 49 | sudo apt-get update 50 | sudo apt-get -yq --no-install-suggests --no-install-recommends install ${{ matrix.packages }} 51 | - uses: actions-rs/toolchain@v1 52 | with: 53 | toolchain: stable 54 | target: ${{ matrix.config.target }} 55 | override: true 56 | - name: Workaround MinGW issue # https://github.com/rust-lang/rust/issues/47048 57 | if: runner.os == 'Linux' && matrix.config.target == 'x86_64-pc-windows-gnu' 58 | run: | 59 | sudo cp /usr/x86_64-w64-mingw32/lib/dllcrt2.o ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/dllcrt2.o 60 | sudo cp /usr/x86_64-w64-mingw32/lib/crt2.o ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/crt2.o 61 | echo "[target.x86_64-pc-windows-gnu]" >> ~/.cargo/config 62 | echo "linker = \"/usr/bin/x86_64-w64-mingw32-gcc\"" >> ~/.cargo/config 63 | - uses: actions-rs/cargo@v1 64 | with: 65 | command: build 66 | args: --release --locked --all-targets --target=${{ matrix.config.target }} 67 | - name: Zip release archive 68 | if: matrix.config.target == 'wasm32-unknown-unknown' 69 | run: | 70 | mkdir ${{ matrix.config.directory }} 71 | cp -pR assets ${{ matrix.config.directory }}/ 72 | cp -pR target/${{ matrix.config.target }}/release/${{ matrix.config.artifact_name }} demo/*.html demo/*.js demo/*.json ${{ matrix.config.directory }}/ 73 | zip -r ${{ matrix.config.zip_file }} ${{ matrix.config.directory }} 74 | - name: Zip release archive 75 | if: matrix.config.target == 'x86_64-unknown-linux-gnu' 76 | run: | 77 | mkdir ${{ matrix.config.directory }} 78 | cp -pR assets ${{ matrix.config.directory }}/ 79 | cp target/${{ matrix.config.target }}/release/${{ matrix.config.artifact_name }} ${{ matrix.config.directory }}/ 80 | tar -zcf ${{ matrix.config.zip_file }} ${{ matrix.config.directory }} 81 | - name: Zip release archive 82 | if: runner.os == 'Windows' || runner.os == 'macOS' 83 | run: | 84 | mkdir -p ${{ matrix.config.directory }} 85 | cp -R assets ${{ matrix.config.directory }}/ 86 | cp target/${{ matrix.config.target }}/release/${{ matrix.config.artifact_name }} ${{ matrix.config.directory }}/ 87 | 7z a -tzip ${{ matrix.config.zip_file }} ${{ matrix.config.directory }} 88 | - name: Upload binaries to release 89 | uses: svenstaro/upload-release-action@v2 90 | with: 91 | repo_token: ${{ secrets.GITHUB_TOKEN }} 92 | file: ${{ matrix.config.zip_file }} 93 | asset_name: ${{ matrix.config.asset_name }} 94 | tag: ${{ github.ref }} 95 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Cross-compile 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | name: Build 15 | runs-on: ${{ matrix.config.os }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | config: 20 | - { os: ubuntu-latest, target: 'x86_64-unknown-linux-gnu' } 21 | - { os: ubuntu-latest, target: 'x86_64-pc-windows-gnu' } 22 | - { os: ubuntu-latest, target: 'wasm32-unknown-unknown' } 23 | - { os: macos-latest, target: 'x86_64-apple-darwin' } 24 | - { os: windows-latest, target: 'x86_64-pc-windows-msvc' } 25 | include: 26 | - os: ubuntu-latest 27 | packages: libx11-dev libxi-dev libgl1-mesa-dev gcc-mingw-w64 libasound2-dev 28 | 29 | steps: 30 | - uses: actions/checkout@v2 31 | - name: Install packages (Linux) 32 | if: runner.os == 'Linux' 33 | run: | 34 | sudo apt-get update 35 | sudo apt-get -yq --no-install-suggests --no-install-recommends install ${{ matrix.packages }} 36 | - uses: actions-rs/toolchain@v1 37 | with: 38 | toolchain: stable 39 | target: ${{ matrix.config.target }} 40 | override: true 41 | - name: Workaround MinGW issue # https://github.com/rust-lang/rust/issues/47048 42 | if: runner.os == 'Linux' && matrix.config.target == 'x86_64-pc-windows-gnu' 43 | run: | 44 | sudo cp /usr/x86_64-w64-mingw32/lib/dllcrt2.o ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/dllcrt2.o 45 | sudo cp /usr/x86_64-w64-mingw32/lib/crt2.o ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/crt2.o 46 | echo "[target.x86_64-pc-windows-gnu]" >> ~/.cargo/config 47 | echo "linker = \"/usr/bin/x86_64-w64-mingw32-gcc\"" >> ~/.cargo/config 48 | - uses: Swatinem/rust-cache@v2 49 | - name: Build 50 | uses: actions-rs/cargo@v1 51 | with: 52 | command: build 53 | args: --all-targets --target=${{ matrix.config.target }} 54 | - name: Machete 55 | uses: bnjbvr/cargo-machete@main 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | /test-slides 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusty-slider" 3 | version = "0.25.0" 4 | authors = ["Olle Wreede "] 5 | edition = "2024" 6 | license = "MIT" 7 | description = "A small tool to display markdown files as a slideshow." 8 | repository = "https://github.com/ollej/rusty-slider" 9 | homepage = "https://ollej.github.io/rusty-slider/" 10 | readme = "README.md" 11 | categories = ["command-line-utilities"] 12 | keywords = ["markdown", "slideshow", "presentation", "graphics", "wasm"] 13 | default-run = "rusty_slider" 14 | 15 | [package.metadata.scripts] 16 | build-web = "bin/build-web" 17 | build-linux = "bin/build-linux" 18 | build-win = "bin/build-win" 19 | serve-web = "bin/serve-web" 20 | build-apk = "bin/build-apk" 21 | 22 | [dependencies] 23 | clap = { version = "4.0.18", features = ["derive"] } 24 | colorsys = "0.6.5" 25 | convert_case = "0.6.0" 26 | glob = "0.3.0" 27 | macroquad = "0.4.13" 28 | markdown = { git = "https://github.com/johannhof/markdown.rs" } 29 | maud = "0.24.0" 30 | nanoserde = "0.1.30" 31 | quad-url = "0.1.0" 32 | regex = "1.5.4" 33 | strum = { version = "0.24.1", features = ["derive"] } 34 | strum_macros = "0.24.3" 35 | syntect = { version = "5.0.0", default-features = false, features = ["default-fancy"] } 36 | tempfile = "3.3.0" 37 | 38 | [profile.dev] 39 | debug = 1 # less precise locations 40 | 41 | # Doesn't work with android build 42 | [profile.dev.package.'*'] 43 | debug = false # no debug symbols for deps 44 | opt-level = 3 45 | 46 | [profile.release] 47 | opt-level = 'z' 48 | lto = true 49 | panic = 'abort' 50 | codegen-units = 1 51 | strip = true 52 | 53 | [lib] 54 | name = "rusty_slider" 55 | path = "src/lib.rs" 56 | 57 | [[bin]] 58 | name = "rusty_slider" 59 | path = "src/main.rs" 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2025 Olle Wreede 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rusty Slider 2 | [![Cross-compile](https://github.com/ollej/rusty-slider/actions/workflows/rust.yml/badge.svg?branch=main)](https://github.com/ollej/rusty-slider/actions/workflows/rust.yml) 3 | 4 | A small tool to display markdown files as a slideshow. 5 | 6 | ![Screenshot](https://ollej.github.io/rusty-slider/assets/screenshot.png) 7 | 8 | ## Demo 9 | 10 | Try out Rusty Slider online: 11 | 12 | * [Example slideshows](https://ollej.github.io/rusty-slider/demo/example-slideshows.html). 13 | 14 | ## Download 15 | 16 | Rusty Slider is available for multiple platforms, such as Windows, 17 | Linux, MacOS, and the web. Download the latest binary build from github: 18 | 19 | [https://github.com/ollej/rusty-slider/releases/](https://github.com/ollej/rusty-slider/releases/) 20 | 21 | ## Usage 22 | 23 | The file `assets/rusty-slider.md` will be read and split into slides on 24 | horizontal lines: `---` 25 | 26 | At the moment, the markdown supported is headers, paragraphs, code blocks, 27 | blockquotes, simple lists and images. Emphasis and strong are supported if the 28 | theme has italic and bold fonts. 29 | 30 | Heading level 1 can be used as title page, as it will render in the 31 | middle of the slide and can have a larger font size set by the theme 32 | option `font_size_header_title`. 33 | 34 | You may use html comments (``) in the markdown for anything you 35 | don't want to be shown. 36 | 37 | ### Images 38 | 39 | Images can be added to the slideshow by using the image markdown It needs to 40 | be placed on its own at the start of a line, anything else in the same 41 | paragraph will be ignored. 42 | 43 | ``` 44 | ![ignored](assets/image.png) 45 | ``` 46 | 47 | ### Background image 48 | 49 | A default background image can be set in the theme file. In addition to this, 50 | it is possible to override this and set a background image per slide. This is 51 | done by adding an image markdown tag with `background` as the title text. 52 | 53 | ``` 54 | ![background](assets/new-background-image.png) 55 | ``` 56 | 57 | ### Shortcuts 58 | 59 | Use `Left`/`H` and `Right`/`L` keys or left and right mouse button to move 60 | back and forth between slides. `Up`/`K`/`Home` keys jump to first slide, 61 | and `Down`/`J`/`End` keys jump to last slide. 62 | 63 | The `S` key saves the current slide as a PNG on disk. 64 | 65 | On supported platforms `C` copies the first codeblock to the clipboard. 66 | 67 | Use the key `Q` or `Escape` to exit the slideshow. 68 | 69 | Press `?` to show a help screen. 70 | 71 | ### Command line options 72 | 73 | Use flag `--automatic N` when starting the application to automatically switch 74 | slide every N seconds. 75 | 76 | The flag `--number N` can be used to start the slideshow at that slide. 77 | 78 | ### Run code blocks 79 | 80 | When the command line flag `--enable-code-execution` is used, it is possible 81 | to run code in code blocks and show the result. 82 | 83 | When a code block with a recognized language is showing on a slide, it can be 84 | executed by pressing the `enter` key. The output will be added in a new code 85 | block at the bottom of the slide. 86 | 87 | This feature only works when running locally on a machine that has the 88 | interpretator for each language installed. Be careful when using this as 89 | there is no checks done on the shell script. 90 | 91 | Only the first code block on a slide can be executed. 92 | 93 | #### Supported languages 94 | 95 | * Bash 96 | * Python 97 | * Perl 98 | * Ruby 99 | * Rust 100 | 101 | ## Theme 102 | 103 | Create a file called `assets/default-theme.json` to modify default display values. 104 | 105 | If you make your own theme file, and want to share it, I'd be happy to add it 106 | to the release. 107 | 108 | ### Transitions 109 | 110 | These are all the available transitions that can be used in the option 111 | `transition`. 112 | 113 | * bignoise 114 | * blobs 115 | * checkerboard 116 | * circleswipe 117 | * cubicnoise 118 | * curtainsclose 119 | * curtainsopen 120 | * diagonalleft 121 | * diagonalright 122 | * fan 123 | * halftone 124 | * implode 125 | * lines 126 | * maze 127 | * mosaic 128 | * noise 129 | * plasma 130 | * radialin 131 | * radialout 132 | * smoke 133 | * split 134 | * starburst 135 | * stripes 136 | * swipedown 137 | * swipeleft 138 | * swiperight 139 | * swipeup 140 | * swirl 141 | * triangles 142 | * vortex 143 | * waves 144 | * zebra 145 | 146 | ### Available code themes 147 | 148 | The following code themes can be set in the config option `code_theme`: 149 | 150 | * base16-ocean.dark 151 | * base16-eighties.dark 152 | * base16-mocha.dark 153 | * base16-ocean.light 154 | * InspiredGitHub 155 | * Solarized (dark) 156 | * Solarized (light) 157 | 158 | ### Example theme.json 159 | 160 | ```json 161 | { 162 | "background_image": "assets/background.png", 163 | "background_color": "#753204", 164 | "heading_color": "#8f4d22", 165 | "text_color": "#cccccc", 166 | "align": "right", 167 | "font": "assets/Amble-Regular.ttf", 168 | "font_bold": "assets/Amble-Bold.ttf", 169 | "font_italic": "assets/Amble-Italic.ttf", 170 | "font_size_header_title": 100, 171 | "font_size_header_slides": 80, 172 | "font_size_text": 40, 173 | "vertical_offset": 20.0, 174 | "horizontal_offset": 100.0, 175 | "line_height": 2.0, 176 | "blockquote_background_color": "#333333", 177 | "blockquote_padding": 20.0, 178 | "blockquote_left_quote": "“", 179 | "blockquote_right_quote": "„", 180 | "font_code": "assets/Hack-Regular.ttf", 181 | "font_code_size": 20, 182 | "code_line_height": 1.2, 183 | "code_background_color": "#002b36", 184 | "code_theme": "Solarized (dark)", 185 | "code_tab_width": 2, 186 | "bullet": "• ", 187 | "shader": true, 188 | "transition": "swirl" 189 | } 190 | ``` 191 | 192 | ## Command line options 193 | 194 | The command line options can also be used as URL arguments to the 195 | web demo. 196 | 197 | ``` 198 | A small tool to display markdown files as a slideshow. 199 | 200 | Usage: rusty_slider [OPTIONS] 201 | 202 | Options: 203 | -d, --directory Path to directory to load slideshow files from [default: assets] 204 | -s, --slides Markdown files with slides text [default: rusty-slider.md] 205 | -t, --theme File with theme options [default: default-theme.json] 206 | -a, --automatic Automatically switch slides every N seconds [default: 0] 207 | --demo-transitions Switch transitions for every slide 208 | -S, --screenshot When taking screenshot, store PNG at this path [default: screenshot.png] 209 | --enable-code-execution Enable executing code in code blocks 210 | -A, --assets Path to directory where application files are loaded from [default: assets] 211 | -n, --number Slide number to start at [default: 0] 212 | -h, --help Print help information 213 | ``` 214 | 215 | ## Licenses 216 | 217 | ### Rusty Slider 218 | 219 | Copyright 2022 Olle Wreede, released under the MIT License. 220 | 221 | ### Amble font 222 | 223 | By Punchcut 224 | Apache License 225 | Version 2.0, January 2004 226 | http://www.apache.org/licenses/ 227 | 228 | ### Hack font 229 | 230 | Copyright Chris Simpkins 231 | SIL OFL 1.1 and Bitstream Vera v0.00 232 | https://www.fontsquirrel.com/license/hack 233 | 234 | ### Transition 235 | 236 | Copyright (c) 2021 TanTanDev 237 | MIT License 238 | 239 | # Related links 240 | 241 | * ollej @ mastodon 242 | * [Rusty Aquarium](https://ollej.github.io/rusty-aquarium/) 243 | * [Olle's portfolio](https://olle.wreede.se/) 244 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-time-machine 2 | google_analytics: G-3C78FHENSR 3 | -------------------------------------------------------------------------------- /assets/Amble-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/Amble-Bold.ttf -------------------------------------------------------------------------------- /assets/Amble-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/Amble-Italic.ttf -------------------------------------------------------------------------------- /assets/Amble-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/Amble-Regular.ttf -------------------------------------------------------------------------------- /assets/Hack-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/Hack-Regular.ttf -------------------------------------------------------------------------------- /assets/advent-of-code.md: -------------------------------------------------------------------------------- 1 | # Advent of Code 2022 2 | 3 | ## By Olle Wreede 4 | 5 | --- 6 | 7 | ## Advent of Code 8 | 9 | * Daily programming puzzles. 10 | * https://adventofcode.com/2022 11 | 12 | --- 13 | 14 | ## Solutions 15 | 16 | * Solutions in the Rust programming language. 17 | * Execute the solutions by hitting `Enter`. 18 | * This only works when running locally. 19 | 20 | --- 21 | 22 | ## Day 1 part 1 23 | 24 | ```rust 25 | use std::{error::Error, fs}; 26 | 27 | fn main() -> Result<(), Box> { 28 | let mut gnomes = vec![]; 29 | let mut calories = 0; 30 | let input = fs::read_to_string("assets/aoc2022/input1.txt")?; 31 | for line in input.lines() { 32 | if line.is_empty() { 33 | gnomes.push(calories); 34 | calories = 0; 35 | } else { 36 | calories += line.parse::()?; 37 | } 38 | } 39 | gnomes.push(calories); 40 | gnomes.sort_by(|a, b| b.partial_cmp(a).unwrap()); 41 | println!("Gnome with most calories: {}", gnomes.first().unwrap()); 42 | Ok(()) 43 | } 44 | ``` 45 | 46 | --- 47 | 48 | ## Day 1 part 2 49 | 50 | ```rust 51 | use std::{error::Error, fs}; 52 | 53 | fn main() -> Result<(), Box> { 54 | let mut gnomes = vec![]; 55 | let mut calories = 0; 56 | let input = fs::read_to_string("assets/aoc2022/input1.txt")?; 57 | for line in input.lines() { 58 | if line.is_empty() { 59 | gnomes.push(calories); 60 | calories = 0; 61 | } else { 62 | calories += line.parse::()?; 63 | } 64 | } 65 | gnomes.push(calories); 66 | gnomes.sort_by(|a, b| b.partial_cmp(a).unwrap()); 67 | let top_three: i32 = gnomes.iter().take(3).sum(); 68 | println!("Calories from top three gnomes: {}", top_three); 69 | Ok(()) 70 | } 71 | ``` 72 | -------------------------------------------------------------------------------- /assets/agical.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_image": "assets/agical.png", 3 | "background_color": "#000000", 4 | "heading_color": "#B0B0B0", 5 | "text_color": "#B0B0B0", 6 | "align": "center", 7 | "font": "assets/Amble-Regular.ttf", 8 | "font_bold": "assets/Amble-Bold.ttf", 9 | "font_italic": "assets/Amble-Italic.ttf", 10 | "font_size_header_title": 100, 11 | "font_size_header_slides": 60, 12 | "font_size_text": 40, 13 | "vertical_offset": 80.0, 14 | "horizontal_offset": 100.0, 15 | "line_height": 1.2, 16 | "blockquote_background_color": "#333333", 17 | "blockquote_padding": 20.0, 18 | "blockquote_left_quote": "“", 19 | "blockquote_right_quote": "„", 20 | "font_code": "assets/Hack-Regular.ttf", 21 | "font_code_size": 30, 22 | "code_line_height": 1.2, 23 | "code_background_color": "#002b36", 24 | "code_theme": "Solarized (dark)", 25 | "code_tab_width": 2, 26 | "bullet": "• ", 27 | "shader": false, 28 | "transition": "split" 29 | } 30 | -------------------------------------------------------------------------------- /assets/agical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/agical.png -------------------------------------------------------------------------------- /assets/application-events.md: -------------------------------------------------------------------------------- 1 | # Application Events 2 | 3 | Olle Wreede 4 | 5 | --- 6 | 7 | ## Application Events 8 | 9 | * Publish/subscribe pattern 10 | * Decouple logic with domain specific events 11 | * Triggered on actions in the system 12 | * Subscribed handlers perform asynchronously 13 | 14 | --- 15 | 16 | ## Use Cases 17 | 18 | * Statistics 19 | * Billing 20 | * Audit log 21 | * Extract side-effects (emails, notifications etc) 22 | * Decoupling between modules 23 | * Separate external concerns 24 | 25 | --- 26 | 27 | ## Events 28 | 29 | * Described as a verb in past tense 30 | * Contains domain specfic data 31 | * Includes no logic 32 | * Plain Ruby object 33 | 34 | --- 35 | 36 | ## Handlers 37 | 38 | * Subscribes to events 39 | * Runs asynchronously using Sidekiq 40 | * Has to be idempotent 41 | * Contains logic 42 | 43 | --- 44 | 45 | ## ApplicationEvent 46 | 47 | * Events classes extends from Events::ApplicationEvent 48 | * Includes a payload hash 49 | * May have one or more categories 50 | * Categories may require attributes 51 | * Always includes attributes uuid and created_at 52 | 53 | --- 54 | 55 | ## EventHandler 56 | 57 | * Sub-classes Events::EventHandler 58 | * Subscribes to events and/or categories 59 | * May define priority queue (critical/default/low) 60 | * handle_event method is called with event instance 61 | 62 | --- 63 | 64 | ## Example Event 65 | 66 | ```ruby 67 | class UserLoggedIn < Events::ApplicationEvent 68 | include Events::UserCategory 69 | 70 | def initialize(attributes = {}) 71 | super 72 | @payload[:login_at] = DateTime.now.utc.to_s 73 | end 74 | 75 | def visit_at 76 | @payload[:login_at] 77 | end 78 | end 79 | ``` 80 | 81 | --- 82 | 83 | ## Example Category 84 | 85 | ```ruby 86 | module Events 87 | module UserCategory 88 | def user=(user) 89 | @payload[:user] = user.uuid 90 | end 91 | 92 | def user 93 | User.find_by!(uuid: @payload[:user]) 94 | end 95 | end 96 | end 97 | ``` 98 | 99 | --- 100 | 101 | ## Example Handler 102 | 103 | ```ruby 104 | class TrackLogins < Events::EventHandler 105 | queue Events::QUEUE_LOW 106 | attach_to UserLoggedIn 107 | 108 | def handle_event(event) 109 | File.open('/tmp/logins', 'a') do |file| 110 | file.write("#{user.name} logged in on #{event.login_at}\n") 111 | end 112 | end 113 | end 114 | ``` 115 | 116 | --- 117 | 118 | # Olle Wreede 119 | 120 | @ollej 121 | -------------------------------------------------------------------------------- /assets/asterisk.md: -------------------------------------------------------------------------------- 1 | ## Webbaserad röstmenystyrning 2 | ## för telefonväxelsystem 3 | 4 | Olle Wreede 5 | 6 | 2007 7 | 8 | --- 9 | 10 | ## Översikt 11 | 12 | * Bakgrund 13 | * Problembeskrivning 14 | * PBX 15 | * Asterisk 16 | * Asterisks arkitektur 17 | 18 | --- 19 | 20 | ## Översikt fortsättning 21 | 22 | * Dialplan 23 | * AGI 24 | * AGI-system 25 | * Manager API 26 | * Implementering 27 | 28 | --- 29 | 30 | ## Bakgrund 31 | 32 | * Långa telefonköer 33 | * Dålig kontroll för kunden 34 | * Röstmenyer är svåra att använda 35 | * Statisk info på supportwebbplatser 36 | * Sammankoppling av webb och samtal 37 | * Större kontroll 38 | * Snabbare service 39 | 40 | --- 41 | 42 | ## Problembeskrivning 43 | 44 | * Gamla växlar är svårkonfigurerade 45 | * Mjukvarubaserad växel – Asterisk 46 | * Vilka möjligheter finns i Asterisk? 47 | * Koppla samman webb med telefon 48 | 49 | --- 50 | 51 | ## Asterisk 52 | 53 | * Open Source 54 | * Lättare att bygga ut 55 | * Stödjer standarder 56 | 57 | --- 58 | 59 | ## PBX och telefonväxlar 60 | 61 | Private Branch Exchange 62 | 63 | --- 64 | 65 | ## Vanliga funktioner i PBX 66 | 67 | * Fler anslutningar än telefonlinjer 68 | * Interna samtal 69 | * Röstbrevlådor 70 | * Vidarekoppling 71 | * Väntmusik 72 | * Vissa hanterar vanlig telefoni och VoIP 73 | 74 | --- 75 | 76 | ## Mjukvarubaserad växel 77 | 78 | 79 | 80 | * PC med instickskort och Asterisk 81 | * Telefonnätet kopplat till PC:n 82 | * Telefoner kopplade till PC:n 83 | * IP-telefon inkopplade via ethernet 84 | 85 | --- 86 | 87 | ## Asterisk 88 | 89 | * Mjukvarubaserad 90 | * Öppen källkod – GPL 91 | * Utvecklas och supportas av Digium 92 | * Klarar vanliga telenätet och VoIP 93 | * Stödjer många olika hårdvaror 94 | * Konfigurerbar och programmerbar 95 | * Utvecklas aktivt 96 | 97 | --- 98 | 99 | ## Asterisks arkitektur 100 | 101 | Kärna för intern funktionalitet 102 | 103 | --- 104 | 105 | ## Fyra API:er 106 | 107 | **Channel API** 108 | 109 | Hanterar olika typer av telefonkanaler 110 | 111 | **Application API** 112 | 113 | Hanterar tjänster som röstbrevlåda och konferenser 114 | 115 | **Codec Translator API** 116 | 117 | Laddar codecs för olika ljudformat 118 | 119 | **File Format API** 120 | 121 | Läser/Skriver data 122 | 123 | --- 124 | 125 | ## Asterisks Dialplan 126 | 127 | * Innehåller instruktioner för samtal 128 | * Detaljstyr samtalens väg genom systemet 129 | * Listar anknytningar 130 | * Läser knapptryckningar 131 | * Spelar upp ljud 132 | * Startar applikationer 133 | * Kraftfullt och avancerat 134 | 135 | --- 136 | 137 | ## Asterisk Gateway Interface 138 | 139 | * Gränssnitt för kommunikation 140 | 141 | mellan Asterisks dialplan och applikation 142 | 143 | * Läser/skriver via FIFO 144 | * Applikationerna startas från dialplan 145 | * AGI-kommandon 146 | 147 | för läsning av knapptryckningar, uppspelning av ljud osv 148 | 149 | * Kan skrivas i vilket språk som helst 150 | 151 | --- 152 | 153 | ## AGI-system 154 | 155 | * Klassbibliotek för AGI-program 156 | * Förenklar kommunikationen 157 | * Direkt stöd för AGI-kommandon 158 | 159 | --- 160 | 161 | ## AGI finns till många språk 162 | 163 | * phpAGI 164 | * Asterisk PHP 165 | * Asterisk-java 166 | * Asterisk Perl Library 167 | 168 | --- 169 | 170 | ## Manager API 171 | 172 | * Används av externa klienter 173 | * Visar information om samtal 174 | * Omstyrning av pågående samtal 175 | * Paket skickas via TCP/IP-ström 176 | * Data skickas asynkront 177 | * Stödjer ett flertal olika actions 178 | 179 | --- 180 | 181 | ## Implementering 182 | 183 | * Två delar – AGI-applikation och webbsystem 184 | * Systemen måste kopplas samman 185 | * Kan göras med kod som knappas in på telefonen 186 | * AGI-applikationen tar över samtalet från dialplan 187 | 188 | --- 189 | 190 | ## Webbsystem 191 | 192 | * Funktionalitet motsvarande röstmeny 193 | * Tillåter tydligare information 194 | * Kan kopplas samman med webbsupport 195 | * Visar kod för sammankoppling 196 | * Kan visa status om pågående samtal 197 | * Ifyllning av information under väntan 198 | 199 | --- 200 | 201 | ## AGI-applikation 202 | 203 | * Startas från dialplan 204 | * Läser in webbkod 205 | * Läser data från webb-sessionen 206 | * Synkar samtalet via databas 207 | * Utnyttjar webbsystemets menysystem 208 | * Agerar slav till webbsystemet 209 | 210 | --- 211 | 212 | ## Slutledning 213 | 214 | * Fullt möjligt att utveckla 215 | * Kan kodas i olika språk 216 | * Komplicerat 217 | * Kräver många olika delar av Asterisks API:er 218 | * Kräver inga dyra system 219 | * Kan byggas ut med fler funktioner 220 | 221 | --- 222 | 223 | # Olle Wreede 224 | -------------------------------------------------------------------------------- /assets/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/background.png -------------------------------------------------------------------------------- /assets/continuous-deployment.md: -------------------------------------------------------------------------------- 1 | # Continuous Deployment 2 | 3 | Deploy every change to production 4 | 5 | --- 6 | 7 | ## Steve Jobs 8 | 9 | > Real artists ship. 10 | 11 | --- 12 | 13 | ## Why? 14 | 15 | ### Why use continuous deployment? 16 | 17 | --- 18 | 19 | ## Why? 20 | 21 | ### Shorter turnaround 22 | 23 | --- 24 | 25 | ## Why? 26 | 27 | ### Minimize risk of large releases 28 | 29 | --- 30 | 31 | ## Why? 32 | 33 | ### No diverging branches 34 | 35 | --- 36 | 37 | ## Why? 38 | 39 | ### Easier to innovate 40 | 41 | --- 42 | 43 | ## Why? 44 | 45 | ### Faster feedback 46 | 47 | --- 48 | 49 | ## Why? 50 | 51 | ### Less unnecessary work 52 | 53 | --- 54 | 55 | ## Why? 56 | 57 | ### Mitigate fear of change 58 | 59 | --- 60 | 61 | ## Salvador Dali 62 | 63 | > Have no fear of perfection - 64 | > 65 | > you'll never reach it. 66 | 67 | --- 68 | 69 | ## How? 70 | 71 | How does continuous deployment work? 72 | 73 | --- 74 | 75 | ## How? 76 | 77 | ### Continuous integration 78 | 79 | --- 80 | 81 | ## How? 82 | 83 | ### Continuous delivery 84 | 85 | --- 86 | 87 | ## How? 88 | 89 | ### Small and independent changes 90 | 91 | --- 92 | 93 | ## How? 94 | 95 | ### Independently deployed applications 96 | 97 | --- 98 | 99 | ## How? 100 | 101 | ### Automated functional tests 102 | 103 | --- 104 | 105 | ## How? 106 | 107 | ### Monitoring and alerts 108 | 109 | --- 110 | 111 | ## How? 112 | 113 | ### Roll forward instead of rollback 114 | 115 | --- 116 | 117 | ## How? 118 | 119 | ### Feature flags 120 | 121 | --- 122 | 123 | ## Benjamin Disraeli 124 | 125 | > Change is inevitable. 126 | > 127 | > Change is constant. 128 | 129 | --- 130 | 131 | ## Feature flags 132 | 133 | ### Add new code without branching 134 | 135 | --- 136 | 137 | ## Feature flags 138 | 139 | ### Hide entry point in interface 140 | 141 | --- 142 | 143 | ## Feature flags 144 | 145 | ### Use flags in application code sparingly 146 | 147 | --- 148 | 149 | ## Feature flags 150 | 151 | ### Retire old flags 152 | 153 | --- 154 | 155 | ## Feature flags 156 | 157 | ### Test with flags activated in „release“ 158 | 159 | ### Test with all flags activated 160 | 161 | --- 162 | 163 | ## Feature flags 164 | 165 | ### Flags activated when customer is ready 166 | 167 | --- 168 | 169 | ## Feature flags 170 | 171 | ### Use only when necessary 172 | 173 | --- 174 | 175 | ## Feature flags 176 | 177 | ### Break down features into releasable parts 178 | 179 | --- 180 | 181 | ## Summary 182 | 183 | --- 184 | 185 | ## Mark Twain 186 | 187 | > The secret of getting ahead is getting started. 188 | 189 | --- 190 | 191 | ## Summary 192 | 193 | ### Smaller releases more often 194 | 195 | --- 196 | 197 | ## Summary 198 | 199 | ### Use feature flags when needed 200 | 201 | --- 202 | 203 | # Olle Wreede 204 | 205 | @ollej 206 | -------------------------------------------------------------------------------- /assets/default-theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_image": "assets/background.png", 3 | "background_color": "#301934", 4 | "heading_color": "#b19cd9", 5 | "text_color": "#ffffff", 6 | "align": "center", 7 | "font": "assets/Amble-Regular.ttf", 8 | "font_bold": "assets/Amble-Bold.ttf", 9 | "font_italic": "assets/Amble-Italic.ttf", 10 | "font_size_header_title": 100, 11 | "font_size_header_slides": 80, 12 | "font_size_text": 50, 13 | "vertical_offset": 60.0, 14 | "horizontal_offset": 100.0, 15 | "line_height": 2.0, 16 | "blockquote_background_color": "#333333", 17 | "blockquote_padding": 20.0, 18 | "blockquote_left_quote": "“", 19 | "blockquote_right_quote": "„", 20 | "font_code": "assets/Hack-Regular.ttf", 21 | "font_code_size": 20, 22 | "code_line_height": 1.2, 23 | "code_background_color": "#002b36", 24 | "code_theme": "Solarized (dark)", 25 | "code_tab_width": 2, 26 | "bullet": "• ", 27 | "shader": true, 28 | "transition": "starburst" 29 | } 30 | -------------------------------------------------------------------------------- /assets/event-sourcing.md: -------------------------------------------------------------------------------- 1 | # Event Sourcing 2 | 3 | --- 4 | 5 | ## Capture state change as a sequence of events 6 | 7 | --- 8 | 9 | ## How we got where we are 10 | 11 | --- 12 | 13 | ## Adjust state to handle retroactive changes 14 | 15 | --- 16 | 17 | ## Query log to get past state 18 | 19 | --- 20 | 21 | ## All change initiated by event objects 22 | 23 | --- 24 | 25 | ## Rebuild state from event log 26 | 27 | --- 28 | 29 | ## Replay events to debug with real data 30 | 31 | --- 32 | 33 | ## Temporal query to find state at any point in time 34 | 35 | --- 36 | 37 | ## Official state is either state or the event log 38 | 39 | --- 40 | 41 | ## State stored in memory 42 | 43 | ## with snapshots stored to disk 44 | 45 | --- 46 | 47 | ## State stored in a database with event log 48 | 49 | ## used for special processing and audits 50 | 51 | --- 52 | 53 | ## Event reversing 54 | 55 | Change as the difference of state 56 | 57 | or 58 | 59 | Store data needed for reversal 60 | 61 | --- 62 | 63 | ## Reprocess events to take advantag 64 | 65 | ## e of new features or bug fixes 66 | 67 | --- 68 | 69 | ## Replay events to test upgrades 70 | 71 | --- 72 | 73 | ## Handle external updates with a gateway which 74 | 75 | ## is aware that the system is processing replays 76 | 77 | --- 78 | 79 | ## External queries store responses in gateway, 80 | 81 | ## or queries for date specific value 82 | 83 | --- 84 | 85 | ## Loosely coupled parallel systems 86 | 87 | ## excellent for horizontal scaling 88 | 89 | --- 90 | 91 | ## Separate reading from writing 92 | 93 | --- 94 | 95 | ## New applications can easily be added 96 | 97 | ## by tapping in to the event stream 98 | 99 | --- 100 | 101 | # Olle Wreede 102 | 103 | @ollej 104 | -------------------------------------------------------------------------------- /assets/exalted-beer.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_image": "assets/exaltedbeer.png", 3 | "background_color": "#222222", 4 | "heading_color": "#34A5DA", 5 | "text_color": "#A6AAA9", 6 | "align": "left", 7 | "font": "assets/Amble-Regular.ttf", 8 | "font_bold": "assets/Amble-Bold.ttf", 9 | "font_italic": "assets/Amble-Italic.ttf", 10 | "font_size_header_title": 120, 11 | "font_size_header_slides": 80, 12 | "font_size_text": 40, 13 | "vertical_offset": 80.0, 14 | "horizontal_offset": 80.0, 15 | "line_height": 1.5, 16 | "blockquote_background_color": "#333333", 17 | "blockquote_padding": 40.0, 18 | "blockquote_left_quote": "“", 19 | "blockquote_right_quote": "„", 20 | "font_code": "assets/Hack-Regular.ttf", 21 | "font_code_size": 20, 22 | "code_line_height": 1.2, 23 | "code_background_color": "#e0e0e0", 24 | "code_theme": "base16-ocean.light", 25 | "code_tab_width": 4, 26 | "bullet": "» ", 27 | "shader": false, 28 | "transition": "split" 29 | } 30 | -------------------------------------------------------------------------------- /assets/exaltedbeer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/exaltedbeer.png -------------------------------------------------------------------------------- /assets/ferris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/ferris.png -------------------------------------------------------------------------------- /assets/homebrewing.md: -------------------------------------------------------------------------------- 1 | # Homebrewing 2 | 3 | --- 4 | 5 | ## Types of beer 6 | 7 | * Bottom fermented 8 | * Top fermented 9 | * Wild and sour beers 10 | 11 | --- 12 | 13 | ## Bottom fermented 14 | 15 | * Lager, pilsner, bock 16 | * Cold fermentation 17 | 18 | --- 19 | 20 | ## Top fermented 21 | 22 | * Ale, porter, stout 23 | * Warm fermentation 24 | 25 | --- 26 | 27 | ## Wild and sour beers 28 | 29 | * Spontaneous fermentation 30 | * Acidifying bacteria 31 | 32 | --- 33 | 34 | ## Brewing process 35 | 36 | 1. Preparation and cleaning 37 | 1. Mashing 38 | 1. Lautering 39 | 1. Boiling the wort 40 | 1. Chilling and aeration 41 | 1. Fermentation 42 | 1. Bottling 43 | 44 | --- 45 | 46 | ## Mashing 47 | 48 | * Malt, mainly from barley, is crushed 49 | * Mashed for 60 minutes at 64-68 C to convert starch 50 | * Single infusion, temperature, or decoction 51 | * Recirculation through malt bed 52 | 53 | --- 54 | 55 | ## Lautering 56 | 57 | * At the end of the mashing 58 | * Mash out at 78 C to stop saccharification and ease lautering 59 | * Sweet wort is extracted by lautering with 78 C water 60 | 61 | --- 62 | 63 | ## Boiling wort 64 | 65 | * Sweet wort is boiled for 60-90 minutes 66 | * Sterilizes 67 | * Increases sugar concentration 68 | * Removes protein 69 | 70 | --- 71 | 72 | ## Adding hops 73 | 74 | * Hops are added in three steps during the boil 75 | * Bitter hops at 60 minutes before end of boil 76 | * Flavoring hops boiled for 20-30 minutes 77 | * Aroma hops is added in the last five minutes 78 | * Hop stand after boiling 79 | 80 | --- 81 | 82 | ## Chilling 83 | 84 | * The boiled wort is chilled 85 | * Aeration to help yeast reproduce 86 | * Yeast culture added 87 | * Yeast converts sugar to alcohol 88 | 89 | --- 90 | 91 | ## Fermentation 92 | 93 | * Takes up to two weeks 94 | * American IPA is dry hopped after primary fermentation 95 | * Lager ferments at 4-12C 96 | * Ales ferments at 18-22C 97 | * Saison ferments at 22-32 C 98 | 99 | --- 100 | 101 | ## Bottling and carbonation 102 | 103 | * After 2-3 weeks fermentation is complete 104 | * Priming sugar is mixed with beer 105 | * Beer is added to bottles and capped 106 | * Carbonation takes a couple of weeks 107 | * Some beers are aged longer 108 | * Drink and enjoy! 109 | 110 | --- 111 | 112 | ### Plato 113 | 114 | > He was a wise man who invented beer 115 | 116 | --- 117 | 118 | # Olle Wreede 119 | 120 | ## Exalted Beer 121 | 122 | www.exalted.beer 123 | -------------------------------------------------------------------------------- /assets/raku.md: -------------------------------------------------------------------------------- 1 | # Raku 2 | 3 | An introduction to Raku programming language 4 | 5 | --- 6 | 7 | ## What is Raku? 8 | 9 | * A language definition 10 | * Multiple implementations 11 | * Not Perl 5 12 | * Used to be Perl 6 13 | * Released on December 25th 2015 14 | 15 | --- 16 | 17 | ## Rakudo 18 | 19 | A compiler for Raku with multiple virtual machine backends. 20 | 21 | --- 22 | 23 | ## Backends 24 | 25 | * MoarVM 26 | * JVM 27 | * Parrot 28 | 29 | --- 30 | 31 | ## Rakudo Star 32 | 33 | * A "useful" early adopter distribution 34 | * Includes Rakudo and useful modules. 35 | 36 | --- 37 | 38 | ## Raku features 39 | 40 | A short overview of new features. 41 | 42 | --- 43 | 44 | ## Grammar 45 | 46 | * Named regexes 47 | * New syntax 48 | * Grouping regexes with Grammar class 49 | * Decorators: regex, token, rule 50 | 51 | --- 52 | 53 | ## Command line scripts 54 | 55 | Built in syntax for command line option parsing. 56 | 57 | --- 58 | 59 | ## Positional command line options 60 | 61 | ```raku 62 | sub MAIN($x, $y) { ... } 63 | sub USAGE() { 64 | say "Usage: foo.pl "; 65 | } 66 | 67 | # foo.pl 42 1337 68 | ``` 69 | 70 | --- 71 | 72 | ## Named parameters 73 | 74 | ```raku 75 | sub MAIN(Bool :$verbose) { ... } 76 | 77 | # foo.pl --verbose 78 | ``` 79 | 80 | --- 81 | 82 | ## Options with arguments 83 | 84 | ```raku 85 | sub MAIN(:$foo = 'bar') { ... } 86 | 87 | # foo.pl --foo=baz 88 | ``` 89 | 90 | --- 91 | 92 | ## Gradual typing 93 | 94 | ```raku 95 | method foo($bar) { ... } 96 | ``` 97 | vs 98 | ```raku 99 | method foo(Str $bar) { ... } 100 | ``` 101 | 102 | --- 103 | 104 | ## User defineable operators 105 | 106 | ```raku 107 | 'bar' ¨ $foo; 108 | ``` 109 | ```raku 110 | sub infix:<¨ >($method, $obj) { 111 | say $obj."$method"(); 112 | } 113 | # $foo->bar(); 114 | ``` 115 | 116 | --- 117 | 118 | ## Threading 119 | 120 | * Implemented using promises and channels 121 | * Wraps implementations in different backends. 122 | 123 | --- 124 | 125 | ## Promises 126 | 127 | A synchronization primitive. 128 | ```raku 129 | my $kept_in_10 = Promise.in(10); 130 | ``` 131 | 132 | --- 133 | 134 | ## Channels 135 | 136 | Sending results between threads. 137 | 138 | ```raku 139 | my $dest = Channel.new; 140 | start { 141 | loop { 142 | $dest.send('foo'); 143 | } 144 | $dest.close; 145 | } 146 | ``` 147 | 148 | --- 149 | 150 | ## Who should use Raku? 151 | 152 | * Interested in language development 153 | * Grammar parsing 154 | 155 | --- 156 | 157 | # Olle Wreede 158 | 159 | @ollej 160 | -------------------------------------------------------------------------------- /assets/rust.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_image": "assets/rust.png", 3 | "background_color": "#ffffff", 4 | "heading_color": "#000000", 5 | "text_color": "#000000", 6 | "align": "left", 7 | "font": "assets/Amble-Regular.ttf", 8 | "font_bold": "assets/Amble-Bold.ttf", 9 | "font_italic": "assets/Amble-Italic.ttf", 10 | "font_size_header_title": 100, 11 | "font_size_header_slides": 80, 12 | "font_size_text": 40, 13 | "vertical_offset": 85.0, 14 | "horizontal_offset": 300.0, 15 | "line_height": 2.0, 16 | "blockquote_background_color": "#0b7261", 17 | "blockquote_padding": 20.0, 18 | "blockquote_left_quote": "“", 19 | "blockquote_right_quote": "„", 20 | "font_code": "assets/Hack-Regular.ttf", 21 | "font_code_size": 20, 22 | "code_line_height": 1.2, 23 | "code_background_color": "#2a3439", 24 | "code_theme": "Solarized (dark)", 25 | "code_tab_width": 2, 26 | "bullet": "• ", 27 | "shader": false, 28 | "transition": "radialin" 29 | } 30 | -------------------------------------------------------------------------------- /assets/rust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/rust.png -------------------------------------------------------------------------------- /assets/rusty-aquarium.md: -------------------------------------------------------------------------------- 1 | # Rusty Aquarium 2 | 3 | --- 4 | 5 | ## Montioring visualization 6 | 7 | Visualize monitoring data as a serene aquarium 8 | 9 | --- 10 | 11 | ## Why 12 | 13 | Static graphs are boring 14 | 15 | Opaque for the uninitated 16 | 17 | Looks beautiful 18 | 19 | --- 20 | 21 | ## What can it be used for? 22 | 23 | System monitoring 24 | 25 | Web request tracking 26 | 27 | E-commerce metrics 28 | 29 | --- 30 | 31 | ## How it works 32 | 33 | Periodically reads a JSON file with input data 34 | 35 | Input data defines fish size, speed, and species 36 | 37 | --- 38 | 39 | ## Input data 40 | 41 | The `size`, `speed`, and `bubbles` values are multipliers 42 | 43 | ```json 44 | { 45 | "school": [ 46 | { "fish": "crab", "size": 1.0, "speed": 1.0, "bubbles": 1.0 } 47 | ] 48 | } 49 | ``` 50 | 51 | --- 52 | 53 | ## Generating data 54 | 55 | Programs to generate input data is not included 56 | 57 | System monitoring application included as exemple 58 | 59 | --- 60 | 61 | ## Thank you 62 | 63 | Olle Wreede 64 | 65 | olle.wreede@agical.se 66 | 67 | @ollej 68 | -------------------------------------------------------------------------------- /assets/rusty-slider.md: -------------------------------------------------------------------------------- 1 | # Rusty Slider 2 | 3 | --- 4 | 5 | ## About 6 | 7 | A small tool to display markdown files as a slideshow. 8 | 9 | Use right key or left mouse button to go to next slide. 10 | 11 | --- 12 | 13 | ## Markdown 14 | 15 | * Slides are written in markdown. 16 | * Supports headers, paragraphs, lists, blockquotes, code blocks. 17 | * Also supports images 18 | * Text between horizontal lines will be a slide. 19 | 20 | --- 21 | 22 | ## Theme 23 | 24 | Colors and fonts can be configured in a json file. 25 | 26 | A background image can also be defined in the theme file. 27 | 28 | --- 29 | 30 | ## Rust + Macroquad 31 | 32 | Developed with Rust and the macroquad game library. 33 | 34 | --- 35 | 36 | ## Cross-platform 37 | 38 | Supports Windows, MacOS, Linux and web. 39 | 40 | --- 41 | 42 | ## Supports images 43 | 44 | ![Image Title](assets/ferris.png) 45 | 46 | --- 47 | 48 | ## Code 49 | 50 | Code blocks will be rendered with syntax highlighting. 51 | 52 | ```rust 53 | let shader_material = load_material( 54 | shaders::crt::VERTEX, 55 | shaders::crt::FRAGMENT, 56 | Default::default(), 57 | ) 58 | .unwrap(); 59 | ``` 60 | 61 | --- 62 | 63 | ## Run code! 64 | 65 | Execute code blocks by pressing `enter`. 66 | 67 | ```bash 68 | echo "Hello, World!" 69 | ``` 70 | 71 | --- 72 | 73 | ## Compiling Rust 74 | 75 | Rust code blocks can be compiled and executed as well. 76 | 77 | ```rust 78 | fn main() { 79 | println!("Hello, world!"); 80 | } 81 | ``` 82 | 83 | --- 84 | 85 | ## Blockquotes 86 | 87 | > Blockquotes renders with background color 88 | > 89 | > *And fancy quotes.* 90 | 91 | --- 92 | 93 | ## Controls 94 | 95 | Left/right keys switches between previous and next slide. 96 | 97 | Escape quits the slideshow. 98 | 99 | Space toggles the shader. 100 | 101 | Enter executes code in first code block. 102 | 103 | --- 104 | 105 | ## Usage 106 | 107 | 1. Create a markdown file called `slides.md` in `assets` directory. 108 | 1. Optionally add a `theme.json` in `assets` directory. 109 | 110 | --- 111 | 112 | ## Possible improvements 113 | 114 | Add transitions. 115 | 116 | Support more shaders. 117 | 118 | --- 119 | 120 | ## License 121 | 122 | **Copyright 2024 Olle Wreede** 123 | 124 | Released under the MIT license. 125 | -------------------------------------------------------------------------------- /assets/rusty.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_image": "assets/background.png", 3 | "background_color": "#753204", 4 | "heading_color": "#e37831", 5 | "text_color": "#cccccc", 6 | "align": "left", 7 | "font": "assets/Amble-Regular.ttf", 8 | "font_bold": "assets/Amble-Bold.ttf", 9 | "font_italic": "assets/Amble-Italic.ttf", 10 | "font_size_header_title": 100, 11 | "font_size_header_slides": 80, 12 | "font_size_text": 40, 13 | "vertical_offset": 60.0, 14 | "horizontal_offset": 100.0, 15 | "line_height": 1.5, 16 | "blockquote_background_color": "#333333", 17 | "blockquote_padding": 20.0, 18 | "blockquote_left_quote": "“", 19 | "blockquote_right_quote": "„", 20 | "font_code": "assets/Hack-Regular.ttf", 21 | "font_code_size": 20, 22 | "code_line_height": 1.2, 23 | "code_background_color": "#e0e0e0", 24 | "code_theme": "base16-ocean.light", 25 | "code_tab_width": 4, 26 | "bullet": "» ", 27 | "shader": false, 28 | "transition": "swirl" 29 | } 30 | -------------------------------------------------------------------------------- /assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/screenshot.png -------------------------------------------------------------------------------- /assets/transitions/bignoise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/bignoise.png -------------------------------------------------------------------------------- /assets/transitions/blobs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/blobs.png -------------------------------------------------------------------------------- /assets/transitions/checkerboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/checkerboard.png -------------------------------------------------------------------------------- /assets/transitions/circleswipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/circleswipe.png -------------------------------------------------------------------------------- /assets/transitions/cubicnoise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/cubicnoise.png -------------------------------------------------------------------------------- /assets/transitions/curtainsclose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/curtainsclose.png -------------------------------------------------------------------------------- /assets/transitions/curtainsopen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/curtainsopen.png -------------------------------------------------------------------------------- /assets/transitions/diagonalleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/diagonalleft.png -------------------------------------------------------------------------------- /assets/transitions/diagonalright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/diagonalright.png -------------------------------------------------------------------------------- /assets/transitions/fade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/fade.png -------------------------------------------------------------------------------- /assets/transitions/fan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/fan.png -------------------------------------------------------------------------------- /assets/transitions/halftone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/halftone.png -------------------------------------------------------------------------------- /assets/transitions/implode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/implode.png -------------------------------------------------------------------------------- /assets/transitions/lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/lines.png -------------------------------------------------------------------------------- /assets/transitions/maze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/maze.png -------------------------------------------------------------------------------- /assets/transitions/mosaic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/mosaic.png -------------------------------------------------------------------------------- /assets/transitions/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/noise.png -------------------------------------------------------------------------------- /assets/transitions/plasma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/plasma.png -------------------------------------------------------------------------------- /assets/transitions/radialin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/radialin.png -------------------------------------------------------------------------------- /assets/transitions/radialout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/radialout.png -------------------------------------------------------------------------------- /assets/transitions/slide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/slide.png -------------------------------------------------------------------------------- /assets/transitions/smoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/smoke.png -------------------------------------------------------------------------------- /assets/transitions/split.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/split.png -------------------------------------------------------------------------------- /assets/transitions/starburst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/starburst.png -------------------------------------------------------------------------------- /assets/transitions/stripes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/stripes.png -------------------------------------------------------------------------------- /assets/transitions/swipedown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/swipedown.png -------------------------------------------------------------------------------- /assets/transitions/swipeleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/swipeleft.png -------------------------------------------------------------------------------- /assets/transitions/swiperight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/swiperight.png -------------------------------------------------------------------------------- /assets/transitions/swipeup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/swipeup.png -------------------------------------------------------------------------------- /assets/transitions/swirl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/swirl.png -------------------------------------------------------------------------------- /assets/transitions/triangles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/triangles.png -------------------------------------------------------------------------------- /assets/transitions/vortex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/vortex.png -------------------------------------------------------------------------------- /assets/transitions/waves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/waves.png -------------------------------------------------------------------------------- /assets/transitions/weave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/weave.png -------------------------------------------------------------------------------- /assets/transitions/zebra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/transitions/zebra.png -------------------------------------------------------------------------------- /assets/uu.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_image": "assets/uu.png", 3 | "background_color": "#ffffff", 4 | "heading_color": "#000000", 5 | "text_color": "#000000", 6 | "align": "left", 7 | "font": "assets/Amble-Regular.ttf", 8 | "font_bold": "assets/Amble-Bold.ttf", 9 | "font_italic": "assets/Amble-Italic.ttf", 10 | "font_size_header_title": 80, 11 | "font_size_header_slides": 60, 12 | "font_size_text": 40, 13 | "vertical_offset": 60.0, 14 | "horizontal_offset": 250.0, 15 | "line_height": 2.0, 16 | "blockquote_background_color": "#2e2459", 17 | "blockquote_padding": 20.0, 18 | "blockquote_left_quote": "“", 19 | "blockquote_right_quote": "„", 20 | "font_code": "assets/Hack-Regular.ttf", 21 | "font_code_size": 20, 22 | "code_line_height": 1.2, 23 | "code_background_color": "#2a3439", 24 | "code_theme": "Solarized (dark)", 25 | "code_tab_width": 2, 26 | "bullet": "• ", 27 | "shader": false, 28 | "transition": "checkerboard" 29 | } 30 | -------------------------------------------------------------------------------- /assets/uu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/assets/uu.png -------------------------------------------------------------------------------- /assets/vim-emacs.md: -------------------------------------------------------------------------------- 1 | # vim + emacs 2 | 3 | --- 4 | 5 | ## vim ~/.emacs 6 | 7 | Changes won't be evaluated 8 | 9 | --- 10 | 11 | ## vim-emacs plugin 12 | 13 | * Select a range 14 | * Type command `:Emacs` 15 | * Will evaluate range in Emacs 16 | 17 | --- 18 | 19 | ## How? 20 | 21 | * Emacs with server 22 | * Lines sent with `emacsclient --eval` 23 | * Elisp expressions evaluated 24 | * Output is returned 25 | 26 | --- 27 | 28 | ## Why? 29 | 30 | Let's end the war! 31 | 32 | --- 33 | 34 | ## Where? 35 | 36 | The vim.emacs plugin is available on GitHub: 37 | 38 | ``` 39 | https://github.com/ollej/vim.emacs 40 | ``` 41 | 42 | --- 43 | 44 | ## Questions? 45 | 46 | No? 47 | 48 | --- 49 | 50 | ## Hate mail 51 | 52 | olle@wreede.se 53 | 54 | @ollej 55 | -------------------------------------------------------------------------------- /bin/build-apk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Build for Android 4 | docker run --rm -v $(pwd)":/root/src" -w /root/src notfl3/cargo-apk cargo apk build 5 | -------------------------------------------------------------------------------- /bin/build-linux: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -euo pipefail 4 | 5 | # Build for Linux 6 | TARGET=x86_64-unknown-linux-gnu 7 | export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-unknown-linux-gnu-gcc 8 | cargo build --release --target $TARGET 9 | 10 | # Package Linux tar ball 11 | APP=rusty-slider 12 | tar -zcf "target/$APP-linux.tar.gz" assets/* -C target/$TARGET/release/ $APP 13 | -------------------------------------------------------------------------------- /bin/build-web: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -euo pipefail 4 | 5 | cargo build --release --target wasm32-unknown-unknown 6 | mkdir -p demo 7 | cp target/wasm32-unknown-unknown/release/rusty_slider.wasm demo/ 8 | cp -pR assets demo/ 9 | -------------------------------------------------------------------------------- /bin/build-win: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -euo pipefail 4 | 5 | # Build for windows 6 | TARGET=x86_64-pc-windows-gnu 7 | cargo build --release --target $TARGET 8 | 9 | # Package windows zip 10 | APP=rusty_slider 11 | zip -jq target/$APP-win.zip target/$TARGET/release/$APP.exe 12 | zip -ulq target/$APP-win.zip assets/* 13 | -------------------------------------------------------------------------------- /bin/serve-web: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | basic-http-server -a 0.0.0.0:4000 demo 4 | -------------------------------------------------------------------------------- /demo/assets/Amble-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/Amble-Bold.ttf -------------------------------------------------------------------------------- /demo/assets/Amble-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/Amble-Italic.ttf -------------------------------------------------------------------------------- /demo/assets/Amble-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/Amble-Regular.ttf -------------------------------------------------------------------------------- /demo/assets/Amble.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/Amble.woff -------------------------------------------------------------------------------- /demo/assets/Hack-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/Hack-Regular.ttf -------------------------------------------------------------------------------- /demo/assets/advent-of-code.md: -------------------------------------------------------------------------------- 1 | # Advent of Code 2022 2 | 3 | ## By Olle Wreede 4 | 5 | --- 6 | 7 | ## Advent of Code 8 | 9 | * Daily programming puzzles. 10 | * https://adventofcode.com/2022 11 | 12 | --- 13 | 14 | ## Solutions 15 | 16 | * Solutions in the Rust programming language. 17 | * Execute the solutions by hitting `Enter`. 18 | * This only works when running locally. 19 | 20 | --- 21 | 22 | ## Day 1 part 1 23 | 24 | ```rust 25 | use std::{error::Error, fs}; 26 | 27 | fn main() -> Result<(), Box> { 28 | let mut gnomes = vec![]; 29 | let mut calories = 0; 30 | let input = fs::read_to_string("assets/aoc2022/input1.txt")?; 31 | for line in input.lines() { 32 | if line.is_empty() { 33 | gnomes.push(calories); 34 | calories = 0; 35 | } else { 36 | calories += line.parse::()?; 37 | } 38 | } 39 | gnomes.push(calories); 40 | gnomes.sort_by(|a, b| b.partial_cmp(a).unwrap()); 41 | println!("Gnome with most calories: {}", gnomes.first().unwrap()); 42 | Ok(()) 43 | } 44 | ``` 45 | 46 | --- 47 | 48 | ## Day 1 part 2 49 | 50 | ```rust 51 | use std::{error::Error, fs}; 52 | 53 | fn main() -> Result<(), Box> { 54 | let mut gnomes = vec![]; 55 | let mut calories = 0; 56 | let input = fs::read_to_string("assets/aoc2022/input1.txt")?; 57 | for line in input.lines() { 58 | if line.is_empty() { 59 | gnomes.push(calories); 60 | calories = 0; 61 | } else { 62 | calories += line.parse::()?; 63 | } 64 | } 65 | gnomes.push(calories); 66 | gnomes.sort_by(|a, b| b.partial_cmp(a).unwrap()); 67 | let top_three: i32 = gnomes.iter().take(3).sum(); 68 | println!("Calories from top three gnomes: {}", top_three); 69 | Ok(()) 70 | } 71 | ``` 72 | -------------------------------------------------------------------------------- /demo/assets/agical-advent-of-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/agical-advent-of-code.png -------------------------------------------------------------------------------- /demo/assets/agical-application-events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/agical-application-events.png -------------------------------------------------------------------------------- /demo/assets/agical-asterisk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/agical-asterisk.png -------------------------------------------------------------------------------- /demo/assets/agical-continuous-deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/agical-continuous-deployment.png -------------------------------------------------------------------------------- /demo/assets/agical-event-sourcing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/agical-event-sourcing.png -------------------------------------------------------------------------------- /demo/assets/agical-homebrewing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/agical-homebrewing.png -------------------------------------------------------------------------------- /demo/assets/agical-macroquad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/agical-macroquad.png -------------------------------------------------------------------------------- /demo/assets/agical-raku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/agical-raku.png -------------------------------------------------------------------------------- /demo/assets/agical-rusty-aquarium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/agical-rusty-aquarium.png -------------------------------------------------------------------------------- /demo/assets/agical-rusty-slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/agical-rusty-slider.png -------------------------------------------------------------------------------- /demo/assets/agical-vim-emacs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/agical-vim-emacs.png -------------------------------------------------------------------------------- /demo/assets/agical.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_image": "assets/agical.png", 3 | "background_color": "#000000", 4 | "heading_color": "#B0B0B0", 5 | "text_color": "#B0B0B0", 6 | "align": "center", 7 | "font": "assets/Amble-Regular.ttf", 8 | "font_bold": "assets/Amble-Bold.ttf", 9 | "font_italic": "assets/Amble-Italic.ttf", 10 | "font_size_header_title": 100, 11 | "font_size_header_slides": 60, 12 | "font_size_text": 40, 13 | "vertical_offset": 80.0, 14 | "horizontal_offset": 100.0, 15 | "line_height": 1.2, 16 | "blockquote_background_color": "#333333", 17 | "blockquote_padding": 20.0, 18 | "blockquote_left_quote": "“", 19 | "blockquote_right_quote": "„", 20 | "font_code": "assets/Hack-Regular.ttf", 21 | "font_code_size": 30, 22 | "code_line_height": 1.2, 23 | "code_background_color": "#002b36", 24 | "code_theme": "Solarized (dark)", 25 | "code_tab_width": 2, 26 | "bullet": "• ", 27 | "shader": false, 28 | "transition": "split" 29 | } 30 | -------------------------------------------------------------------------------- /demo/assets/agical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/agical.png -------------------------------------------------------------------------------- /demo/assets/application-events.md: -------------------------------------------------------------------------------- 1 | # Application Events 2 | 3 | Olle Wreede 4 | 5 | --- 6 | 7 | ## Application Events 8 | 9 | * Publish/subscribe pattern 10 | * Decouple logic with domain specific events 11 | * Triggered on actions in the system 12 | * Subscribed handlers perform asynchronously 13 | 14 | --- 15 | 16 | ## Use Cases 17 | 18 | * Statistics 19 | * Billing 20 | * Audit log 21 | * Extract side-effects (emails, notifications etc) 22 | * Decoupling between modules 23 | * Separate external concerns 24 | 25 | --- 26 | 27 | ## Events 28 | 29 | * Described as a verb in past tense 30 | * Contains domain specfic data 31 | * Includes no logic 32 | * Plain Ruby object 33 | 34 | --- 35 | 36 | ## Handlers 37 | 38 | * Subscribes to events 39 | * Runs asynchronously using Sidekiq 40 | * Has to be idempotent 41 | * Contains logic 42 | 43 | --- 44 | 45 | ## ApplicationEvent 46 | 47 | * Events classes extends from Events::ApplicationEvent 48 | * Includes a payload hash 49 | * May have one or more categories 50 | * Categories may require attributes 51 | * Always includes attributes uuid and created_at 52 | 53 | --- 54 | 55 | ## EventHandler 56 | 57 | * Sub-classes Events::EventHandler 58 | * Subscribes to events and/or categories 59 | * May define priority queue (critical/default/low) 60 | * handle_event method is called with event instance 61 | 62 | --- 63 | 64 | ## Example Event 65 | 66 | ```ruby 67 | class UserLoggedIn < Events::ApplicationEvent 68 | include Events::UserCategory 69 | 70 | def initialize(attributes = {}) 71 | super 72 | @payload[:login_at] = DateTime.now.utc.to_s 73 | end 74 | 75 | def visit_at 76 | @payload[:login_at] 77 | end 78 | end 79 | ``` 80 | 81 | --- 82 | 83 | ## Example Category 84 | 85 | ```ruby 86 | module Events 87 | module UserCategory 88 | def user=(user) 89 | @payload[:user] = user.uuid 90 | end 91 | 92 | def user 93 | User.find_by!(uuid: @payload[:user]) 94 | end 95 | end 96 | end 97 | ``` 98 | 99 | --- 100 | 101 | ## Example Handler 102 | 103 | ```ruby 104 | class TrackLogins < Events::EventHandler 105 | queue Events::QUEUE_LOW 106 | attach_to UserLoggedIn 107 | 108 | def handle_event(event) 109 | File.open('/tmp/logins', 'a') do |file| 110 | file.write("#{user.name} logged in on #{event.login_at}\n") 111 | end 112 | end 113 | end 114 | ``` 115 | 116 | --- 117 | 118 | # Olle Wreede 119 | 120 | @ollej 121 | -------------------------------------------------------------------------------- /demo/assets/application-events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/application-events.png -------------------------------------------------------------------------------- /demo/assets/asterisk.md: -------------------------------------------------------------------------------- 1 | ## Webbaserad röstmenystyrning 2 | ## för telefonväxelsystem 3 | 4 | Olle Wreede 5 | 6 | 2007 7 | 8 | --- 9 | 10 | ## Översikt 11 | 12 | * Bakgrund 13 | * Problembeskrivning 14 | * PBX 15 | * Asterisk 16 | * Asterisks arkitektur 17 | 18 | --- 19 | 20 | ## Översikt fortsättning 21 | 22 | * Dialplan 23 | * AGI 24 | * AGI-system 25 | * Manager API 26 | * Implementering 27 | 28 | --- 29 | 30 | ## Bakgrund 31 | 32 | * Långa telefonköer 33 | * Dålig kontroll för kunden 34 | * Röstmenyer är svåra att använda 35 | * Statisk info på supportwebbplatser 36 | * Sammankoppling av webb och samtal 37 | * Större kontroll 38 | * Snabbare service 39 | 40 | --- 41 | 42 | ## Problembeskrivning 43 | 44 | * Gamla växlar är svårkonfigurerade 45 | * Mjukvarubaserad växel – Asterisk 46 | * Vilka möjligheter finns i Asterisk? 47 | * Koppla samman webb med telefon 48 | 49 | --- 50 | 51 | ## Asterisk 52 | 53 | * Open Source 54 | * Lättare att bygga ut 55 | * Stödjer standarder 56 | 57 | --- 58 | 59 | ## PBX och telefonväxlar 60 | 61 | Private Branch Exchange 62 | 63 | --- 64 | 65 | ## Vanliga funktioner i PBX 66 | 67 | * Fler anslutningar än telefonlinjer 68 | * Interna samtal 69 | * Röstbrevlådor 70 | * Vidarekoppling 71 | * Väntmusik 72 | * Vissa hanterar vanlig telefoni och VoIP 73 | 74 | --- 75 | 76 | ## Mjukvarubaserad växel 77 | 78 | 79 | 80 | * PC med instickskort och Asterisk 81 | * Telefonnätet kopplat till PC:n 82 | * Telefoner kopplade till PC:n 83 | * IP-telefon inkopplade via ethernet 84 | 85 | --- 86 | 87 | ## Asterisk 88 | 89 | * Mjukvarubaserad 90 | * Öppen källkod – GPL 91 | * Utvecklas och supportas av Digium 92 | * Klarar vanliga telenätet och VoIP 93 | * Stödjer många olika hårdvaror 94 | * Konfigurerbar och programmerbar 95 | * Utvecklas aktivt 96 | 97 | --- 98 | 99 | ## Asterisks arkitektur 100 | 101 | Kärna för intern funktionalitet 102 | 103 | --- 104 | 105 | ## Fyra API:er 106 | 107 | **Channel API** 108 | 109 | Hanterar olika typer av telefonkanaler 110 | 111 | **Application API** 112 | 113 | Hanterar tjänster som röstbrevlåda och konferenser 114 | 115 | **Codec Translator API** 116 | 117 | Laddar codecs för olika ljudformat 118 | 119 | **File Format API** 120 | 121 | Läser/Skriver data 122 | 123 | --- 124 | 125 | ## Asterisks Dialplan 126 | 127 | * Innehåller instruktioner för samtal 128 | * Detaljstyr samtalens väg genom systemet 129 | * Listar anknytningar 130 | * Läser knapptryckningar 131 | * Spelar upp ljud 132 | * Startar applikationer 133 | * Kraftfullt och avancerat 134 | 135 | --- 136 | 137 | ## Asterisk Gateway Interface 138 | 139 | * Gränssnitt för kommunikation 140 | 141 | mellan Asterisks dialplan och applikation 142 | 143 | * Läser/skriver via FIFO 144 | * Applikationerna startas från dialplan 145 | * AGI-kommandon 146 | 147 | för läsning av knapptryckningar, uppspelning av ljud osv 148 | 149 | * Kan skrivas i vilket språk som helst 150 | 151 | --- 152 | 153 | ## AGI-system 154 | 155 | * Klassbibliotek för AGI-program 156 | * Förenklar kommunikationen 157 | * Direkt stöd för AGI-kommandon 158 | 159 | --- 160 | 161 | ## AGI finns till många språk 162 | 163 | * phpAGI 164 | * Asterisk PHP 165 | * Asterisk-java 166 | * Asterisk Perl Library 167 | 168 | --- 169 | 170 | ## Manager API 171 | 172 | * Används av externa klienter 173 | * Visar information om samtal 174 | * Omstyrning av pågående samtal 175 | * Paket skickas via TCP/IP-ström 176 | * Data skickas asynkront 177 | * Stödjer ett flertal olika actions 178 | 179 | --- 180 | 181 | ## Implementering 182 | 183 | * Två delar – AGI-applikation och webbsystem 184 | * Systemen måste kopplas samman 185 | * Kan göras med kod som knappas in på telefonen 186 | * AGI-applikationen tar över samtalet från dialplan 187 | 188 | --- 189 | 190 | ## Webbsystem 191 | 192 | * Funktionalitet motsvarande röstmeny 193 | * Tillåter tydligare information 194 | * Kan kopplas samman med webbsupport 195 | * Visar kod för sammankoppling 196 | * Kan visa status om pågående samtal 197 | * Ifyllning av information under väntan 198 | 199 | --- 200 | 201 | ## AGI-applikation 202 | 203 | * Startas från dialplan 204 | * Läser in webbkod 205 | * Läser data från webb-sessionen 206 | * Synkar samtalet via databas 207 | * Utnyttjar webbsystemets menysystem 208 | * Agerar slav till webbsystemet 209 | 210 | --- 211 | 212 | ## Slutledning 213 | 214 | * Fullt möjligt att utveckla 215 | * Kan kodas i olika språk 216 | * Komplicerat 217 | * Kräver många olika delar av Asterisks API:er 218 | * Kräver inga dyra system 219 | * Kan byggas ut med fler funktioner 220 | 221 | --- 222 | 223 | # Olle Wreede 224 | -------------------------------------------------------------------------------- /demo/assets/asterisk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/asterisk.png -------------------------------------------------------------------------------- /demo/assets/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/background.png -------------------------------------------------------------------------------- /demo/assets/continuous-deployment.md: -------------------------------------------------------------------------------- 1 | # Continuous Deployment 2 | 3 | Deploy every change to production 4 | 5 | --- 6 | 7 | ## Steve Jobs 8 | 9 | > Real artists ship. 10 | 11 | --- 12 | 13 | ## Why? 14 | 15 | ### Why use continuous deployment? 16 | 17 | --- 18 | 19 | ## Why? 20 | 21 | ### Shorter turnaround 22 | 23 | --- 24 | 25 | ## Why? 26 | 27 | ### Minimize risk of large releases 28 | 29 | --- 30 | 31 | ## Why? 32 | 33 | ### No diverging branches 34 | 35 | --- 36 | 37 | ## Why? 38 | 39 | ### Easier to innovate 40 | 41 | --- 42 | 43 | ## Why? 44 | 45 | ### Faster feedback 46 | 47 | --- 48 | 49 | ## Why? 50 | 51 | ### Less unnecessary work 52 | 53 | --- 54 | 55 | ## Why? 56 | 57 | ### Mitigate fear of change 58 | 59 | --- 60 | 61 | ## Salvador Dali 62 | 63 | > Have no fear of perfection - 64 | > 65 | > you'll never reach it. 66 | 67 | --- 68 | 69 | ## How? 70 | 71 | How does continuous deployment work? 72 | 73 | --- 74 | 75 | ## How? 76 | 77 | ### Continuous integration 78 | 79 | --- 80 | 81 | ## How? 82 | 83 | ### Continuous delivery 84 | 85 | --- 86 | 87 | ## How? 88 | 89 | ### Small and independent changes 90 | 91 | --- 92 | 93 | ## How? 94 | 95 | ### Independently deployed applications 96 | 97 | --- 98 | 99 | ## How? 100 | 101 | ### Automated functional tests 102 | 103 | --- 104 | 105 | ## How? 106 | 107 | ### Monitoring and alerts 108 | 109 | --- 110 | 111 | ## How? 112 | 113 | ### Roll forward instead of rollback 114 | 115 | --- 116 | 117 | ## How? 118 | 119 | ### Feature flags 120 | 121 | --- 122 | 123 | ## Benjamin Disraeli 124 | 125 | > Change is inevitable. 126 | > 127 | > Change is constant. 128 | 129 | --- 130 | 131 | ## Feature flags 132 | 133 | ### Add new code without branching 134 | 135 | --- 136 | 137 | ## Feature flags 138 | 139 | ### Hide entry point in interface 140 | 141 | --- 142 | 143 | ## Feature flags 144 | 145 | ### Use flags in application code sparingly 146 | 147 | --- 148 | 149 | ## Feature flags 150 | 151 | ### Retire old flags 152 | 153 | --- 154 | 155 | ## Feature flags 156 | 157 | ### Test with flags activated in „release“ 158 | 159 | ### Test with all flags activated 160 | 161 | --- 162 | 163 | ## Feature flags 164 | 165 | ### Flags activated when customer is ready 166 | 167 | --- 168 | 169 | ## Feature flags 170 | 171 | ### Use only when necessary 172 | 173 | --- 174 | 175 | ## Feature flags 176 | 177 | ### Break down features into releasable parts 178 | 179 | --- 180 | 181 | ## Summary 182 | 183 | --- 184 | 185 | ## Mark Twain 186 | 187 | > The secret of getting ahead is getting started. 188 | 189 | --- 190 | 191 | ## Summary 192 | 193 | ### Smaller releases more often 194 | 195 | --- 196 | 197 | ## Summary 198 | 199 | ### Use feature flags when needed 200 | 201 | --- 202 | 203 | # Olle Wreede 204 | 205 | @ollej 206 | -------------------------------------------------------------------------------- /demo/assets/continuous-deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/continuous-deployment.png -------------------------------------------------------------------------------- /demo/assets/default-theme-advent-of-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/default-theme-advent-of-code.png -------------------------------------------------------------------------------- /demo/assets/default-theme-application-events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/default-theme-application-events.png -------------------------------------------------------------------------------- /demo/assets/default-theme-asterisk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/default-theme-asterisk.png -------------------------------------------------------------------------------- /demo/assets/default-theme-continuous-deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/default-theme-continuous-deployment.png -------------------------------------------------------------------------------- /demo/assets/default-theme-event-sourcing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/default-theme-event-sourcing.png -------------------------------------------------------------------------------- /demo/assets/default-theme-homebrewing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/default-theme-homebrewing.png -------------------------------------------------------------------------------- /demo/assets/default-theme-macroquad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/default-theme-macroquad.png -------------------------------------------------------------------------------- /demo/assets/default-theme-raku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/default-theme-raku.png -------------------------------------------------------------------------------- /demo/assets/default-theme-rusty-aquarium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/default-theme-rusty-aquarium.png -------------------------------------------------------------------------------- /demo/assets/default-theme-rusty-slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/default-theme-rusty-slider.png -------------------------------------------------------------------------------- /demo/assets/default-theme-vim-emacs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/default-theme-vim-emacs.png -------------------------------------------------------------------------------- /demo/assets/default-theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_image": "assets/background.png", 3 | "background_color": "#301934", 4 | "heading_color": "#b19cd9", 5 | "text_color": "#ffffff", 6 | "align": "center", 7 | "font": "assets/Amble-Regular.ttf", 8 | "font_bold": "assets/Amble-Bold.ttf", 9 | "font_italic": "assets/Amble-Italic.ttf", 10 | "font_size_header_title": 100, 11 | "font_size_header_slides": 80, 12 | "font_size_text": 50, 13 | "vertical_offset": 60.0, 14 | "horizontal_offset": 100.0, 15 | "line_height": 2.0, 16 | "blockquote_background_color": "#333333", 17 | "blockquote_padding": 20.0, 18 | "blockquote_left_quote": "“", 19 | "blockquote_right_quote": "„", 20 | "font_code": "assets/Hack-Regular.ttf", 21 | "font_code_size": 20, 22 | "code_line_height": 1.2, 23 | "code_background_color": "#002b36", 24 | "code_theme": "Solarized (dark)", 25 | "code_tab_width": 2, 26 | "bullet": "• ", 27 | "shader": true, 28 | "transition": "starburst" 29 | } 30 | -------------------------------------------------------------------------------- /demo/assets/default-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/default-theme.png -------------------------------------------------------------------------------- /demo/assets/event-sourcing.md: -------------------------------------------------------------------------------- 1 | # Event Sourcing 2 | 3 | --- 4 | 5 | ## Capture state change as a sequence of events 6 | 7 | --- 8 | 9 | ## How we got where we are 10 | 11 | --- 12 | 13 | ## Adjust state to handle retroactive changes 14 | 15 | --- 16 | 17 | ## Query log to get past state 18 | 19 | --- 20 | 21 | ## All change initiated by event objects 22 | 23 | --- 24 | 25 | ## Rebuild state from event log 26 | 27 | --- 28 | 29 | ## Replay events to debug with real data 30 | 31 | --- 32 | 33 | ## Temporal query to find state at any point in time 34 | 35 | --- 36 | 37 | ## Official state is either state or the event log 38 | 39 | --- 40 | 41 | ## State stored in memory 42 | 43 | ## with snapshots stored to disk 44 | 45 | --- 46 | 47 | ## State stored in a database with event log 48 | 49 | ## used for special processing and audits 50 | 51 | --- 52 | 53 | ## Event reversing 54 | 55 | Change as the difference of state 56 | 57 | or 58 | 59 | Store data needed for reversal 60 | 61 | --- 62 | 63 | ## Reprocess events to take advantag 64 | 65 | ## e of new features or bug fixes 66 | 67 | --- 68 | 69 | ## Replay events to test upgrades 70 | 71 | --- 72 | 73 | ## Handle external updates with a gateway which 74 | 75 | ## is aware that the system is processing replays 76 | 77 | --- 78 | 79 | ## External queries store responses in gateway, 80 | 81 | ## or queries for date specific value 82 | 83 | --- 84 | 85 | ## Loosely coupled parallel systems 86 | 87 | ## excellent for horizontal scaling 88 | 89 | --- 90 | 91 | ## Separate reading from writing 92 | 93 | --- 94 | 95 | ## New applications can easily be added 96 | 97 | ## by tapping in to the event stream 98 | 99 | --- 100 | 101 | # Olle Wreede 102 | 103 | @ollej 104 | -------------------------------------------------------------------------------- /demo/assets/event-sourcing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/event-sourcing.png -------------------------------------------------------------------------------- /demo/assets/exalted-beer-advent-of-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/exalted-beer-advent-of-code.png -------------------------------------------------------------------------------- /demo/assets/exalted-beer-application-events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/exalted-beer-application-events.png -------------------------------------------------------------------------------- /demo/assets/exalted-beer-asterisk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/exalted-beer-asterisk.png -------------------------------------------------------------------------------- /demo/assets/exalted-beer-continuous-deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/exalted-beer-continuous-deployment.png -------------------------------------------------------------------------------- /demo/assets/exalted-beer-event-sourcing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/exalted-beer-event-sourcing.png -------------------------------------------------------------------------------- /demo/assets/exalted-beer-homebrewing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/exalted-beer-homebrewing.png -------------------------------------------------------------------------------- /demo/assets/exalted-beer-macroquad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/exalted-beer-macroquad.png -------------------------------------------------------------------------------- /demo/assets/exalted-beer-raku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/exalted-beer-raku.png -------------------------------------------------------------------------------- /demo/assets/exalted-beer-rusty-aquarium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/exalted-beer-rusty-aquarium.png -------------------------------------------------------------------------------- /demo/assets/exalted-beer-rusty-slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/exalted-beer-rusty-slider.png -------------------------------------------------------------------------------- /demo/assets/exalted-beer-vim-emacs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/exalted-beer-vim-emacs.png -------------------------------------------------------------------------------- /demo/assets/exalted-beer.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_image": "assets/exaltedbeer.png", 3 | "background_color": "#222222", 4 | "heading_color": "#34A5DA", 5 | "text_color": "#A6AAA9", 6 | "align": "left", 7 | "font": "assets/Amble-Regular.ttf", 8 | "font_bold": "assets/Amble-Bold.ttf", 9 | "font_italic": "assets/Amble-Italic.ttf", 10 | "font_size_header_title": 120, 11 | "font_size_header_slides": 80, 12 | "font_size_text": 40, 13 | "vertical_offset": 80.0, 14 | "horizontal_offset": 80.0, 15 | "line_height": 1.5, 16 | "blockquote_background_color": "#333333", 17 | "blockquote_padding": 40.0, 18 | "blockquote_left_quote": "“", 19 | "blockquote_right_quote": "„", 20 | "font_code": "assets/Hack-Regular.ttf", 21 | "font_code_size": 20, 22 | "code_line_height": 1.2, 23 | "code_background_color": "#e0e0e0", 24 | "code_theme": "base16-ocean.light", 25 | "code_tab_width": 4, 26 | "bullet": "» ", 27 | "shader": false, 28 | "transition": "split" 29 | } 30 | -------------------------------------------------------------------------------- /demo/assets/exalted-beer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/exalted-beer.png -------------------------------------------------------------------------------- /demo/assets/exaltedbeer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/exaltedbeer.png -------------------------------------------------------------------------------- /demo/assets/ferris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/ferris.png -------------------------------------------------------------------------------- /demo/assets/homebrewing.md: -------------------------------------------------------------------------------- 1 | # Homebrewing 2 | 3 | --- 4 | 5 | ## Types of beer 6 | 7 | * Bottom fermented 8 | * Top fermented 9 | * Wild and sour beers 10 | 11 | --- 12 | 13 | ## Bottom fermented 14 | 15 | * Lager, pilsner, bock 16 | * Cold fermentation 17 | 18 | --- 19 | 20 | ## Top fermented 21 | 22 | * Ale, porter, stout 23 | * Warm fermentation 24 | 25 | --- 26 | 27 | ## Wild and sour beers 28 | 29 | * Spontaneous fermentation 30 | * Acidifying bacteria 31 | 32 | --- 33 | 34 | ## Brewing process 35 | 36 | 1. Preparation and cleaning 37 | 1. Mashing 38 | 1. Lautering 39 | 1. Boiling the wort 40 | 1. Chilling and aeration 41 | 1. Fermentation 42 | 1. Bottling 43 | 44 | --- 45 | 46 | ## Mashing 47 | 48 | * Malt, mainly from barley, is crushed 49 | * Mashed for 60 minutes at 64-68 C to convert starch 50 | * Single infusion, temperature, or decoction 51 | * Recirculation through malt bed 52 | 53 | --- 54 | 55 | ## Lautering 56 | 57 | * At the end of the mashing 58 | * Mash out at 78 C to stop saccharification and ease lautering 59 | * Sweet wort is extracted by lautering with 78 C water 60 | 61 | --- 62 | 63 | ## Boiling wort 64 | 65 | * Sweet wort is boiled for 60-90 minutes 66 | * Sterilizes 67 | * Increases sugar concentration 68 | * Removes protein 69 | 70 | --- 71 | 72 | ## Adding hops 73 | 74 | * Hops are added in three steps during the boil 75 | * Bitter hops at 60 minutes before end of boil 76 | * Flavoring hops boiled for 20-30 minutes 77 | * Aroma hops is added in the last five minutes 78 | * Hop stand after boiling 79 | 80 | --- 81 | 82 | ## Chilling 83 | 84 | * The boiled wort is chilled 85 | * Aeration to help yeast reproduce 86 | * Yeast culture added 87 | * Yeast converts sugar to alcohol 88 | 89 | --- 90 | 91 | ## Fermentation 92 | 93 | * Takes up to two weeks 94 | * American IPA is dry hopped after primary fermentation 95 | * Lager ferments at 4-12C 96 | * Ales ferments at 18-22C 97 | * Saison ferments at 22-32 C 98 | 99 | --- 100 | 101 | ## Bottling and carbonation 102 | 103 | * After 2-3 weeks fermentation is complete 104 | * Priming sugar is mixed with beer 105 | * Beer is added to bottles and capped 106 | * Carbonation takes a couple of weeks 107 | * Some beers are aged longer 108 | * Drink and enjoy! 109 | 110 | --- 111 | 112 | ### Plato 113 | 114 | > He was a wise man who invented beer 115 | 116 | --- 117 | 118 | # Olle Wreede 119 | 120 | ## Exalted Beer 121 | 122 | www.exalted.beer 123 | -------------------------------------------------------------------------------- /demo/assets/homebrewing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/homebrewing.png -------------------------------------------------------------------------------- /demo/assets/macroquad.md: -------------------------------------------------------------------------------- 1 | # Macroquad 2 | 3 | --- 4 | 5 | ## Vad är Macroquad? 6 | 7 | Ett enkelt och lättanvänt spelramverk i Rust 8 | 9 | Innehåller allt för att bygga ett 2D-spel! 10 | 11 | Utvecklas av Fedor Logachev 12 | 13 | https://macroquad.rs 14 | 15 | --- 16 | 17 | ## Designmål 18 | 19 | Några av Macroquads designmål: 20 | 21 | * Snabb kompilering 22 | * Korsplattform 23 | * Stödjer budgetenheter 24 | * Hackbar 25 | 26 | --- 27 | 28 | ## Allt inkluderat 29 | 30 | * 2D-rendering 31 | * Audio 32 | * Immediate mode UI 33 | * Bygg för Android/iOS/WASM med endast ett kommando 34 | 35 | --- 36 | 37 | ## Baserat på Miniquad 38 | 39 | Miniquad är ett minimalt grafikabstraktionslager 40 | 41 | --- 42 | 43 | ## Stödda plattformar 44 | 45 | * Windows 46 | * MacOS 47 | * Linux 48 | * HTML5 / WebAssembly 49 | * Android 50 | * iOS 51 | 52 | --- 53 | 54 | ## Minimalt exempel 55 | 56 | ```rust 57 | use macroquad::prelude::*; 58 | 59 | #[macroquad::main("Macroquad")] 60 | async fn main() { 61 | loop { 62 | clear_background(LIGHTGRAY); 63 | next_frame().await 64 | } 65 | } 66 | ``` 67 | 68 | --- 69 | 70 | ## Minimalt exempel 71 | 72 | * Importera macroquad 73 | * Macro för main-funktionen 74 | * Asynkron main-funktion 75 | * Rensa bakgrunden i början av loopen 76 | * Avsluta med att vänta på nästa frame 77 | 78 | --- 79 | 80 | ## Fönsterkonfiguration 81 | 82 | * Macrot skapar ett fönster. 83 | * Det går även att konfigurera fönstret. 84 | * Använd en funktion som returnerar `Conf`. 85 | * Ändra storlek, fullskärm, titel med mera 86 | 87 | --- 88 | 89 | ## Fönsterkonfiguration 90 | 91 | ```rust 92 | use macroquad::prelude::*; 93 | 94 | fn window_conf() -> Conf { 95 | Conf { 96 | window_title: "Window name".to_owned(), 97 | fullscreen: false, 98 | high_dpi: true, 99 | window_resizable: true, 100 | window_width: 640, 101 | window_height: 480, 102 | ..Default::default() 103 | } 104 | } 105 | 106 | #[macroquad::main(window_conf)] 107 | async fn main() { 108 | loop { 109 | clear_background(LIGHTGRAY); 110 | next_frame().await 111 | } 112 | } 113 | ``` 114 | 115 | --- 116 | 117 | ## Visa bild 118 | 119 | * Ladda bild med `load_texture()` 120 | * Stödjer endast PNG 121 | * Visa bild med `draw_texture()` 122 | * Placera med `screen_width()` och `screen_height()` 123 | * Använd bakgrundsfärgen `WHITE` 124 | * Laddar bilden asynkront 125 | * Fungerar båda lokalt och via WebAssembly 126 | 127 | --- 128 | 129 | ## Visa bild 130 | 131 | ```rust 132 | use macroquad::prelude::*; 133 | 134 | #[macroquad::main("Bild")] 135 | async fn main() { 136 | let texture: Texture2D = load_texture("examples/ferris.png").await.unwrap(); 137 | loop { 138 | clear_background(LIGHTGRAY); 139 | draw_texture( 140 | texture, 141 | screen_width() / 2. - texture.width() / 2., 142 | screen_height() / 2. - texture.height() / 2., 143 | WHITE, 144 | ); 145 | next_frame().await 146 | } 147 | } 148 | ``` 149 | 150 | --- 151 | 152 | ## Texture API 153 | 154 | Metoder för att arbeta med texturer. 155 | 156 | * `build_textures_atlas()` 157 | * `draw_texture( ... )` 158 | * `draw_texture_ex( ... )` 159 | * `get_screen_data()` 160 | * `load_image( ... )` 161 | * `load_texture( ... )` 162 | * `render_target( ... )` 163 | 164 | --- 165 | 166 | ## Rita 167 | 168 | Stöd för att rita enklare figurer med bakgrundsfärg. 169 | 170 | * Cirkel 171 | * Rektangel 172 | * Linje 173 | * Hexagon 174 | * Triangel 175 | * Polygon 176 | 177 | --- 178 | 179 | ## Rita 180 | 181 | ```rust 182 | use macroquad::prelude::*; 183 | 184 | #[macroquad::main("Rita")] 185 | async fn main() { 186 | loop { 187 | clear_background(LIGHTGRAY); 188 | draw_rectangle(screen_width() / 2.0 - 60.0, 100.0, 120.0, 60.0, GREEN); 189 | next_frame().await 190 | } 191 | } 192 | ``` 193 | 194 | --- 195 | 196 | ## Shapes API 197 | 198 | * `draw_circle(x: f32, y: f32, r: f32, color: Color)` 199 | * `draw_hexagon( ... )` 200 | * `draw_line( ... )` 201 | * `draw_poly( ... )` 202 | * `draw_rectangle( ... )` 203 | * `draw_triangle( ... )` 204 | 205 | --- 206 | 207 | ## Rita 208 | 209 | Rita figurer med konturer. 210 | 211 | --- 212 | 213 | ## Shapes API lines 214 | 215 | * `draw_circle_lines( ... )` 216 | * `draw_poly_lines( ... )` 217 | * `draw_rectangle_lines( ... )` 218 | * `draw_triangle_lines( ... )` 219 | 220 | --- 221 | 222 | ### Text 223 | 224 | Fonter kan användas för att visa text 225 | 226 | * Ladda font med `load_ttf_font()` 227 | * Använd `measure_text()` för att räkna ut baslinjen 228 | * Visa texten med `draw_text()` eller `draw_text_ex()` 229 | * Y-koordinaten börjar uppifrån 230 | * Y-koordinaten anger baslinjen 231 | 232 | --- 233 | 234 | ## Text 235 | 236 | ```rust 237 | use macroquad::prelude::*; 238 | 239 | #[macroquad::main("Text")] 240 | async fn main() { 241 | loop { 242 | clear_background(LIGHTGRAY); 243 | let text = "Hej världen!"; 244 | let font_size = 30; 245 | let font = load_ttf_font("examples/Hack-Regular.ttf").await.ok(); 246 | let dim = measure_text(text, font, font_size, 1.0); 247 | draw_text(text, 30.0, dim.offset_y, font_size as f32, DARKGRAY); 248 | next_frame().await 249 | } 250 | } 251 | ``` 252 | 253 | --- 254 | 255 | ### Text API 256 | 257 | * `camera_font_scale( ... )` 258 | * `draw_text( ... )` 259 | * `draw_text_ex( ... )` 260 | * `get_text_center( ... )` 261 | * `load_ttf_font( ... )` 262 | * `load_ttf_font_from_bytes( ... )` 263 | * `measure_text( ... )` 264 | 265 | --- 266 | 267 | ## Hantera input 268 | 269 | ```rust 270 | use macroquad::prelude::*; 271 | 272 | #[macroquad::main("Input")] 273 | async fn main() { 274 | let mut x = screen_width() / 2.0; 275 | let mut y = screen_height() / 2.0; 276 | 277 | loop { 278 | clear_background(LIGHTGRAY); 279 | 280 | if is_key_down(KeyCode::Right) { x += 1.0; } 281 | if is_key_down(KeyCode::Left) { x -= 1.0; } 282 | if is_key_down(KeyCode::Down) { y += 1.0; } 283 | if is_key_down(KeyCode::Up) { y -= 1.0; } 284 | 285 | draw_circle(x, y, 15.0, YELLOW); 286 | next_frame().await 287 | } 288 | } 289 | ``` 290 | 291 | --- 292 | 293 | ## Tangentbordsinput 294 | 295 | * `get_char_pressed() -> Option` 296 | * `get_last_key_pressed() -> Option` 297 | * `is_key_down(key_code: KeyCode)` 298 | * `is_key_pressed(key_code: KeyCode)` 299 | * `is_key_released(key_code: KeyCode)` 300 | 301 | --- 302 | 303 | ## KeyCode enum 304 | 305 | * `Space` 306 | * `Up` 307 | * `Down` 308 | * `Left` 309 | * `Right` 310 | * `A .. Z` 311 | * `0 .. 9` 312 | * ... 313 | 314 | --- 315 | 316 | ## Mus-input 317 | 318 | * `is_mouse_button_down(btn: MouseButton)` 319 | * `is_mouse_button_pressed(btn: MouseButton)` 320 | * `is_mouse_button_released(btn: MouseButton)` 321 | * `mouse_position -> (f32, f32)` 322 | * `mouse_position_local -> Vec2` 323 | * `mouse_wheel -> (f32, f32)` 324 | * `set_cursor_grab(grab: bool)` 325 | * `show_mouse(shown: bool)` 326 | 327 | --- 328 | 329 | ## MouseButton enum 330 | 331 | * `Right` 332 | * `Left` 333 | * `Middle` 334 | * `Unknown` 335 | 336 | --- 337 | 338 | ## Touch input för mobiler 339 | 340 | * `simulate_mouse_with_touch(option: bool)` 341 | * `touches -> Vec` 342 | * `touches_local -> Vec` 343 | 344 | --- 345 | 346 | ## TouchPhase enum 347 | 348 | * `Started` 349 | * `Stationary` 350 | * `Moved` 351 | * `Ended` 352 | * `Cancelled` 353 | 354 | --- 355 | 356 | ## Ljud 357 | 358 | ```rust 359 | use macroquad::{ 360 | audio::{load_sound, play_sound, PlaySoundParams}, 361 | window::next_frame, 362 | }; 363 | 364 | #[macroquad::main("Ljud")] 365 | async fn main() { 366 | let sound = load_sound("examples/sound.wav").await.unwrap(); 367 | play_sound( 368 | sound, 369 | PlaySoundParams { 370 | looped: true, 371 | volume: 1., 372 | }, 373 | ); 374 | loop { 375 | set_sound_volume(sound, 0.1); 376 | next_frame().await; 377 | } 378 | } 379 | ``` 380 | 381 | --- 382 | 383 | ## Sound API 384 | 385 | * `load_sound(path: &str)` 386 | * `load_sound_from_bytes(data: &[u8])` 387 | * `play_sound(sound: Sound, params: PlaySoundParams)` 388 | * `play_sound_once(sound: Sound)` 389 | * `set_sound_volume(sound: Sound, volume: f32)` 390 | * `stop_sound(sound: Sound)` 391 | 392 | --- 393 | 394 | ## Filladdning 395 | 396 | ```rust 397 | use macroquad::prelude::*; 398 | 399 | #[macroquad::main("File")] 400 | async fn main() -> Result<(), FileError> { 401 | set_pc_assets_folder("assets"); 402 | let text = load_string("test.txt").await?; 403 | println!("Content: {}", text); 404 | Ok(()) 405 | } 406 | ``` 407 | 408 | --- 409 | 410 | ## File API 411 | 412 | * `load_file(path: &str)` 413 | * `load_string(path: &str)` 414 | * `set_pc_assets_folder(path: &str)` 415 | 416 | --- 417 | 418 | ## Tid 419 | 420 | ```rust 421 | use macroquad::prelude::*; 422 | 423 | #[macroquad::main("Time")] 424 | async fn main() { 425 | let start_time = get_time(); 426 | let mut elapsed: f32 = 0.0; 427 | loop { 428 | elapsed += get_frame_time(); 429 | if elapsed > 1.0 { 430 | println!( 431 | "FPS: {} Elapsed time: {:.3}", 432 | get_fps(), 433 | get_time() - start_time 434 | ); 435 | elapsed = 0.0; 436 | } 437 | next_frame().await 438 | } 439 | } 440 | ``` 441 | 442 | --- 443 | 444 | ## Time API 445 | 446 | * `get_fps() -> i32` 447 | * `get_frame_time() -> f32` 448 | * `get_time() -> f64` 449 | 450 | --- 451 | 452 | ## Färger 453 | 454 | ```rust 455 | use macroquad::prelude::*; 456 | 457 | #[macroquad::main("Colors")] 458 | async fn main() { 459 | let red = Color::from_rgba(255, 0, 0, 0); 460 | loop { 461 | clear_background(red); 462 | next_frame().await 463 | } 464 | } 465 | ``` 466 | 467 | --- 468 | 469 | ## Color API 470 | 471 | * `Color::new(r: f32, g: f32, b: f32, a: f32)` 472 | * `Color::from_rgba(r: u8, g: u8, b: u8, a: u8)` 473 | * `Color::from_vec(vec: Vec4)` 474 | * `hsl_to_rgb(h: f32, s: f32, l: f32)` 475 | * `rgb_to_hsl(color: Color) -> (f32, f32, f32)` 476 | * `macroquad::color::colors::BLACK` ... 477 | 478 | --- 479 | 480 | ## Slumpning 481 | 482 | ```rust 483 | use macroquad::{miniquad, rand::{self, ChooseRandom}}; 484 | 485 | #[macroquad::main("Particles")] 486 | async fn main() { 487 | rand::srand(miniquad::date::now() as u64); 488 | let dice = rand::gen_range(1, 6); 489 | let list = vec!["foo", "bar", "baz"]; 490 | let string = list.choose().unwrap(); 491 | println!("Tärningsslag: {}, Slumpad text: {}", dice, string); 492 | } 493 | ``` 494 | 495 | --- 496 | 497 | ## Rand API 498 | 499 | * `gen_range(low: T, high: T) -> T` 500 | * `rand() -> u32` 501 | * `srand(seed: u64)` 502 | 503 | --- 504 | 505 | ## ChooseRandom trait 506 | 507 | * `shuffle` 508 | * `choose` 509 | * `choose_mut` 510 | * `choose_multiple(amount: usize)` 511 | 512 | --- 513 | 514 | ## Partiklar 515 | 516 | ```rust 517 | use macroquad::prelude::*; 518 | use macroquad_particles::{Curve, Emitter, EmitterConfig, ParticleShape}; 519 | 520 | #[macroquad::main("Particles")] 521 | async fn main() { 522 | let mut emitter = Emitter::new(EmitterConfig { 523 | initial_velocity: 500.0, 524 | initial_direction_spread: 2. * std::f32::consts::PI, 525 | shape: ParticleShape::Circle { subdivisions: 360 }, 526 | size_curve: Some(Curve { 527 | points: vec![(0.0, 0.5), (0.5, 1.0), (1.0, 0.0)], 528 | ..Default::default() 529 | }), 530 | ..Default::default() 531 | }); 532 | 533 | loop { 534 | clear_background(BLACK); 535 | emitter.draw(vec2(screen_width() / 2., screen_height() / 2.)); 536 | next_frame().await 537 | } 538 | } 539 | ``` 540 | 541 | --- 542 | 543 | ## Olle Wreede 544 | 545 | @ollej@hachyderm.io 546 | 547 | ![Agical](assets/agical-logo.png) 548 | -------------------------------------------------------------------------------- /demo/assets/raku.md: -------------------------------------------------------------------------------- 1 | # Raku 2 | 3 | An introduction to Raku programming language 4 | 5 | --- 6 | 7 | ## What is Raku? 8 | 9 | * A language definition 10 | * Multiple implementations 11 | * Not Perl 5 12 | * Used to be Perl 6 13 | * Released on December 25th 2015 14 | 15 | --- 16 | 17 | ## Rakudo 18 | 19 | A compiler for Raku with multiple virtual machine backends. 20 | 21 | --- 22 | 23 | ## Backends 24 | 25 | * MoarVM 26 | * JVM 27 | * Parrot 28 | 29 | --- 30 | 31 | ## Rakudo Star 32 | 33 | * A "useful" early adopter distribution 34 | * Includes Rakudo and useful modules. 35 | 36 | --- 37 | 38 | ## Raku features 39 | 40 | A short overview of new features. 41 | 42 | --- 43 | 44 | ## Grammar 45 | 46 | * Named regexes 47 | * New syntax 48 | * Grouping regexes with Grammar class 49 | * Decorators: regex, token, rule 50 | 51 | --- 52 | 53 | ## Command line scripts 54 | 55 | Built in syntax for command line option parsing. 56 | 57 | --- 58 | 59 | ## Positional command line options 60 | 61 | ```raku 62 | sub MAIN($x, $y) { ... } 63 | sub USAGE() { 64 | say "Usage: foo.pl "; 65 | } 66 | 67 | # foo.pl 42 1337 68 | ``` 69 | 70 | --- 71 | 72 | ## Named parameters 73 | 74 | ```raku 75 | sub MAIN(Bool :$verbose) { ... } 76 | 77 | # foo.pl --verbose 78 | ``` 79 | 80 | --- 81 | 82 | ## Options with arguments 83 | 84 | ```raku 85 | sub MAIN(:$foo = 'bar') { ... } 86 | 87 | # foo.pl --foo=baz 88 | ``` 89 | 90 | --- 91 | 92 | ## Gradual typing 93 | 94 | ```raku 95 | method foo($bar) { ... } 96 | ``` 97 | vs 98 | ```raku 99 | method foo(Str $bar) { ... } 100 | ``` 101 | 102 | --- 103 | 104 | ## User defineable operators 105 | 106 | ```raku 107 | 'bar' ¨ $foo; 108 | ``` 109 | ```raku 110 | sub infix:<¨ >($method, $obj) { 111 | say $obj."$method"(); 112 | } 113 | # $foo->bar(); 114 | ``` 115 | 116 | --- 117 | 118 | ## Threading 119 | 120 | * Implemented using promises and channels 121 | * Wraps implementations in different backends. 122 | 123 | --- 124 | 125 | ## Promises 126 | 127 | A synchronization primitive. 128 | ```raku 129 | my $kept_in_10 = Promise.in(10); 130 | ``` 131 | 132 | --- 133 | 134 | ## Channels 135 | 136 | Sending results between threads. 137 | 138 | ```raku 139 | my $dest = Channel.new; 140 | start { 141 | loop { 142 | $dest.send('foo'); 143 | } 144 | $dest.close; 145 | } 146 | ``` 147 | 148 | --- 149 | 150 | ## Who should use Raku? 151 | 152 | * Interested in language development 153 | * Grammar parsing 154 | 155 | --- 156 | 157 | # Olle Wreede 158 | 159 | @ollej 160 | -------------------------------------------------------------------------------- /demo/assets/raku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/raku.png -------------------------------------------------------------------------------- /demo/assets/rust-advent-of-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rust-advent-of-code.png -------------------------------------------------------------------------------- /demo/assets/rust-application-events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rust-application-events.png -------------------------------------------------------------------------------- /demo/assets/rust-asterisk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rust-asterisk.png -------------------------------------------------------------------------------- /demo/assets/rust-continuous-deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rust-continuous-deployment.png -------------------------------------------------------------------------------- /demo/assets/rust-event-sourcing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rust-event-sourcing.png -------------------------------------------------------------------------------- /demo/assets/rust-homebrewing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rust-homebrewing.png -------------------------------------------------------------------------------- /demo/assets/rust-macroquad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rust-macroquad.png -------------------------------------------------------------------------------- /demo/assets/rust-raku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rust-raku.png -------------------------------------------------------------------------------- /demo/assets/rust-rusty-aquarium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rust-rusty-aquarium.png -------------------------------------------------------------------------------- /demo/assets/rust-rusty-slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rust-rusty-slider.png -------------------------------------------------------------------------------- /demo/assets/rust-vim-emacs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rust-vim-emacs.png -------------------------------------------------------------------------------- /demo/assets/rust.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_image": "assets/rust.png", 3 | "background_color": "#ffffff", 4 | "heading_color": "#000000", 5 | "text_color": "#000000", 6 | "align": "left", 7 | "font": "assets/Amble-Regular.ttf", 8 | "font_bold": "assets/Amble-Bold.ttf", 9 | "font_italic": "assets/Amble-Italic.ttf", 10 | "font_size_header_title": 100, 11 | "font_size_header_slides": 80, 12 | "font_size_text": 40, 13 | "vertical_offset": 85.0, 14 | "horizontal_offset": 300.0, 15 | "line_height": 2.0, 16 | "blockquote_background_color": "#0b7261", 17 | "blockquote_padding": 20.0, 18 | "blockquote_left_quote": "“", 19 | "blockquote_right_quote": "„", 20 | "font_code": "assets/Hack-Regular.ttf", 21 | "font_code_size": 20, 22 | "code_line_height": 1.2, 23 | "code_background_color": "#2a3439", 24 | "code_theme": "Solarized (dark)", 25 | "code_tab_width": 2, 26 | "bullet": "• ", 27 | "shader": false, 28 | "transition": "radialin" 29 | } 30 | -------------------------------------------------------------------------------- /demo/assets/rust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rust.png -------------------------------------------------------------------------------- /demo/assets/rusty-advent-of-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rusty-advent-of-code.png -------------------------------------------------------------------------------- /demo/assets/rusty-application-events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rusty-application-events.png -------------------------------------------------------------------------------- /demo/assets/rusty-aquarium.md: -------------------------------------------------------------------------------- 1 | # Rusty Aquarium 2 | 3 | --- 4 | 5 | ## Montioring visualization 6 | 7 | Visualize monitoring data as a serene aquarium 8 | 9 | --- 10 | 11 | ## Why 12 | 13 | Static graphs are boring 14 | 15 | Opaque for the uninitated 16 | 17 | Looks beautiful 18 | 19 | --- 20 | 21 | ## What can it be used for? 22 | 23 | System monitoring 24 | 25 | Web request tracking 26 | 27 | E-commerce metrics 28 | 29 | --- 30 | 31 | ## How it works 32 | 33 | Periodically reads a JSON file with input data 34 | 35 | Input data defines fish size, speed, and species 36 | 37 | --- 38 | 39 | ## Input data 40 | 41 | The `size`, `speed`, and `bubbles` values are multipliers 42 | 43 | ```json 44 | { 45 | "school": [ 46 | { "fish": "crab", "size": 1.0, "speed": 1.0, "bubbles": 1.0 } 47 | ] 48 | } 49 | ``` 50 | 51 | --- 52 | 53 | ## Generating data 54 | 55 | Programs to generate input data is not included 56 | 57 | System monitoring application included as exemple 58 | 59 | --- 60 | 61 | ## Thank you 62 | 63 | Olle Wreede 64 | 65 | olle.wreede@agical.se 66 | 67 | @ollej 68 | -------------------------------------------------------------------------------- /demo/assets/rusty-aquarium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rusty-aquarium.png -------------------------------------------------------------------------------- /demo/assets/rusty-asterisk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rusty-asterisk.png -------------------------------------------------------------------------------- /demo/assets/rusty-continuous-deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rusty-continuous-deployment.png -------------------------------------------------------------------------------- /demo/assets/rusty-event-sourcing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rusty-event-sourcing.png -------------------------------------------------------------------------------- /demo/assets/rusty-homebrewing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rusty-homebrewing.png -------------------------------------------------------------------------------- /demo/assets/rusty-macroquad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rusty-macroquad.png -------------------------------------------------------------------------------- /demo/assets/rusty-raku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rusty-raku.png -------------------------------------------------------------------------------- /demo/assets/rusty-rusty-aquarium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rusty-rusty-aquarium.png -------------------------------------------------------------------------------- /demo/assets/rusty-rusty-slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rusty-rusty-slider.png -------------------------------------------------------------------------------- /demo/assets/rusty-slider-card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rusty-slider-card.jpg -------------------------------------------------------------------------------- /demo/assets/rusty-slider.md: -------------------------------------------------------------------------------- 1 | # Rusty Slider 2 | 3 | --- 4 | 5 | ## About 6 | 7 | A small tool to display markdown files as a slideshow. 8 | 9 | Use right key or left mouse button to go to next slide. 10 | 11 | --- 12 | 13 | ## Markdown 14 | 15 | * Slides are written in markdown. 16 | * Supports headers, paragraphs, lists, blockquotes, code blocks. 17 | * Also supports images 18 | * Text between horizontal lines will be a slide. 19 | 20 | --- 21 | 22 | ## Theme 23 | 24 | Colors and fonts can be configured in a json file. 25 | 26 | A background image can also be defined in the theme file. 27 | 28 | --- 29 | 30 | ## Rust + Macroquad 31 | 32 | Developed with Rust and the macroquad game library. 33 | 34 | --- 35 | 36 | ## Cross-platform 37 | 38 | Supports Windows, MacOS, Linux and web. 39 | 40 | --- 41 | 42 | ## Supports images 43 | 44 | ![Image Title](assets/ferris.png) 45 | 46 | --- 47 | 48 | ## Code 49 | 50 | Code blocks will be rendered with syntax highlighting. 51 | 52 | ```rust 53 | let shader_material = load_material( 54 | shaders::crt::VERTEX, 55 | shaders::crt::FRAGMENT, 56 | Default::default(), 57 | ) 58 | .unwrap(); 59 | ``` 60 | 61 | --- 62 | 63 | ## Run code! 64 | 65 | Execute code blocks by pressing `enter`. 66 | 67 | ```bash 68 | echo "Hello, World!" 69 | ``` 70 | 71 | --- 72 | 73 | ## Compiling Rust 74 | 75 | Rust code blocks can be compiled and executed as well. 76 | 77 | ```rust 78 | fn main() { 79 | println!("Hello, world!"); 80 | } 81 | ``` 82 | 83 | --- 84 | 85 | ## Blockquotes 86 | 87 | > Blockquotes renders with background color 88 | > 89 | > *And fancy quotes.* 90 | 91 | --- 92 | 93 | ## Controls 94 | 95 | Left/right keys switches between previous and next slide. 96 | 97 | Escape quits the slideshow. 98 | 99 | Space toggles the shader. 100 | 101 | Enter executes code in first code block. 102 | 103 | --- 104 | 105 | ## Usage 106 | 107 | 1. Create a markdown file called `slides.md` in `assets` directory. 108 | 1. Optionally add a `theme.json` in `assets` directory. 109 | 110 | --- 111 | 112 | ## Possible improvements 113 | 114 | Add transitions. 115 | 116 | Support more shaders. 117 | 118 | --- 119 | 120 | ## License 121 | 122 | **Copyright 2022 Olle Wreede** 123 | 124 | Released under the MIT license. 125 | -------------------------------------------------------------------------------- /demo/assets/rusty-slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rusty-slider.png -------------------------------------------------------------------------------- /demo/assets/rusty-vim-emacs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rusty-vim-emacs.png -------------------------------------------------------------------------------- /demo/assets/rusty.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_image": "assets/background.png", 3 | "background_color": "#753204", 4 | "heading_color": "#e37831", 5 | "text_color": "#cccccc", 6 | "align": "left", 7 | "font": "assets/Amble-Regular.ttf", 8 | "font_bold": "assets/Amble-Bold.ttf", 9 | "font_italic": "assets/Amble-Italic.ttf", 10 | "font_size_header_title": 100, 11 | "font_size_header_slides": 80, 12 | "font_size_text": 40, 13 | "vertical_offset": 60.0, 14 | "horizontal_offset": 100.0, 15 | "line_height": 1.5, 16 | "blockquote_background_color": "#333333", 17 | "blockquote_padding": 20.0, 18 | "blockquote_left_quote": "“", 19 | "blockquote_right_quote": "„", 20 | "font_code": "assets/Hack-Regular.ttf", 21 | "font_code_size": 20, 22 | "code_line_height": 1.2, 23 | "code_background_color": "#e0e0e0", 24 | "code_theme": "base16-ocean.light", 25 | "code_tab_width": 4, 26 | "bullet": "» ", 27 | "shader": false, 28 | "transition": "swirl" 29 | } 30 | -------------------------------------------------------------------------------- /demo/assets/rusty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/rusty.png -------------------------------------------------------------------------------- /demo/assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/screenshot.png -------------------------------------------------------------------------------- /demo/assets/theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_image": "assets/background.png", 3 | "background_color": "#301934", 4 | "heading_color": "#b19cd9", 5 | "text_color": "#ffffff", 6 | "align": "center", 7 | "font": "assets/Amble-Regular.ttf", 8 | "font_bold": "assets/Amble-Bold.ttf", 9 | "font_italic": "assets/Amble-Italic.ttf", 10 | "font_size_header_title": 100, 11 | "font_size_header_slides": 80, 12 | "font_size_text": 40, 13 | "vertical_offset": 20.0, 14 | "horizontal_offset": 100.0, 15 | "line_height": 2.0, 16 | "blockquote_background_color": "#333333", 17 | "blockquote_padding": 20.0, 18 | "blockquote_left_quote": "“", 19 | "blockquote_right_quote": "„", 20 | "code_font": "assets/Hack-Regular.ttf", 21 | "code_font_size": 20, 22 | "code_line_height": 1.2, 23 | "code_background_color": "#002b36", 24 | "code_theme": "Solarized (dark)", 25 | "code_tab_width": 2, 26 | "bullet": "• ", 27 | "shader": true 28 | } 29 | -------------------------------------------------------------------------------- /demo/assets/thumbnail-agical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/thumbnail-agical.png -------------------------------------------------------------------------------- /demo/assets/thumbnail-default-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/thumbnail-default-theme.png -------------------------------------------------------------------------------- /demo/assets/thumbnail-exalted-beer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/thumbnail-exalted-beer.png -------------------------------------------------------------------------------- /demo/assets/thumbnail-rust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/thumbnail-rust.png -------------------------------------------------------------------------------- /demo/assets/thumbnail-rusty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/thumbnail-rusty.png -------------------------------------------------------------------------------- /demo/assets/thumbnail-uu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/thumbnail-uu.png -------------------------------------------------------------------------------- /demo/assets/transition-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transition-demo.gif -------------------------------------------------------------------------------- /demo/assets/transition-demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transition-demo.mp4 -------------------------------------------------------------------------------- /demo/assets/transition_starburst.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transition_starburst.jpg -------------------------------------------------------------------------------- /demo/assets/transitions/bignoise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/bignoise.png -------------------------------------------------------------------------------- /demo/assets/transitions/blobs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/blobs.png -------------------------------------------------------------------------------- /demo/assets/transitions/checkerboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/checkerboard.png -------------------------------------------------------------------------------- /demo/assets/transitions/circleswipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/circleswipe.png -------------------------------------------------------------------------------- /demo/assets/transitions/cubicnoise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/cubicnoise.png -------------------------------------------------------------------------------- /demo/assets/transitions/curtains.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/curtains.png -------------------------------------------------------------------------------- /demo/assets/transitions/curtainsclose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/curtainsclose.png -------------------------------------------------------------------------------- /demo/assets/transitions/curtainsopen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/curtainsopen.png -------------------------------------------------------------------------------- /demo/assets/transitions/diagonalleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/diagonalleft.png -------------------------------------------------------------------------------- /demo/assets/transitions/diagonalright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/diagonalright.png -------------------------------------------------------------------------------- /demo/assets/transitions/fade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/fade.png -------------------------------------------------------------------------------- /demo/assets/transitions/fan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/fan.png -------------------------------------------------------------------------------- /demo/assets/transitions/halftone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/halftone.png -------------------------------------------------------------------------------- /demo/assets/transitions/implode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/implode.png -------------------------------------------------------------------------------- /demo/assets/transitions/lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/lines.png -------------------------------------------------------------------------------- /demo/assets/transitions/maze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/maze.png -------------------------------------------------------------------------------- /demo/assets/transitions/mosaic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/mosaic.png -------------------------------------------------------------------------------- /demo/assets/transitions/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/noise.png -------------------------------------------------------------------------------- /demo/assets/transitions/plasma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/plasma.png -------------------------------------------------------------------------------- /demo/assets/transitions/radial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/radial.png -------------------------------------------------------------------------------- /demo/assets/transitions/radialin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/radialin.png -------------------------------------------------------------------------------- /demo/assets/transitions/radialout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/radialout.png -------------------------------------------------------------------------------- /demo/assets/transitions/slide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/slide.png -------------------------------------------------------------------------------- /demo/assets/transitions/smoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/smoke.png -------------------------------------------------------------------------------- /demo/assets/transitions/split.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/split.png -------------------------------------------------------------------------------- /demo/assets/transitions/starburst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/starburst.png -------------------------------------------------------------------------------- /demo/assets/transitions/stripes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/stripes.png -------------------------------------------------------------------------------- /demo/assets/transitions/swipedown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/swipedown.png -------------------------------------------------------------------------------- /demo/assets/transitions/swipeleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/swipeleft.png -------------------------------------------------------------------------------- /demo/assets/transitions/swiperight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/swiperight.png -------------------------------------------------------------------------------- /demo/assets/transitions/swipeup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/swipeup.png -------------------------------------------------------------------------------- /demo/assets/transitions/swirl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/swirl.png -------------------------------------------------------------------------------- /demo/assets/transitions/triangles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/triangles.png -------------------------------------------------------------------------------- /demo/assets/transitions/vortex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/vortex.png -------------------------------------------------------------------------------- /demo/assets/transitions/waves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/waves.png -------------------------------------------------------------------------------- /demo/assets/transitions/weave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/weave.png -------------------------------------------------------------------------------- /demo/assets/transitions/zebra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/transitions/zebra.png -------------------------------------------------------------------------------- /demo/assets/uu-advent-of-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/uu-advent-of-code.png -------------------------------------------------------------------------------- /demo/assets/uu-application-events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/uu-application-events.png -------------------------------------------------------------------------------- /demo/assets/uu-asterisk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/uu-asterisk.png -------------------------------------------------------------------------------- /demo/assets/uu-continuous-deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/uu-continuous-deployment.png -------------------------------------------------------------------------------- /demo/assets/uu-event-sourcing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/uu-event-sourcing.png -------------------------------------------------------------------------------- /demo/assets/uu-homebrewing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/uu-homebrewing.png -------------------------------------------------------------------------------- /demo/assets/uu-macroquad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/uu-macroquad.png -------------------------------------------------------------------------------- /demo/assets/uu-raku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/uu-raku.png -------------------------------------------------------------------------------- /demo/assets/uu-rusty-aquarium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/uu-rusty-aquarium.png -------------------------------------------------------------------------------- /demo/assets/uu-rusty-slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/uu-rusty-slider.png -------------------------------------------------------------------------------- /demo/assets/uu-vim-emacs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/uu-vim-emacs.png -------------------------------------------------------------------------------- /demo/assets/uu.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_image": "assets/uu.png", 3 | "background_color": "#ffffff", 4 | "heading_color": "#000000", 5 | "text_color": "#000000", 6 | "align": "left", 7 | "font": "assets/Amble-Regular.ttf", 8 | "font_bold": "assets/Amble-Bold.ttf", 9 | "font_italic": "assets/Amble-Italic.ttf", 10 | "font_size_header_title": 80, 11 | "font_size_header_slides": 60, 12 | "font_size_text": 40, 13 | "vertical_offset": 60.0, 14 | "horizontal_offset": 250.0, 15 | "line_height": 2.0, 16 | "blockquote_background_color": "#2e2459", 17 | "blockquote_padding": 20.0, 18 | "blockquote_left_quote": "“", 19 | "blockquote_right_quote": "„", 20 | "font_code": "assets/Hack-Regular.ttf", 21 | "font_code_size": 20, 22 | "code_line_height": 1.2, 23 | "code_background_color": "#2a3439", 24 | "code_theme": "Solarized (dark)", 25 | "code_tab_width": 2, 26 | "bullet": "• ", 27 | "shader": false, 28 | "transition": "checkerboard" 29 | } 30 | -------------------------------------------------------------------------------- /demo/assets/uu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/uu.png -------------------------------------------------------------------------------- /demo/assets/vim-emacs.md: -------------------------------------------------------------------------------- 1 | # vim + emacs 2 | 3 | --- 4 | 5 | ## vim ~/.emacs 6 | 7 | Changes won't be evaluated 8 | 9 | --- 10 | 11 | ## vim-emacs plugin 12 | 13 | * Select a range 14 | * Type command `:Emacs` 15 | * Will evaluate range in Emacs 16 | 17 | --- 18 | 19 | ## How? 20 | 21 | * Emacs with server 22 | * Lines sent with `emacsclient --eval` 23 | * Elisp expressions evaluated 24 | * Output is returned 25 | 26 | --- 27 | 28 | ## Why? 29 | 30 | Let's end the war! 31 | 32 | --- 33 | 34 | ## Where? 35 | 36 | The vim.emacs plugin is available on GitHub: 37 | 38 | ``` 39 | https://github.com/ollej/vim.emacs 40 | ``` 41 | 42 | --- 43 | 44 | ## Questions? 45 | 46 | No? 47 | 48 | --- 49 | 50 | ## Hate mail 51 | 52 | olle@wreede.se 53 | 54 | @ollej 55 | -------------------------------------------------------------------------------- /demo/assets/vim-emacs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/assets/vim-emacs.png -------------------------------------------------------------------------------- /demo/example-slideshows.html: -------------------------------------------------------------------------------- 1 | rusty slider

rusty slider

A list of example slideshows made with Rusty Slider.

View rusty-slider on GitHub

Themes

These are the themes included with Rusty Slider. You can also create your own.

  • Agical
  • Default Theme
  • Exalted Beer
  • Rust
  • Rusty
  • Uu

Transitions

There are a number of transitions available, this is how they look.

Animation of transitions

Slideshows

Click on a presentation to run it with the selected theme.

  • Advent Of Code
  • Application Events
  • Asterisk
  • Continuous Deployment
  • Event Sourcing
  • Homebrewing
  • Macroquad
  • Raku
  • Rusty Aquarium
  • Rusty Slider
  • Vim Emacs

ollej maintains Rusty Slider

-------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rusty Slider 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /demo/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "display": "fullscreen", 3 | "orientation": "landscape", 4 | "start_url": "/" 5 | } 6 | -------------------------------------------------------------------------------- /demo/quad-url.js: -------------------------------------------------------------------------------- 1 | var ctx = null; 2 | var memory; 3 | 4 | params_set_mem = function (wasm_memory, _wasm_exports) { 5 | memory = wasm_memory; 6 | ctx = {}; 7 | } 8 | 9 | function set_url(params, hash) { 10 | let result = window.location.origin + window.location.pathname; 11 | if (params != "") { 12 | if (params !== undefined && params !== null) { 13 | result += '?' + params; 14 | } else { 15 | result += window.location.search; 16 | } 17 | } 18 | if (hash != "") { 19 | if (hash !== undefined && hash !== null) { 20 | result += '#' + hash; 21 | } else { 22 | result += window.location.hash; 23 | } 24 | } 25 | window.history.pushState({path:result},'',result); // https://stackoverflow.com/questions/10970078/modifying-a-query-string-without-reloading-the-page 26 | } 27 | 28 | params_register_js_plugin = function (importObject) { 29 | importObject.env.quad_url_path = function (full) { 30 | if (full == 1) { 31 | return js_object(window.location.href); 32 | } else { 33 | return js_object(window.location.origin + window.location.pathname); 34 | } 35 | } 36 | importObject.env.quad_url_param_count = function () { 37 | ctx.entries = []; 38 | var some = new URLSearchParams(window.location.search); 39 | for (let i of some.entries()) { 40 | ctx.entries.push(i); 41 | } 42 | return ctx.entries.length; 43 | } 44 | importObject.env.quad_url_get_key = function (i) { 45 | return js_object(ctx.entries[i][0]) 46 | } 47 | importObject.env.quad_url_get_value = function (i) { 48 | return js_object(ctx.entries[i][1]) 49 | } 50 | importObject.env.quad_url_link_open = function (url_rs, new_tab) { 51 | let url = get_js_object(url_rs); 52 | if (new_tab == 0) { 53 | window.open(url, "_self"); // https://stackoverflow.com/questions/8454510/open-url-in-same-window-and-in-same-tab 54 | } else { 55 | window.open(url); 56 | } 57 | } 58 | importObject.env.quad_url_set_program_parameter = function (name_rs, value_rs) { 59 | let name = get_js_object(name_rs); 60 | let value = get_js_object(value_rs); 61 | let params = new URLSearchParams(window.location.search); 62 | params.set(name, value); 63 | // todo убрать вопрос если пусто 64 | set_url(params.toString(), null); 65 | } 66 | importObject.env.quad_url_delete_program_parameter = function (name_rs) { 67 | let name = get_js_object(name_rs); 68 | let params = new URLSearchParams(window.location.search); 69 | params.delete(name); 70 | set_url(params.toString(), null); 71 | } 72 | importObject.env.quad_url_get_hash = function () { 73 | return js_object(window.location.hash); 74 | } 75 | importObject.env.quad_url_set_hash = function (hash) { 76 | set_url(null, get_js_object(hash)); 77 | } 78 | } 79 | 80 | miniquad_add_plugin({ 81 | register_plugin: params_register_js_plugin, 82 | on_init: params_set_mem, 83 | name: "quad_url", 84 | version: "0.1.0" 85 | }); 86 | -------------------------------------------------------------------------------- /demo/rusty_slider.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollej/rusty-slider/6dfb5d2473317aaf96aeb1bea35c7b3d7dde1bcf/demo/rusty_slider.wasm -------------------------------------------------------------------------------- /src/app_options.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::{Parser, command}; 4 | 5 | use crate::prelude::Duration; 6 | 7 | #[derive(Parser, Debug, Clone)] 8 | #[command( 9 | name = "rusty-slider", 10 | about = "A small tool to display markdown files as a slideshow." 11 | )] 12 | pub struct AppOptions { 13 | /// Path to directory to load slideshow files from 14 | #[arg(short, long, default_value = "assets")] 15 | pub directory: PathBuf, 16 | /// Markdown files with slides text. 17 | #[arg(short, long, default_value = "rusty-slider.md")] 18 | pub slides: PathBuf, 19 | /// File with theme options. 20 | #[arg(short, long, default_value = "default-theme.json")] 21 | pub theme: PathBuf, 22 | /// Automatically switch slides every N seconds. 23 | #[arg(short, long, default_value = "0")] 24 | pub automatic: Duration, 25 | /// Switch transitions for every slide 26 | #[arg(long)] 27 | pub demo_transitions: bool, 28 | /// When taking screenshot, store PNG at this path 29 | #[arg(short = 'S', long, default_value = "screenshot.png")] 30 | pub screenshot: PathBuf, 31 | /// Enable executing code in code blocks 32 | #[arg(long)] 33 | pub enable_code_execution: bool, 34 | /// Path to directory where application files are loaded from 35 | #[arg(short = 'A', long, default_value = "assets")] 36 | pub assets: PathBuf, 37 | /// Slide number to start at 38 | #[arg(short = 'n', long, default_value = "1", value_parser = clap::value_parser!(u32).range(1..))] 39 | pub number: u32, 40 | } 41 | 42 | impl AppOptions { 43 | pub fn slides_path(&self) -> PathBuf { 44 | let mut path = self.directory.clone(); 45 | path.push(self.slides.clone()); 46 | path 47 | } 48 | 49 | pub fn theme_path(&self) -> PathBuf { 50 | let mut path = self.directory.clone(); 51 | path.push(self.theme.clone()); 52 | path 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/clipboard.rs: -------------------------------------------------------------------------------- 1 | pub fn set_clipboard(data: &str) { 2 | macroquad::miniquad::window::clipboard_set(data); 3 | } 4 | -------------------------------------------------------------------------------- /src/code_box_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use macroquad::prelude::{Color, Font}; 3 | use syntect::easy::HighlightLines; 4 | use syntect::highlighting::FontStyle; 5 | use syntect::highlighting::ThemeSet; 6 | use syntect::parsing::SyntaxSet; 7 | use syntect::util::LinesWithEndings; 8 | 9 | pub struct CodeBoxBuilder { 10 | ps: SyntaxSet, 11 | ts: ThemeSet, 12 | font_text: Font, 13 | font_bold: Font, 14 | font_italic: Font, 15 | font_size: FontSize, 16 | line_height: Height, 17 | background_color: Color, 18 | tab_spaces: String, 19 | highlighting_theme: String, 20 | margin: Height, 21 | } 22 | 23 | impl CodeBoxBuilder { 24 | pub fn new(theme: Theme, font_text: Font, font_bold: Font, font_italic: Font) -> Self { 25 | Self { 26 | ts: ThemeSet::load_defaults(), 27 | ps: SyntaxSet::load_defaults_newlines(), 28 | font_text, 29 | font_bold, 30 | font_italic, 31 | font_size: theme.font_code_size.to_owned(), 32 | line_height: theme.code_line_height.to_owned(), 33 | background_color: theme.code_background_color.to_owned(), 34 | tab_spaces: " ".repeat(theme.code_tab_width), 35 | highlighting_theme: theme.code_theme.to_owned(), 36 | margin: 10.0, 37 | } 38 | } 39 | 40 | pub fn build_draw_box(&self, language: Option, code: String) -> CodeBox { 41 | CodeBox::new( 42 | TextBox::new( 43 | self.build_text_lines(language, code), 44 | 0., 45 | Some(self.background_color), 46 | TextBoxStyle::Code, 47 | ), 48 | self.margin, 49 | Some(self.background_color), 50 | ) 51 | } 52 | 53 | fn build_text_lines(&self, language: Option, code: String) -> Vec { 54 | let syntax = match language { 55 | Some(lang) => self.ps.find_syntax_by_token(&lang), 56 | None => self.ps.find_syntax_by_first_line(&code), 57 | } 58 | .unwrap_or_else(|| self.ps.find_syntax_plain_text()); 59 | let theme = &self.ts.themes[&self.highlighting_theme]; 60 | let mut h = HighlightLines::new(syntax, theme); 61 | let lines = LinesWithEndings::from(&code) 62 | .map(|line| h.highlight_line(line, &self.ps)) 63 | .filter_map(Result::ok) 64 | .collect::>(); 65 | 66 | let mut text_lines = vec![]; 67 | let mut partials = vec![]; 68 | for tokens in lines.iter() { 69 | for (style, text) in tokens { 70 | let text = text.trim_end_matches('\n').replace('\t', &self.tab_spaces); 71 | if text.is_empty() { 72 | continue; 73 | } 74 | 75 | let c = style.foreground; 76 | let font_style = match style.font_style { 77 | FontStyle::BOLD => self.font_bold.clone(), 78 | FontStyle::ITALIC => self.font_italic.clone(), 79 | _ => self.font_text.clone(), 80 | }; 81 | 82 | partials.push(TextPartial::new( 83 | &text, 84 | font_style, 85 | self.font_size, 86 | Color::from_rgba(c.r, c.g, c.b, c.a), 87 | self.line_height, 88 | )); 89 | } 90 | text_lines.push(TextLine::new(DrawAlignment::left, partials)); 91 | partials = Vec::new(); 92 | } 93 | 94 | text_lines 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/codebox.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use macroquad::prelude::*; 3 | 4 | #[derive(Clone)] 5 | pub struct CodeBox { 6 | width: Width, 7 | height: Height, 8 | margin: Height, 9 | background_color: Option, 10 | textbox: TextBox, 11 | } 12 | 13 | impl CodeBox { 14 | const TITLE_BAR_HEIGHT: f32 = 30.; 15 | const CHROME_CORNER_RADIUS: f32 = 8.; 16 | const CHROME_CIRCLE_DISTANCE: f32 = 10.; 17 | const CHROME_CIRCLE_RADIUS: f32 = 8.; 18 | const DEFAULT_TITLE_BAR_COLOR: Color = Color::new(0.965, 0.961, 0.961, 1.0); 19 | const CHROME_COLOR_RED: Color = Color::new(0.996, 0.373, 0.345, 1.); 20 | const CHROME_OUTLINE_RED: Color = Color::new(0.863, 0.227, 0.216, 1.); 21 | const CHROME_COLOR_YELLOW: Color = Color::new(0.996, 0.737, 0.173, 1.); 22 | const CHROME_OUTLINE_YELLOW: Color = Color::new(0.863, 0.592, 0.110, 1.); 23 | const CHROME_COLOR_GREEN: Color = Color::new(0.157, 0.784, 0.251, 1.); 24 | const CHROME_OUTLINE_GREEN: Color = Color::new(0.106, 0.639, 0.153, 1.); 25 | 26 | pub fn new(textbox: TextBox, margin: Height, background_color: Option) -> Self { 27 | Self { 28 | width: textbox.width_with_padding(), 29 | height: textbox.height_with_margin(), 30 | margin, 31 | background_color, 32 | textbox, 33 | } 34 | } 35 | 36 | pub fn draw(&self, hpos: Hpos, vpos: Vpos) -> Vpos { 37 | let vpos = vpos + self.margin; 38 | self.draw_rounded_rectangle( 39 | hpos, 40 | vpos, 41 | self.width + Self::CHROME_CORNER_RADIUS, 42 | self.height + Self::TITLE_BAR_HEIGHT + Self::CHROME_CORNER_RADIUS, 43 | Self::CHROME_CORNER_RADIUS, 44 | self.background_color 45 | .unwrap_or(Self::DEFAULT_TITLE_BAR_COLOR), 46 | ); 47 | self.draw_outlined_circle( 48 | hpos + Self::CHROME_CIRCLE_DISTANCE + Self::CHROME_CIRCLE_RADIUS, 49 | vpos, 50 | Self::CHROME_COLOR_RED, 51 | Self::CHROME_OUTLINE_RED, 52 | ); 53 | self.draw_outlined_circle( 54 | hpos + Self::CHROME_CIRCLE_DISTANCE * 2. + Self::CHROME_CIRCLE_RADIUS * 3., 55 | vpos, 56 | Self::CHROME_COLOR_YELLOW, 57 | Self::CHROME_OUTLINE_YELLOW, 58 | ); 59 | self.draw_outlined_circle( 60 | hpos + Self::CHROME_CIRCLE_DISTANCE * 3. + Self::CHROME_CIRCLE_RADIUS * 5., 61 | vpos, 62 | Self::CHROME_COLOR_GREEN, 63 | Self::CHROME_OUTLINE_GREEN, 64 | ); 65 | let new_vpos = self.textbox.draw(hpos, vpos + Self::TITLE_BAR_HEIGHT); 66 | new_vpos + self.margin 67 | } 68 | 69 | fn draw_rounded_rectangle( 70 | &self, 71 | hpos: Hpos, 72 | vpos: Vpos, 73 | width: Width, 74 | height: Height, 75 | corner_radius: f32, 76 | color: Color, 77 | ) { 78 | draw_circle( 79 | hpos + corner_radius, 80 | vpos + corner_radius, 81 | corner_radius, 82 | color, 83 | ); 84 | draw_circle( 85 | hpos + width - corner_radius, 86 | vpos + corner_radius, 87 | corner_radius, 88 | color, 89 | ); 90 | draw_rectangle( 91 | hpos, 92 | vpos + corner_radius, 93 | width, 94 | height - corner_radius * 2., 95 | color, 96 | ); 97 | draw_rectangle( 98 | hpos + corner_radius, 99 | vpos, 100 | width - corner_radius * 2., 101 | height, 102 | color, 103 | ); 104 | draw_circle( 105 | hpos + corner_radius, 106 | vpos + height - corner_radius, 107 | corner_radius, 108 | color, 109 | ); 110 | draw_circle( 111 | hpos + width - corner_radius, 112 | vpos + height - corner_radius, 113 | corner_radius, 114 | color, 115 | ); 116 | } 117 | 118 | fn draw_outlined_circle(&self, hpos: Hpos, vpos: Vpos, color: Color, outline_color: Color) { 119 | draw_circle( 120 | hpos, 121 | vpos + Self::TITLE_BAR_HEIGHT / 2., 122 | Self::CHROME_CIRCLE_RADIUS, 123 | color, 124 | ); 125 | draw_circle_lines( 126 | hpos, 127 | vpos + Self::TITLE_BAR_HEIGHT / 2., 128 | Self::CHROME_CIRCLE_RADIUS, 129 | 1., 130 | outline_color, 131 | ); 132 | } 133 | 134 | pub fn width(&self) -> Width { 135 | self.width 136 | } 137 | 138 | pub fn width_with_padding(&self) -> Width { 139 | self.width + Self::CHROME_CORNER_RADIUS * 2. 140 | } 141 | 142 | pub fn height(&self) -> Height { 143 | self.height 144 | } 145 | 146 | pub fn height_with_padding(&self) -> Width { 147 | self.height + Self::CHROME_CORNER_RADIUS 148 | } 149 | 150 | pub fn height_with_margin(&self) -> Height { 151 | self.height() + self.margin 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/drawbox.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use macroquad::prelude::*; 3 | 4 | #[derive(Clone)] 5 | pub enum DrawBox { 6 | Image(ImageBox), 7 | Text(TextBox), 8 | Code(CodeBox), 9 | } 10 | 11 | impl DrawBox { 12 | pub async fn load_image(&mut self) { 13 | match self { 14 | DrawBox::Image(draw_box) => { 15 | if let Some(path) = draw_box.path() { 16 | match load_texture(&path).await { 17 | Ok(texture) => { 18 | draw_box.set_image(texture); 19 | debug!("Image loaded: {}", path); 20 | } 21 | _ => { 22 | error!("Couldn't load image file: {}", path); 23 | } 24 | } 25 | } 26 | } 27 | DrawBox::Text(_) => (), 28 | DrawBox::Code(_) => (), 29 | } 30 | } 31 | 32 | pub fn draw(&self, hpos: Hpos, vpos: Vpos) -> Vpos { 33 | match self { 34 | DrawBox::Image(image_box) => image_box.draw(hpos, vpos), 35 | DrawBox::Text(text_box) => text_box.draw(hpos, vpos), 36 | DrawBox::Code(code_box) => code_box.draw(hpos, vpos), 37 | } 38 | } 39 | 40 | pub fn width_with_padding(&self) -> Width { 41 | match self { 42 | DrawBox::Image(image_box) => image_box.width_with_padding(), 43 | DrawBox::Text(text_box) => text_box.width_with_padding(), 44 | DrawBox::Code(code_box) => code_box.width_with_padding(), 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/executable_code.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::OsStr, 3 | fmt, 4 | io::prelude::*, 5 | process::{Command, Stdio}, 6 | }; 7 | 8 | use tempfile::NamedTempFile; 9 | 10 | #[derive(Debug)] 11 | pub enum ExecutionError { 12 | Execute(std::io::Error), 13 | InputOutput, 14 | CreateTempFile(String), 15 | Compile(String), 16 | UnknkownLanguage(String), 17 | } 18 | 19 | impl fmt::Display for ExecutionError { 20 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 21 | match self { 22 | ExecutionError::Execute(error) => write!(f, "Execution error: {:?}", error.to_string()), 23 | ExecutionError::InputOutput => write!(f, "Coulnt't read Std I/O"), 24 | ExecutionError::CreateTempFile(message) => { 25 | write!(f, "Creating build file: {}", message) 26 | } 27 | ExecutionError::Compile(message) => write!(f, "Compile error: {}", message), 28 | ExecutionError::UnknkownLanguage(language) => { 29 | write!(f, "Don't know how to compile {}", language) 30 | } 31 | } 32 | } 33 | } 34 | 35 | impl std::error::Error for ExecutionError {} 36 | 37 | impl From for ExecutionError { 38 | fn from(e: std::io::Error) -> Self { 39 | ExecutionError::Execute(e) 40 | } 41 | } 42 | 43 | #[derive(Clone)] 44 | pub enum ExecutableCode { 45 | Bash(String), 46 | Python(String), 47 | Ruby(String), 48 | Perl(String), 49 | Rust(String), 50 | Unknown(String, String), 51 | } 52 | 53 | impl fmt::Display for ExecutableCode { 54 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 55 | match &*self { 56 | ExecutableCode::Bash(_) => write!(f, "bash"), 57 | ExecutableCode::Python(_) => write!(f, "python"), 58 | ExecutableCode::Ruby(_) => write!(f, "ruby"), 59 | ExecutableCode::Perl(_) => write!(f, "perl"), 60 | ExecutableCode::Rust(_) => write!(f, "rust"), 61 | ExecutableCode::Unknown(language, _) => write!(f, "unknown: {}", language), 62 | } 63 | } 64 | } 65 | 66 | impl ExecutableCode { 67 | pub fn from(language: &str, code: &String) -> Option { 68 | match language { 69 | "bash" | "sh" => Some(ExecutableCode::Bash(code.to_string())), 70 | "python" => Some(ExecutableCode::Python(code.to_string())), 71 | "ruby" => Some(ExecutableCode::Ruby(code.to_string())), 72 | "perl" => Some(ExecutableCode::Perl(code.to_string())), 73 | "rust" => Some(ExecutableCode::Rust(code.to_string())), 74 | language => Some(ExecutableCode::Unknown( 75 | language.to_string(), 76 | code.to_string(), 77 | )), 78 | } 79 | } 80 | 81 | pub fn code(&self) -> String { 82 | match self { 83 | ExecutableCode::Bash(code) => code.clone(), 84 | ExecutableCode::Python(code) => code.clone(), 85 | ExecutableCode::Ruby(code) => code.clone(), 86 | ExecutableCode::Perl(code) => code.clone(), 87 | ExecutableCode::Rust(code) => code.clone(), 88 | ExecutableCode::Unknown(_language, code) => code.clone(), 89 | } 90 | } 91 | 92 | pub fn execute(&self) -> Result { 93 | match self { 94 | ExecutableCode::Bash(code) => self.execute_command("bash", ["-"], code), 95 | ExecutableCode::Python(code) => self.execute_command("python3", ["-"], code), 96 | ExecutableCode::Ruby(code) => self.execute_command("ruby", ["-"], code), 97 | ExecutableCode::Perl(code) => self.execute_command("perl", ["-"], code), 98 | ExecutableCode::Rust(code) => self.compile_rust(code), 99 | ExecutableCode::Unknown(language, _code) => { 100 | Err(ExecutionError::UnknkownLanguage(language.to_string())) 101 | } 102 | } 103 | } 104 | 105 | fn compile_rust(&self, code: &String) -> Result { 106 | let temp_file = NamedTempFile::new()?; 107 | let file_path = temp_file.path().to_string_lossy(); 108 | self.execute_command("rustc", ["-v", "-o", &file_path, "-"], code)?; 109 | self.run_command_capture_output(&file_path) 110 | } 111 | 112 | fn run_command_capture_output(&self, command: &str) -> Result { 113 | let process = Command::new(command).stdout(Stdio::piped()).spawn()?; 114 | let mut output = String::new(); 115 | process.stdout.unwrap().read_to_string(&mut output)?; 116 | Ok(output) 117 | } 118 | 119 | fn execute_command( 120 | &self, 121 | command: &str, 122 | arguments: I, 123 | code: &String, 124 | ) -> Result 125 | where 126 | I: IntoIterator, 127 | S: AsRef, 128 | { 129 | let mut process = Command::new(command) 130 | .args(arguments) 131 | .stdin(Stdio::piped()) 132 | .stdout(Stdio::piped()) 133 | .stderr(Stdio::piped()) 134 | .spawn()?; 135 | process 136 | .stdin 137 | .take() 138 | .ok_or(ExecutionError::InputOutput)? 139 | .write_all(code.as_bytes())?; 140 | let mut output = String::new(); 141 | if process.wait()?.success() { 142 | process 143 | .stdout 144 | .take() 145 | .ok_or(ExecutionError::InputOutput)? 146 | .read_to_string(&mut output)?; 147 | Ok(output) 148 | } else { 149 | process 150 | .stderr 151 | .take() 152 | .ok_or(ExecutionError::InputOutput)? 153 | .read_to_string(&mut output)?; 154 | Err(ExecutionError::Compile(output)) 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/helptext.txt: -------------------------------------------------------------------------------- 1 | Shortcuts 2 | 3 | ESC / Q - Quit program 4 | Left arrow - Go to previous slide 5 | Right arrow - Go to next slide 6 | Space - Toggle shader 7 | Enter - Execute code in first code block 8 | S - Save screenshot 9 | C - Copy first code block to clipboard 10 | ? - Show this help screen 11 | 12 | -------------------------------------------------------------------------------- /src/hex_color.rs: -------------------------------------------------------------------------------- 1 | use colorsys::Rgb; 2 | use macroquad::prelude::{Color, WHITE}; 3 | use nanoserde::DeJson; 4 | 5 | #[derive(DeJson)] 6 | #[nserde(transparent)] 7 | pub struct HexColor(String); 8 | 9 | impl HexColor { 10 | pub fn as_str(&self) -> &str { 11 | &self.0 12 | } 13 | } 14 | 15 | impl From<&HexColor> for Color { 16 | fn from(color: &HexColor) -> Color { 17 | match Rgb::from_hex_str(color.as_str()) { 18 | Ok(rgb) => Color::new( 19 | rgb.red() as f32 / 255., 20 | rgb.green() as f32 / 255., 21 | rgb.blue() as f32 / 255., 22 | 1., 23 | ), 24 | Err(_) => WHITE, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/imagebox.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use macroquad::prelude::*; 3 | 4 | #[derive(Clone)] 5 | pub struct ImageBox { 6 | margin: Height, 7 | padding: f32, 8 | background_color: Option, 9 | path: String, 10 | image: Option, 11 | } 12 | 13 | impl ImageBox { 14 | const BOX_PADDING: f32 = 20.; 15 | 16 | pub fn new(path: &str, margin: Height, background_color: Option) -> Self { 17 | Self { 18 | margin, 19 | padding: Self::BOX_PADDING, 20 | background_color, 21 | path: path.to_string(), 22 | image: None, 23 | } 24 | } 25 | 26 | pub fn draw(&self, hpos: Hpos, vpos: Vpos) -> Vpos { 27 | //debug!( 28 | // "Image draw hpos:{} vpos:{} width:{} height: {}", 29 | // hpos, 30 | // vpos, 31 | // self.width(), 32 | // self.height() 33 | //); 34 | if let Some(image) = self.image.clone() { 35 | draw_texture_ex( 36 | &image, 37 | hpos, 38 | vpos + self.padding + self.margin, 39 | WHITE, 40 | DrawTextureParams { 41 | dest_size: Some(vec2(self.width(), self.height())), 42 | ..Default::default() 43 | }, 44 | ); 45 | } 46 | vpos + self.height_with_margin() 47 | } 48 | 49 | pub fn draw_background(&self, hpos: Hpos, vpos: Vpos) { 50 | if let Some(color) = self.background_color() { 51 | draw_rectangle( 52 | hpos, 53 | vpos, 54 | self.width_with_padding(), 55 | self.height_with_padding(), 56 | color, 57 | ); 58 | } 59 | } 60 | 61 | pub fn path(&self) -> Option { 62 | Some(self.path.clone()) 63 | } 64 | 65 | pub fn set_image(&mut self, image: Texture2D) { 66 | self.image = Some(image); 67 | } 68 | 69 | fn background_color(&self) -> Option { 70 | self.background_color 71 | } 72 | 73 | pub fn width(&self) -> Width { 74 | match self.image.clone() { 75 | Some(image) => image.width(), 76 | None => 0., 77 | } 78 | } 79 | 80 | pub fn width_with_padding(&self) -> Width { 81 | self.width() + self.padding * 2. 82 | } 83 | 84 | pub fn height(&self) -> Height { 85 | match self.image.clone() { 86 | Some(image) => image.height(), 87 | None => 0., 88 | } 89 | } 90 | 91 | pub fn height_with_padding(&self) -> Height { 92 | self.height() + self.padding * 2. 93 | } 94 | 95 | fn height_with_margin(&self) -> Height { 96 | self.height_with_padding() + self.margin 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod app_options; 2 | pub mod clipboard; 3 | pub mod code_box_builder; 4 | pub mod codebox; 5 | pub mod drawbox; 6 | pub mod executable_code; 7 | pub mod hex_color; 8 | pub mod imagebox; 9 | pub mod markdowntoslides; 10 | pub mod prelude; 11 | pub mod shaders; 12 | pub mod show_help; 13 | pub mod slider; 14 | pub mod textbox; 15 | pub mod theme; 16 | pub mod transition; 17 | pub mod transitioner; 18 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![windows_subsystem = "windows"] 2 | 3 | extern crate markdown; 4 | 5 | use {clap::Parser, macroquad::prelude::*, quad_url::get_program_parameters}; 6 | 7 | use rusty_slider::prelude::*; 8 | 9 | fn window_conf() -> Conf { 10 | Conf { 11 | window_title: "Rusty Slider".to_owned(), 12 | fullscreen: true, 13 | ..Default::default() 14 | } 15 | } 16 | 17 | #[macroquad::main(window_conf())] 18 | async fn main() { 19 | let options = AppOptions::parse_from(get_program_parameters().iter()); 20 | 21 | let theme = Theme::load(options.theme_path()).await; 22 | debug!( 23 | "background_color: {:?} text_color: {:?} heading_color{:?}", 24 | theme.background_color, theme.text_color, theme.heading_color, 25 | ); 26 | let mut shader_activated = theme.shader; 27 | let mut slides = Slides::load(options.clone(), theme).await; 28 | let mut show_help = ShowHelp::new(); 29 | let shader_material = load_material( 30 | ShaderSource::Glsl { 31 | vertex: crt::VERTEX, 32 | fragment: crt::FRAGMENT, 33 | }, 34 | Default::default(), 35 | ) 36 | .unwrap(); 37 | 38 | loop { 39 | #[cfg(not(target_arch = "wasm32"))] 40 | if is_key_pressed(KeyCode::Q) | is_key_pressed(KeyCode::Escape) { 41 | break; 42 | } 43 | if is_key_pressed(KeyCode::Left) 44 | || is_key_pressed(KeyCode::H) 45 | || is_mouse_button_pressed(MouseButton::Right) 46 | { 47 | slides.prev(); 48 | } 49 | if is_key_pressed(KeyCode::Right) 50 | || is_key_pressed(KeyCode::L) 51 | || is_mouse_button_pressed(MouseButton::Left) 52 | { 53 | slides.next(); 54 | } 55 | if is_key_pressed(KeyCode::Up) 56 | || is_key_pressed(KeyCode::K) 57 | || is_key_pressed(KeyCode::Home) 58 | { 59 | slides.first(); 60 | } 61 | if is_key_pressed(KeyCode::Down) 62 | || is_key_pressed(KeyCode::J) 63 | || is_key_pressed(KeyCode::End) 64 | { 65 | slides.last(); 66 | } 67 | if is_key_pressed(KeyCode::Space) { 68 | shader_activated = !shader_activated; 69 | } 70 | if is_key_pressed(KeyCode::C) { 71 | slides.copy_codeblock(); 72 | } 73 | match get_char_pressed() { 74 | Some('?') => show_help.toggle_show(), 75 | _ => (), 76 | } 77 | #[cfg(not(target_arch = "wasm32"))] 78 | if options.enable_code_execution && is_key_pressed(KeyCode::Enter) { 79 | slides.run_code_block(); 80 | } 81 | 82 | slides.update(get_frame_time()); 83 | slides.draw(); 84 | let texture = slides.texture(); 85 | 86 | set_default_camera(); 87 | clear_background(BLACK); 88 | if shader_activated { 89 | gl_use_material(&shader_material); 90 | } 91 | draw_texture_ex( 92 | &texture, 93 | 0., 94 | 0., 95 | WHITE, 96 | DrawTextureParams { 97 | dest_size: Some(vec2(screen_width(), screen_height())), 98 | flip_y: true, 99 | ..Default::default() 100 | }, 101 | ); 102 | if shader_activated { 103 | gl_use_default_material(); 104 | } 105 | show_help.draw(); 106 | 107 | if is_key_pressed(KeyCode::S) { 108 | get_screen_data().export_png(&options.screenshot.to_string_lossy()); 109 | } 110 | 111 | next_frame().await 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/markdowntoslides.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use macroquad::prelude::*; 3 | use markdown::{Block, ListItem, Span}; 4 | use std::mem::discriminant; 5 | 6 | pub struct MarkdownToSlides { 7 | theme: Theme, 8 | font_text: Font, 9 | font_bold: Font, 10 | font_italic: Font, 11 | font_code: Font, 12 | code_box_builder: CodeBoxBuilder, 13 | } 14 | 15 | impl MarkdownToSlides { 16 | pub fn new( 17 | theme: Theme, 18 | font_text: Font, 19 | font_bold: Font, 20 | font_italic: Font, 21 | font_code: Font, 22 | ) -> Self { 23 | let code_box_builder = CodeBoxBuilder::new( 24 | theme.clone(), 25 | font_code.clone(), 26 | font_bold.clone(), 27 | font_italic.clone(), 28 | ); 29 | Self { 30 | theme, 31 | code_box_builder, 32 | font_text, 33 | font_bold, 34 | font_italic, 35 | font_code, 36 | } 37 | } 38 | 39 | pub fn parse(&self, markdown: String) -> Vec { 40 | let tokens = markdown::tokenize(&markdown); 41 | let slide_blocks = self.split_tokens_into_slides(tokens); 42 | self.build_slides(slide_blocks) 43 | } 44 | 45 | fn split_tokens_into_slides(&self, tokens: Vec) -> Vec> { 46 | let mut slides: Vec> = vec![]; 47 | let mut blocks: Vec = vec![]; 48 | for block in tokens.iter() { 49 | debug!("{:?}", block); 50 | match block { 51 | Block::Hr => { 52 | slides.push(blocks); 53 | blocks = vec![]; 54 | } 55 | _ => blocks.push(block.to_owned()), 56 | } 57 | } 58 | if !blocks.is_empty() { 59 | slides.push(blocks); 60 | } 61 | slides 62 | } 63 | 64 | fn build_slides(&self, slide_blocks: Vec>) -> Vec { 65 | let mut slides = vec![]; 66 | for blocks in slide_blocks.iter() { 67 | slides.push(self.build_slide(blocks)); 68 | } 69 | slides 70 | } 71 | 72 | fn build_slide(&self, blocks: &[Block]) -> Slide { 73 | let (draw_boxes, background_path) = 74 | self.blocks_to_draw_boxes(blocks, None, TextBoxStyle::Standard); 75 | Slide::new( 76 | draw_boxes, 77 | self.find_first_code_block(blocks), 78 | self.theme.align, 79 | self.theme.horizontal_offset, 80 | background_path, 81 | ) 82 | } 83 | 84 | fn find_first_code_block(&self, blocks: &[Block]) -> Option { 85 | for block in blocks.iter() { 86 | if let Block::CodeBlock(Some(language), code) = block { 87 | if let Some(cb) = ExecutableCode::from(language, code) { 88 | return Some(cb); 89 | } 90 | } 91 | } 92 | None 93 | } 94 | 95 | fn blocks_to_draw_boxes( 96 | &self, 97 | blocks: &[Block], 98 | background_color: Option, 99 | style: TextBoxStyle, 100 | ) -> (Vec, Option) { 101 | let mut draw_boxes = vec![]; 102 | let mut text_lines = vec![]; 103 | let mut background_path: Option = None; 104 | for block in blocks.iter() { 105 | match block { 106 | Block::Header(spans, 1) => { 107 | if !text_lines.is_empty() { 108 | draw_boxes.push(DrawBox::Text(TextBox::new( 109 | text_lines, 110 | self.theme.vertical_offset, 111 | background_color, 112 | style.clone(), 113 | ))); 114 | text_lines = Vec::new(); 115 | } 116 | draw_boxes.push(DrawBox::Text(TextBox::new( 117 | vec![TextLine::new( 118 | self.theme.align, 119 | self.spans_to_text_partials( 120 | spans, 121 | self.font_text.clone(), 122 | self.theme.font_size_header_title, 123 | self.theme.heading_color, 124 | ), 125 | )], 126 | self.theme.vertical_offset, 127 | background_color, 128 | TextBoxStyle::Title, 129 | ))); 130 | } 131 | Block::Header(spans, _size) => { 132 | text_lines.push(TextLine::new( 133 | self.theme.align, 134 | self.spans_to_text_partials( 135 | spans, 136 | self.font_text.clone(), 137 | self.theme.font_size_header_slides, 138 | self.theme.heading_color, 139 | ), 140 | )); 141 | } 142 | Block::Paragraph(spans) if self.is_image(spans) => { 143 | if !text_lines.is_empty() { 144 | draw_boxes.push(DrawBox::Text(TextBox::new( 145 | text_lines, 146 | self.theme.vertical_offset, 147 | background_color, 148 | style.clone(), 149 | ))); 150 | text_lines = Vec::new(); 151 | } 152 | if let Some(Span::Image(title, path, _)) = spans.first() { 153 | if title.as_str() == "background" { 154 | background_path = Some(path.clone()); 155 | } else { 156 | draw_boxes.push(DrawBox::Image(ImageBox::new(path, 0., None))); 157 | } 158 | } 159 | } 160 | Block::Paragraph(spans) => { 161 | text_lines.push(TextLine::new( 162 | self.theme.align, 163 | self.spans_to_text_partials( 164 | spans, 165 | self.font_text.clone(), 166 | self.theme.font_size_text, 167 | self.theme.text_color, 168 | ), 169 | )); 170 | } 171 | Block::UnorderedList(items) => { 172 | text_lines.extend(self.build_list_box(items, Some(&self.theme.bullet))); 173 | } 174 | Block::OrderedList(items, _) => { 175 | text_lines.extend(self.build_list_box(items, None)); 176 | } 177 | Block::Blockquote(blocks) => { 178 | if !text_lines.is_empty() { 179 | draw_boxes.push(DrawBox::Text(TextBox::new( 180 | text_lines, 181 | self.theme.vertical_offset, 182 | background_color, 183 | style.clone(), 184 | ))); 185 | text_lines = Vec::new(); 186 | } 187 | let (inner_blocks, image_path) = self.blocks_to_draw_boxes( 188 | blocks, 189 | Some(self.theme.blockquote_background_color), 190 | TextBoxStyle::Blockquote { 191 | size: self.theme.font_size_header_title * 2, 192 | font: self.font_text.clone(), 193 | color: self.theme.text_color, 194 | }, 195 | ); 196 | if image_path.is_some() { 197 | background_path = image_path; 198 | } 199 | draw_boxes.extend(inner_blocks); 200 | } 201 | Block::CodeBlock(language, code) => { 202 | if !text_lines.is_empty() { 203 | draw_boxes.push(DrawBox::Text(TextBox::new( 204 | text_lines, 205 | self.theme.vertical_offset, 206 | background_color, 207 | style.clone(), 208 | ))); 209 | text_lines = Vec::new(); 210 | } 211 | draw_boxes.push(DrawBox::Code( 212 | self.code_box_builder 213 | .build_draw_box(language.to_owned(), code.to_owned()), 214 | )); 215 | } 216 | 217 | _ => (), 218 | } 219 | } 220 | if !text_lines.is_empty() { 221 | draw_boxes.push(DrawBox::Text(TextBox::new( 222 | text_lines, 223 | self.theme.vertical_offset, 224 | background_color, 225 | style, 226 | ))); 227 | } 228 | (draw_boxes, background_path) 229 | } 230 | 231 | fn is_image(&self, spans: &[Span]) -> bool { 232 | if let Some(span) = spans.first() { 233 | return discriminant(span) 234 | == discriminant(&Span::Image("".to_string(), "".to_string(), None)); 235 | } 236 | false 237 | } 238 | 239 | fn spans_to_text_partials( 240 | &self, 241 | spans: &[Span], 242 | font: Font, 243 | font_size: FontSize, 244 | color: Color, 245 | ) -> Vec { 246 | let mut partials = vec![]; 247 | // TODO: Text with only newline should start new line 248 | for span in spans.iter() { 249 | match span { 250 | Span::Text(text) => partials.push(TextPartial::new( 251 | text, 252 | font.clone(), 253 | font_size, 254 | color, 255 | self.theme.line_height, 256 | )), 257 | Span::Code(text) => partials.push(TextPartial::new( 258 | text, 259 | self.font_code.clone(), 260 | font_size, 261 | self.theme.text_color, // TODO: Add code text color to theme 262 | self.theme.line_height, 263 | )), 264 | Span::Emphasis(spans) => partials.extend(self.spans_to_text_partials( 265 | spans, 266 | self.font_italic.clone(), 267 | font_size, 268 | color, 269 | )), 270 | Span::Strong(spans) => partials.extend(self.spans_to_text_partials( 271 | spans, 272 | self.font_bold.clone(), 273 | font_size, 274 | color, 275 | )), 276 | _ => (), 277 | }; 278 | } 279 | partials 280 | } 281 | 282 | fn build_list_box(&self, items: &[ListItem], bullet: Option<&String>) -> Vec { 283 | let mut lines: Vec = vec![]; 284 | for (index, item) in items.iter().enumerate() { 285 | if let ListItem::Simple(spans) = item { 286 | let mut partials = vec![self.build_bullet_partial(index, bullet)]; 287 | partials.extend(self.spans_to_text_partials( 288 | spans, 289 | self.font_text.clone(), 290 | self.theme.font_size_text, 291 | self.theme.text_color, 292 | )); 293 | let text_line = TextLine::new(DrawAlignment::left, partials); 294 | lines.push(text_line); 295 | }; 296 | } 297 | lines 298 | } 299 | 300 | fn build_bullet_partial(&self, index: usize, bullet: Option<&String>) -> TextPartial { 301 | let item_bullet = match bullet { 302 | Some(b) => b.to_owned(), 303 | None => format!("{}. ", index + 1), 304 | }; 305 | TextPartial::new( 306 | &item_bullet, 307 | self.font_text.clone(), 308 | self.theme.font_size_text, 309 | self.theme.text_color, 310 | self.theme.line_height, 311 | ) 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::app_options::*; 2 | pub use crate::clipboard::*; 3 | pub use crate::code_box_builder::*; 4 | pub use crate::codebox::*; 5 | pub use crate::drawbox::*; 6 | pub use crate::executable_code::*; 7 | pub use crate::hex_color::*; 8 | pub use crate::imagebox::*; 9 | pub use crate::markdowntoslides::*; 10 | pub use crate::shaders::*; 11 | pub use crate::show_help::*; 12 | pub use crate::slider::*; 13 | pub use crate::textbox::*; 14 | pub use crate::theme::*; 15 | pub use crate::transition::*; 16 | pub use crate::transitioner::*; 17 | 18 | pub type Vpos = f32; 19 | pub type Hpos = f32; 20 | pub type Duration = f32; 21 | pub type Width = f32; 22 | pub type Height = f32; 23 | pub type FontSize = u16; 24 | -------------------------------------------------------------------------------- /src/shaders.rs: -------------------------------------------------------------------------------- 1 | pub mod crt { 2 | pub const FRAGMENT: &str = r#"#version 100 3 | precision lowp float; 4 | varying vec4 color; 5 | varying vec2 uv; 6 | 7 | uniform sampler2D Texture; 8 | // https://www.shadertoy.com/view/XtlSD7 9 | vec2 CRTCurveUV(vec2 uv) 10 | { 11 | uv = uv * 2.0 - 1.0; 12 | vec2 offset = abs( uv.yx ) / vec2( 6.0, 4.0 ); 13 | uv = uv + uv * offset * offset; 14 | uv = uv * 0.5 + 0.5; 15 | return uv; 16 | } 17 | void DrawVignette( inout vec3 color, vec2 uv ) 18 | { 19 | float vignette = uv.x * uv.y * ( 1.0 - uv.x ) * ( 1.0 - uv.y ); 20 | vignette = clamp( pow( 16.0 * vignette, 0.3 ), 0.0, 1.0 ); 21 | color *= vignette; 22 | } 23 | void DrawScanline( inout vec3 color, vec2 uv ) 24 | { 25 | float iTime = 0.1; 26 | float scanline = clamp( 0.95 + 0.05 * cos( 3.14 * ( uv.y + 0.008 * iTime ) * 240.0 * 1.0 ), 0.0, 1.0 ); 27 | float grille = 0.85 + 0.15 * clamp( 1.5 * cos( 3.14 * uv.x * 640.0 * 1.0 ), 0.0, 1.0 ); 28 | color *= scanline * grille * 1.2; 29 | } 30 | void main() { 31 | vec2 crtUV = CRTCurveUV(uv); 32 | 33 | vec3 res = texture2D(Texture, uv).rgb * color.rgb; 34 | 35 | if (crtUV.x < 0.0 || crtUV.x > 1.0 || crtUV.y < 0.0 || crtUV.y > 1.0) 36 | { 37 | res = vec3(0.0, 0.0, 0.0); 38 | } 39 | DrawVignette(res, crtUV); 40 | DrawScanline(res, uv); 41 | gl_FragColor = vec4(res, 1.0); 42 | } 43 | "#; 44 | 45 | pub const VERTEX: &str = r#"#version 100 46 | attribute vec3 position; 47 | attribute vec2 texcoord; 48 | attribute vec4 color0; 49 | varying lowp vec2 uv; 50 | varying lowp vec4 color; 51 | uniform mat4 Model; 52 | uniform mat4 Projection; 53 | void main() { 54 | gl_Position = Projection * Model * vec4(position, 1); 55 | color = color0 / 255.0; 56 | uv = texcoord; 57 | } 58 | "#; 59 | } 60 | -------------------------------------------------------------------------------- /src/show_help.rs: -------------------------------------------------------------------------------- 1 | use macroquad::{ 2 | color::{Color, colors::WHITE}, 3 | shapes::draw_rectangle, 4 | text::draw_text, 5 | window::{screen_height, screen_width}, 6 | }; 7 | 8 | pub struct ShowHelp { 9 | pub showing: bool, 10 | } 11 | 12 | impl Default for ShowHelp { 13 | fn default() -> Self { 14 | Self::new() 15 | } 16 | } 17 | 18 | impl ShowHelp { 19 | const BACKGROUND_COLOR: Color = Color::new(0.1, 0.1, 0.1, 0.5); 20 | const FONT_COLOR: Color = WHITE; 21 | const MARGIN: f32 = 60.; 22 | const FONT_SIZE: f32 = 70.; 23 | const LINE_OFFSET: f32 = 10.; 24 | const HELP_TEXT: &'static str = include_str!("helptext.txt"); 25 | 26 | pub fn new() -> Self { 27 | Self { showing: false } 28 | } 29 | 30 | pub fn draw(&self) { 31 | if !self.showing { 32 | return; 33 | } 34 | draw_rectangle( 35 | Self::MARGIN, 36 | Self::MARGIN, 37 | screen_width() - Self::MARGIN * 2., 38 | screen_height() - Self::MARGIN * 2., 39 | Self::BACKGROUND_COLOR, 40 | ); 41 | 42 | let mut offset_y = Self::MARGIN + Self::FONT_SIZE + Self::LINE_OFFSET; 43 | for line in Self::HELP_TEXT.split('\n') { 44 | offset_y = self.draw_line(Self::MARGIN * 2., offset_y, line); 45 | } 46 | } 47 | 48 | pub fn toggle_show(&mut self) { 49 | self.showing = !self.showing 50 | } 51 | 52 | fn draw_line(&self, offset_x: f32, offset_y: f32, text: &str) -> f32 { 53 | draw_text(text, offset_x, offset_y, Self::FONT_SIZE, Self::FONT_COLOR); 54 | offset_y + Self::FONT_SIZE + Self::LINE_OFFSET 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/textbox.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use macroquad::prelude::*; 3 | 4 | #[derive(Clone)] 5 | pub struct TextBox { 6 | width: Width, 7 | height: Height, 8 | margin: Height, 9 | padding: f32, 10 | offset_y: Vpos, 11 | background_color: Option, 12 | style: TextBoxStyle, 13 | lines: Vec, 14 | } 15 | 16 | impl TextBox { 17 | const BOX_PADDING: f32 = 20.; 18 | 19 | pub fn new( 20 | lines: Vec, 21 | margin: Height, 22 | background_color: Option, 23 | style: TextBoxStyle, 24 | ) -> Self { 25 | let mut width: Width = 0.; 26 | let mut height: Height = 0.; 27 | let mut offset_y: Vpos = 0.; 28 | for line in lines.iter() { 29 | width = width.max(line.width); 30 | offset_y = offset_y.max(line.offset_y); 31 | height += line.height; 32 | } 33 | Self { 34 | width, 35 | height, 36 | margin, 37 | padding: Self::BOX_PADDING, 38 | offset_y, 39 | background_color, 40 | style, 41 | lines, 42 | } 43 | } 44 | 45 | pub fn draw(&self, hpos: Hpos, vpos: Vpos) -> Vpos { 46 | let vpos = self.style.top_position(vpos, self); 47 | self.draw_background(hpos, vpos + self.margin); 48 | self.style.draw(hpos, vpos, self); 49 | let inner_hpos = hpos + self.padding; 50 | let mut new_position = vpos + self.padding + self.margin; 51 | for line in self.lines.iter() { 52 | let line_hpos = match line.align { 53 | DrawAlignment::left => inner_hpos, 54 | DrawAlignment::right => inner_hpos + self.width() - line.width, 55 | DrawAlignment::center => inner_hpos + self.width() / 2. - line.width / 2., 56 | }; 57 | new_position = line.draw(line_hpos, new_position, self.offset_y); 58 | } 59 | vpos + self.height_with_margin() 60 | } 61 | 62 | pub fn draw_background(&self, hpos: Hpos, vpos: Vpos) { 63 | if let Some(color) = self.background_color { 64 | draw_rectangle( 65 | hpos, 66 | vpos, 67 | self.width_with_padding(), 68 | self.height_with_padding(), 69 | color, 70 | ); 71 | } 72 | } 73 | 74 | pub fn background_color(&self) -> Option { 75 | self.background_color 76 | } 77 | 78 | pub fn width(&self) -> Width { 79 | self.width 80 | } 81 | 82 | pub fn width_with_padding(&self) -> Width { 83 | self.width() + self.padding * 2. 84 | } 85 | 86 | pub fn height(&self) -> Height { 87 | self.height 88 | } 89 | 90 | pub fn height_with_padding(&self) -> Height { 91 | self.height() + self.padding * 2. 92 | } 93 | 94 | pub fn height_with_margin(&self) -> Height { 95 | self.height_with_padding() + self.margin 96 | } 97 | } 98 | 99 | #[derive(Clone)] 100 | pub struct TextLine { 101 | width: Width, 102 | height: Height, 103 | offset_y: Vpos, 104 | align: DrawAlignment, 105 | partials: Vec, 106 | } 107 | 108 | impl TextLine { 109 | pub fn new(align: DrawAlignment, partials: Vec) -> Self { 110 | let mut width: Width = 0.; 111 | let mut height: Height = 0.; 112 | let mut offset_y: Vpos = 0.; 113 | for partial in &partials { 114 | width += partial.width; 115 | height = height.max(partial.height); 116 | offset_y = offset_y.max(partial.offset_y); 117 | } 118 | Self { 119 | width, 120 | height, 121 | offset_y, 122 | align, 123 | partials, 124 | } 125 | } 126 | 127 | fn draw(&self, start_hpos: Hpos, vpos: Vpos, offset_y: Vpos) -> Vpos { 128 | let mut hpos = start_hpos; 129 | for partial in &self.partials { 130 | hpos = partial.draw(hpos, vpos, offset_y); 131 | } 132 | vpos + self.height 133 | } 134 | } 135 | 136 | #[derive(Clone)] 137 | pub struct TextPartial { 138 | width: Width, 139 | height: Height, 140 | color: Color, 141 | font: Font, 142 | font_size: FontSize, 143 | offset_y: Vpos, 144 | text: String, 145 | } 146 | 147 | impl TextPartial { 148 | pub fn new( 149 | text: &str, 150 | font: Font, 151 | font_size: FontSize, 152 | color: Color, 153 | line_height: Height, 154 | ) -> Self { 155 | let dimensions = measure_text(text, Some(&font), font_size, 1.); 156 | Self { 157 | width: dimensions.width, 158 | height: font_size as Height * line_height, 159 | color, 160 | font, 161 | font_size, 162 | offset_y: dimensions.offset_y, 163 | text: text.to_owned(), 164 | } 165 | } 166 | 167 | fn draw(&self, hpos: Hpos, vpos: Vpos, offset_y: Vpos) -> Vpos { 168 | draw_text_ex( 169 | &self.text, 170 | hpos, 171 | vpos + offset_y, 172 | TextParams { 173 | font: Some(&self.font), 174 | font_size: self.font_size, 175 | color: self.color, 176 | ..Default::default() 177 | }, 178 | ); 179 | hpos + self.width 180 | } 181 | } 182 | #[derive(Clone)] 183 | pub enum TextBoxStyle { 184 | Standard, 185 | Title, 186 | Blockquote { 187 | size: FontSize, 188 | font: Font, 189 | color: Color, 190 | }, 191 | Code, 192 | } 193 | 194 | impl TextBoxStyle { 195 | fn draw(&self, hpos: Hpos, vpos: Vpos, draw_box: &TextBox) { 196 | if let TextBoxStyle::Blockquote { size, font, color } = self { 197 | self.draw_blockquote(hpos, vpos, draw_box, *size, font.clone(), *color) 198 | } 199 | } 200 | 201 | fn top_position(&self, vpos: Vpos, draw_box: &TextBox) -> Vpos { 202 | match self { 203 | TextBoxStyle::Title => { 204 | screen_height() / 2. 205 | - draw_box.height / 2. 206 | - draw_box.margin 207 | - draw_box.padding 208 | - draw_box.offset_y 209 | } 210 | _ => vpos, 211 | } 212 | } 213 | 214 | fn draw_blockquote( 215 | &self, 216 | hpos: Hpos, 217 | vpos: Vpos, 218 | draw_box: &TextBox, 219 | font_size: FontSize, 220 | font: Font, 221 | color: Color, 222 | ) { 223 | let text_params = TextParams { 224 | font: Some(&font), 225 | font_size, 226 | color, 227 | ..Default::default() 228 | }; 229 | let dimensions = measure_text("“", Some(&font), font_size, 1.); 230 | draw_text_ex( 231 | "“", 232 | hpos - dimensions.width, 233 | vpos + font_size as Vpos, 234 | text_params.clone(), 235 | ); 236 | draw_text_ex( 237 | "„", 238 | hpos + draw_box.width_with_padding(), 239 | vpos + draw_box.height_with_margin(), 240 | text_params.clone(), 241 | ); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/theme.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use macroquad::prelude::{Color, WHITE, debug, load_string}; 3 | use nanoserde::DeJson; 4 | use std::path::PathBuf; 5 | 6 | #[derive(Clone, DeJson)] 7 | #[nserde(default)] 8 | pub struct Theme { 9 | pub background_image: Option, 10 | #[nserde(proxy = "HexColor")] 11 | pub background_color: Color, 12 | #[nserde(proxy = "HexColor")] 13 | pub heading_color: Color, 14 | #[nserde(proxy = "HexColor")] 15 | pub text_color: Color, 16 | pub align: DrawAlignment, 17 | pub font: String, 18 | pub font_bold: String, 19 | pub font_italic: String, 20 | pub font_size_header_title: FontSize, 21 | pub font_size_header_slides: FontSize, 22 | pub font_size_text: FontSize, 23 | pub vertical_offset: Vpos, 24 | pub horizontal_offset: Hpos, 25 | pub line_height: Height, 26 | #[nserde(proxy = "HexColor")] 27 | pub blockquote_background_color: Color, 28 | pub blockquote_padding: f32, 29 | pub blockquote_left_quote: String, 30 | pub blockquote_right_quote: String, 31 | pub font_code: String, 32 | pub font_code_size: FontSize, 33 | pub code_line_height: Height, 34 | #[nserde(proxy = "HexColor")] 35 | pub code_background_color: Color, 36 | pub code_theme: String, 37 | pub code_tab_width: usize, 38 | pub bullet: String, 39 | pub shader: bool, 40 | pub transition: Option, 41 | } 42 | 43 | impl Default for Theme { 44 | fn default() -> Theme { 45 | Theme { 46 | background_image: None, 47 | background_color: Color::from_rgba(48, 25, 52, 255), 48 | heading_color: Color::from_rgba(177, 156, 217, 255), 49 | text_color: WHITE, 50 | align: DrawAlignment::center, 51 | font: "assets/Amble-Regular.ttf".to_string(), 52 | font_bold: "assets/Amble-Bold.ttf".to_string(), 53 | font_italic: "assets/Amble-Italic.ttf".to_string(), 54 | font_size_header_title: 100, 55 | font_size_header_slides: 80, 56 | font_size_text: 40, 57 | vertical_offset: 20.0, 58 | horizontal_offset: 20.0, 59 | line_height: 2.0, 60 | blockquote_background_color: Color::from_rgba(51, 51, 51, 255), 61 | blockquote_padding: 20., 62 | blockquote_left_quote: "“".to_string(), 63 | blockquote_right_quote: "„".to_string(), 64 | font_code: "assets/Hack-Regular.ttf".to_string(), 65 | font_code_size: 20, 66 | code_line_height: 1.2, 67 | code_background_color: Color::from_rgba(0, 43, 54, 255), 68 | code_theme: "Solarized (dark)".to_string(), 69 | code_tab_width: 4, 70 | bullet: "• ".to_string(), 71 | shader: true, 72 | transition: Some(Transitioning::swiperight), 73 | } 74 | } 75 | } 76 | 77 | impl Theme { 78 | pub async fn load(theme_path: PathBuf) -> Self { 79 | let path = theme_path.as_path().to_str().unwrap().to_owned(); 80 | debug!("Theme path: {}", path); 81 | match load_string(&path).await { 82 | Ok(json) => match DeJson::deserialize_json(&json) { 83 | Ok(theme) => theme, 84 | Err(_) => { 85 | eprintln!("Couldn't parse theme file: {}", path); 86 | std::process::exit(2); 87 | } 88 | }, 89 | Err(_) => Theme::default(), 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/transition.rs: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 TanTanDev 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 | 23 | use macroquad::prelude::*; 24 | 25 | #[derive(Default)] 26 | pub struct DrawParam { 27 | pub flip_y: bool, 28 | } 29 | 30 | pub struct Transition { 31 | pub material: Material, 32 | pub fade: f32, 33 | } 34 | 35 | impl Transition { 36 | pub fn draw(&mut self, base_texture: Texture2D, into_texture: Texture2D, progress: f32) { 37 | self.draw_ex(base_texture, into_texture, progress, DrawParam::default()); 38 | } 39 | 40 | pub fn draw_ex( 41 | &mut self, 42 | base_texture: Texture2D, 43 | into_texture: Texture2D, 44 | progress: f32, 45 | draw_param: DrawParam, 46 | ) { 47 | self.material.set_uniform("cutoff", progress); 48 | self.material.set_uniform("fade", self.fade); 49 | self.material.set_texture("tex_into", into_texture); 50 | gl_use_material(&self.material); 51 | clear_background(WHITE); 52 | draw_texture_ex( 53 | &base_texture, 54 | -1., 55 | -1., 56 | WHITE, 57 | DrawTextureParams { 58 | dest_size: Some(vec2(2., 2.)), 59 | flip_y: draw_param.flip_y, 60 | ..Default::default() 61 | }, 62 | ); 63 | gl_use_default_material(); 64 | } 65 | 66 | pub fn change_transition_tex(&mut self, texture: Texture2D) { 67 | self.material.set_texture("tex_transition", texture); 68 | } 69 | 70 | pub fn new(transition_tex: Texture2D, fade: f32) -> Self { 71 | let pipeline_params = PipelineParams { 72 | depth_write: true, 73 | depth_test: Comparison::LessOrEqual, 74 | ..Default::default() 75 | }; 76 | 77 | let material = load_material( 78 | ShaderSource::Glsl { 79 | vertex: DEFAULT_VERTEX_SHADER, 80 | fragment: DEFAULT_FRAGMENT_SHADER, 81 | }, 82 | MaterialParams { 83 | textures: vec!["tex_transition".to_string(), "tex_into".to_string()], 84 | uniforms: vec![ 85 | UniformDesc::new("cutoff", UniformType::Float1), 86 | UniformDesc::new("fade", UniformType::Float1), 87 | ], 88 | pipeline_params, 89 | }, 90 | ) 91 | .unwrap(); 92 | 93 | material.set_texture("tex_transition", transition_tex); 94 | Transition { material, fade } 95 | } 96 | } 97 | 98 | const DEFAULT_FRAGMENT_SHADER: &str = "#version 100 99 | precision lowp float; 100 | varying vec2 uv; 101 | 102 | uniform float cutoff; 103 | uniform float fade; 104 | // base texture 105 | uniform sampler2D Texture; 106 | uniform sampler2D tex_into; 107 | uniform sampler2D tex_transition; 108 | 109 | varying vec4 color; 110 | 111 | void main() { 112 | float transition = texture2D(tex_transition, uv).r; 113 | vec4 base_color = texture2D(Texture, uv); 114 | vec4 into_color = texture2D(tex_into, uv); 115 | 116 | // remap transition from 0-1 to fade -> 1.0-fade 117 | transition = transition * (1.0 - fade) + fade; 118 | float f = smoothstep(cutoff, cutoff + fade, transition); 119 | gl_FragColor = mix(base_color, into_color, f); 120 | } 121 | "; 122 | 123 | const DEFAULT_VERTEX_SHADER: &str = "#version 100 124 | attribute vec3 position; 125 | attribute vec2 texcoord; 126 | varying vec2 uv; 127 | 128 | void main() { 129 | gl_Position = vec4(position, 1); 130 | uv = texcoord; 131 | } 132 | "; 133 | -------------------------------------------------------------------------------- /src/transitioner.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use std::{ 3 | collections::HashMap, 4 | fmt, 5 | path::{Path, PathBuf}, 6 | }; 7 | use {macroquad::prelude::*, nanoserde::DeJson}; 8 | use {strum::IntoEnumIterator, strum_macros::EnumIter}; 9 | 10 | #[allow(non_camel_case_types)] 11 | #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord, DeJson, EnumIter)] 12 | pub enum Transitioning { 13 | bignoise, 14 | blobs, 15 | checkerboard, 16 | circleswipe, 17 | cubicnoise, 18 | curtainsclose, 19 | curtainsopen, 20 | diagonalleft, 21 | diagonalright, 22 | fan, 23 | halftone, 24 | implode, 25 | lines, 26 | maze, 27 | mosaic, 28 | noise, 29 | plasma, 30 | radialin, 31 | radialout, 32 | smoke, 33 | split, 34 | starburst, 35 | stripes, 36 | swipedown, 37 | swipeleft, 38 | swiperight, 39 | swipeup, 40 | swirl, 41 | triangles, 42 | vortex, 43 | waves, 44 | zebra, 45 | } 46 | 47 | impl Default for Transitioning { 48 | fn default() -> Self { 49 | Transitioning::split 50 | } 51 | } 52 | 53 | impl fmt::Display for Transitioning { 54 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 55 | write!(f, "{:?}", self) 56 | } 57 | } 58 | 59 | impl Transitioning { 60 | pub fn texture

(&self, directory: P) -> PathBuf 61 | where 62 | P: AsRef, 63 | { 64 | Self::with_dir(directory, self.filename()) 65 | } 66 | 67 | fn filename(&self) -> String { 68 | format!("transitions/{}.png", self) 69 | } 70 | 71 | fn with_dir

(directory: P, path: String) -> PathBuf 72 | where 73 | P: AsRef, 74 | { 75 | let mut dir = directory.as_ref().to_path_buf(); 76 | dir.push(path); 77 | dir.as_path().to_owned() 78 | } 79 | } 80 | 81 | pub struct Transitioner { 82 | render_target: RenderTarget, 83 | transition: Transition, 84 | transition_progress: Duration, 85 | pub transitioning: bool, 86 | textures: HashMap, 87 | transitions: Vec, 88 | current_transition: usize, 89 | } 90 | 91 | impl Transitioner { 92 | const TRANSITIONING_TIME: Duration = 1.0; 93 | 94 | pub async fn load

(asset_dir: P, transitioning: Transitioning, fade: f32) -> Self 95 | where 96 | P: AsRef, 97 | { 98 | let mut textures: HashMap = HashMap::new(); 99 | for transitioning in Transitioning::iter() { 100 | let transition_tex: Texture2D = 101 | load_texture(transitioning.texture(&asset_dir).to_str().unwrap()) 102 | .await 103 | .expect("Failed loading transition texture"); 104 | textures.insert(transitioning, transition_tex); 105 | } 106 | let mut transitions = textures.keys().cloned().collect::>(); 107 | transitions.sort(); 108 | let transition = Transition::new(textures.get(&transitioning).unwrap().to_owned(), fade); 109 | let render_target = render_target(screen_width() as u32, screen_height() as u32); 110 | render_target.texture.set_filter(FilterMode::Linear); 111 | Self { 112 | render_target, 113 | transition, 114 | transition_progress: 0., 115 | transitioning: false, 116 | textures, 117 | transitions, 118 | current_transition: 0, 119 | } 120 | } 121 | 122 | pub fn start(&mut self) { 123 | self.transition_progress = 0.; 124 | self.transitioning = true; 125 | } 126 | 127 | pub fn stop(&mut self) { 128 | self.transition_progress = 0.; 129 | self.transitioning = false; 130 | } 131 | 132 | pub fn current_transition(&self) -> Option<&Transitioning> { 133 | self.transitions.get(self.current_transition) 134 | } 135 | 136 | pub fn set_transition(&mut self, transitioning: &Transitioning) { 137 | self.transition 138 | .change_transition_tex(self.textures.get(transitioning).unwrap().to_owned()); 139 | } 140 | 141 | pub fn next_transition(&mut self) { 142 | self.current_transition += 1; 143 | if self.current_transition == self.textures.len() { 144 | self.current_transition = 0; 145 | } 146 | } 147 | 148 | pub fn update(&mut self, delta: Duration) { 149 | if !self.transitioning { 150 | return; 151 | } 152 | self.transition_progress += delta * 2.; 153 | if self.transition_progress > Self::TRANSITIONING_TIME { 154 | self.stop(); 155 | } 156 | } 157 | 158 | pub fn draw(&mut self, from_texture: Texture2D, to_texture: Texture2D) { 159 | let mut render_target_camera = 160 | Camera2D::from_display_rect(Rect::new(0., 0., screen_width(), screen_height())); 161 | render_target_camera.render_target = Some(self.render_target.clone()); 162 | set_camera(&render_target_camera); 163 | 164 | self.transition 165 | .draw(to_texture, from_texture, self.transition_progress); 166 | } 167 | 168 | pub fn texture(&self) -> Texture2D { 169 | self.render_target.texture.clone() 170 | } 171 | } 172 | --------------------------------------------------------------------------------