├── .nojekyll ├── nimib.toml ├── docs ├── assets │ └── doorbell.mp3 ├── head_other.mustache ├── index.json ├── flashing_canvas.html ├── get_started.html ├── easing.html ├── doorbell.html ├── index.html ├── polygons.html ├── keyboard.html ├── sinewave.html ├── okazz_220919a.html └── okazz_221026a.html ├── src ├── p5.nim └── p5 │ ├── p5sound.nim │ ├── p5nimib.nim │ ├── p5types.nim │ ├── p5sugar.nim │ └── p5instance_logic.nim ├── docsrc ├── flashing_canvas.nim ├── get_started.nim ├── easing.nim ├── doorbell.nim ├── sinewave.nim ├── keyboard.nim ├── polygons.nim ├── index.nim ├── instances.nim ├── okazz_220919a.nim └── okazz_221026a.nim ├── p5.nimble ├── LICENSE.md └── README.MD /.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nimib.toml: -------------------------------------------------------------------------------- 1 | [nimib] 2 | srcDir = "docsrc" 3 | homeDir = "docs" -------------------------------------------------------------------------------- /docs/assets/doorbell.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pietroppeter/p5nim/master/docs/assets/doorbell.mp3 -------------------------------------------------------------------------------- /docs/head_other.mustache: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/p5.nim: -------------------------------------------------------------------------------- 1 | when defined(js): 2 | import p5 / [p5js, p5sugar, p5instance_logic, p5types] 3 | export p5js, p5sugar, p5instance_logic, p5types 4 | 5 | import std / lenientops 6 | export lenientops 7 | converter toFloat*(n: int): float = float(n) 8 | else: 9 | import p5 / p5nimib 10 | export p5nimib 11 | -------------------------------------------------------------------------------- /src/p5/p5sound.nim: -------------------------------------------------------------------------------- 1 | # see https://github.com/processing/p5.js-sound/blob/main/src/soundfile.js 2 | # and https://p5js.org/reference/#/p5.SoundFile 3 | type SoundFile* = ref object 4 | 5 | {. push importc .} 6 | 7 | proc loadSound*(path: cstring): SoundFile 8 | 9 | {. pop .} 10 | 11 | {. push importcpp .} 12 | 13 | proc play*(soundFile: SoundFile) 14 | proc stop*(soundFile: SoundFile) 15 | proc isPlaying*(soundFile: SoundFile): bool 16 | 17 | {. pop .} -------------------------------------------------------------------------------- /docsrc/flashing_canvas.nim: -------------------------------------------------------------------------------- 1 | import nimib, index, p5 2 | nbInit 3 | nbUseP5 4 | nb.addEntry(2, "Flashing canvas", "A canvas that flashes random colors (shows how to change frameRate)") 5 | nbCodeDisplay(nbJsFromCode): 6 | setup: 7 | createCanvas(500, 500) 8 | frameRate(1) # 1 frame every second (default is 60 frames per second) 9 | 10 | draw: 11 | background(random()*255, random()*255, random()*255) 12 | 13 | nbJsShowSource() 14 | nbSave -------------------------------------------------------------------------------- /docsrc/get_started.nim: -------------------------------------------------------------------------------- 1 | import nimib, index, p5 2 | nbInit 3 | nbUseP5 4 | nb.addEntry(1, "Get started", """ 5 | This program creates a canvas that is 400 pixels wide and 400 pixels high, and then starts drawing white circles at the position of the mouse. When a mouse button is pressed, the circle color changes to black. 6 | """) 7 | nbCodeDisplay(nbJsFromCode): 8 | setup: 9 | createCanvas(400, 400) 10 | background(200) 11 | 12 | draw: 13 | if mouseIsPressed: 14 | fill(0) 15 | else: 16 | fill(255) 17 | ellipse(mouseX, mouseY, 40, 40) 18 | nbSave -------------------------------------------------------------------------------- /docsrc/easing.nim: -------------------------------------------------------------------------------- 1 | import nimib, index, p5 2 | nbInit 3 | nbUseP5 4 | nb.addEntry(3, "Easing", """ 5 | Move the mouse across the screen and the symbol will follow. 6 | """) 7 | nbCodeDisplay(nbJsFromCode): 8 | var x = 1.0 9 | var y = 1.0 10 | var easing = 0.05 11 | 12 | setup: 13 | createCanvas(400, 400) 14 | noStroke() 15 | 16 | draw: 17 | background(237, 34, 93) 18 | let targetX = mouseX 19 | let dx = targetX - x 20 | x += dx * easing 21 | 22 | let targetY = mouseY 23 | let dy = targetY - y 24 | y += dy * easing 25 | 26 | ellipse(x, y, 66, 66) 27 | 28 | nbJsShowSource() 29 | nbSave -------------------------------------------------------------------------------- /src/p5/p5nimib.nim: -------------------------------------------------------------------------------- 1 | template nbUseP5* = 2 | nb.partials["head"] &= """""" 3 | nbJsFromCodeGlobal: 4 | import p5 5 | 6 | template nbP5Instance*(body: untyped) = 7 | let p5instanceId = "p5instance-" & $nb.newId() 8 | nbRawHtml: "
" 9 | nbJsFromCode(p5instanceId): 10 | instance(p5instanceId): 11 | body 12 | 13 | template nbUseP5Sound* = 14 | nb.partials["head"] &= """""" 15 | nbJsFromCodeGlobal: 16 | import p5 / p5sound 17 | -------------------------------------------------------------------------------- /p5.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.1.0" 4 | author = "Jacob Romano, Pietro Peterlongo" 5 | description = "Nim bindings for p5.js" 6 | license = "MIT" 7 | 8 | srcDir = "src" 9 | 10 | # Dependencies 11 | 12 | requires "nim >= 1.6.0" 13 | 14 | template build(name: string) = exec "nim r docsrc/" & name 15 | 16 | task index, "rebuild the index": 17 | build "index" 18 | 19 | task examples, "rebuild all examples": 20 | build "get_started" 21 | build "flashing_canvas" 22 | build "easing" 23 | build "keyboard" 24 | build "polygons" 25 | build "okazz_220919a" 26 | build "okazz_221026a" 27 | build "sinewave" 28 | build "instances" 29 | build "doorbell" -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2018 Jacob Romano 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. -------------------------------------------------------------------------------- /docsrc/doorbell.nim: -------------------------------------------------------------------------------- 1 | import nimib, index, p5 2 | nbInit 3 | nbUseP5 4 | nbUseP5Sound 5 | 6 | nb.addEntry(10, "Doorbell", """ 7 | How to load and play a sound (using [p5sound](https://p5js.org/reference/#/libraries/p5.sound)). 8 | Adapted from [p5.SoundFile](https://p5js.org/reference/#/p5.SoundFile) reference. 9 | """) 10 | nbText: """ 11 | This uses the additional [p5sound](https://p5js.org/reference/#/libraries/p5.sound) library. 12 | You will need to call `nbUseP5Sound` to use it in nimib. 13 | Loading the sound file does not work when opening the html locally in a browser, 14 | you will need to run a static server (e.g. [nimhttpd](https://github.com/h3rald/nimhttpd)). 15 | """ 16 | 17 | nbCodeDisplay(nbJsFromCode): 18 | const nimColor = "#ffc200" 19 | var doorbell: SoundFile 20 | 21 | preload: 22 | doorbell = loadSound("assets/doorbell.mp3") 23 | 24 | setup: 25 | createCanvas(200, 100) 26 | background(nimColor) 27 | text("click to play the doorbell 🛎️", 10, 20) 28 | 29 | mousePressed: 30 | play(doorbell) 31 | 32 | nbSave -------------------------------------------------------------------------------- /docsrc/sinewave.nim: -------------------------------------------------------------------------------- 1 | import nimib, index, p5 2 | nbInit 3 | nbUseP5 4 | nb.addEntry(8, "Sine Wave", """ 5 | Render a simple sine wave. Original by Daniel Shiffman 6 | """) 7 | nbCodeDisplay(nbJsFromCode): 8 | const xspacing = 16 # Distance between each horizontal location 9 | const amplitude = 75.0 # Height of wave 10 | const period = 500.0 # How many pixels before the wave repeats 11 | 12 | var theta: float # Start angle at 0 13 | var yvalues: seq[float] 14 | 15 | proc calcWave = 16 | theta+=0.02 17 | let dx = (TWO_PI / period) * xspacing 18 | for x in 0..yvalues.len: 19 | yvalues[x] = sin(x*dx+theta)*amplitude 20 | 21 | proc renderWave = 22 | noStroke() 23 | colorMode(HSB, 100) 24 | fill(0) 25 | for x in 0..yvalues.len: 26 | fill(25+x*2, 100, 50) 27 | ellipse(x*xspacing, height/2+yvalues[x], 16, 16) 28 | 29 | setup: 30 | theta = 0.0 31 | createCanvas(710, 400) 32 | let nelements = (width+16)/xspacing 33 | yvalues = newSeq[float](nelements.int) 34 | 35 | draw: 36 | background(0) 37 | calcWave() 38 | renderWave() 39 | 40 | nbJsShowSource() 41 | nbSave 42 | -------------------------------------------------------------------------------- /docsrc/keyboard.nim: -------------------------------------------------------------------------------- 1 | import nimib, index, p5 2 | nbInit 3 | nbUseP5 4 | nb.addEntry(4, "Keyboard", """ 5 | Click on the image to give it focus and press the letter keys to create forms in time and space. Each key has a unique identifying number. These numbers can be used to position shapes in space. 6 | """) 7 | nbCodeDisplay(nbJsFromCode): 8 | var rectWidth: float 9 | 10 | setup: 11 | createCanvas(720, 400) 12 | noStroke() 13 | background(230) 14 | rectWidth = width / 4 15 | 16 | draw: 17 | ## keep draw() here to continue looping while waiting for keys 18 | discard 19 | 20 | keyPressed: 21 | var keyIndex = -1 22 | if $key >= "a" and $key <= "z": 23 | keyIndex = ord(key[0]) - ord('a') 24 | if keyIndex == -1: 25 | ## If it's not a letter key, clear the screen 26 | background(230) 27 | else: 28 | ## It's a letter key, fill a rectangle 29 | let 30 | randFill_r = floor(random() * 255 + 1) 31 | randFill_g = floor(random() * 255 + 1) 32 | randFill_b = floor(random() * 255 + 1) 33 | fill(randFill_r, randFill_g, randFill_b) 34 | let x = map(keyIndex, 0, 25, 0, width - rectWidth) 35 | rect(x, 0, rectWidth, height) 36 | 37 | nbJsShowSource() 38 | nbSave -------------------------------------------------------------------------------- /docsrc/polygons.nim: -------------------------------------------------------------------------------- 1 | import nimib, index, p5 2 | nbInit 3 | nbUseP5 4 | nb.addEntry(5, "Polygons", """ 5 | What is your favorite? Pentagon? Hexagon? Heptagon? No? What about the icosagon? The polygon() function created for this example is capable of drawing any regular polygon. Also shows [possible inputs for colors](https://p5js.org/reference/#/p5/fill). 6 | """) 7 | nbCodeDisplay(nbJsFromCode): 8 | proc polygon(x, y, radius, npoints: PNumber) = 9 | let angle = TWO_PI / npoints 10 | beginShape() 11 | var a = 0.0 12 | while a < TWO_PI: 13 | let sx = x + cos(a) * radius 14 | let sy = y + sin(a) * radius 15 | a += angle 16 | vertex(sx, sy) 17 | endShape(CLOSE) 18 | 19 | setup: 20 | createCanvas(720, 400) 21 | 22 | draw: 23 | background(102) ## grayscale 24 | 25 | push() 26 | fill(255, 204, 0) ## RGB integer values 27 | translate(width * 0.2, height * 0.5) 28 | rotate(frameCount / 200.0) 29 | polygon(0, 0, 82, 3) 30 | pop() 31 | 32 | push() 33 | fill("red") ## Named SVG/CSS color string 34 | translate(width * 0.5, height * 0.5) 35 | rotate(frameCount / 50.0) 36 | polygon(0, 0, 80, 20) 37 | pop() 38 | 39 | push() 40 | fill("#fae") ## three-digit (or six-digit) hexadecimal RGB notation 41 | translate(width * 0.8, height * 0.5) 42 | rotate(frameCount / -100.0) 43 | polygon(0, 0, 70, 7) 44 | pop() 45 | 46 | nbJsShowSource() 47 | nbSave -------------------------------------------------------------------------------- /docsrc/index.nim: -------------------------------------------------------------------------------- 1 | import std / [os, algorithm, sugar, strformat, strutils, tables] 2 | import nimib except toJson # we need to remove this from nimib exports! it breaks jsony! 3 | import jsony 4 | 5 | type 6 | Entry* = object 7 | filename: string 8 | title: string 9 | description: string 10 | numbering: int 11 | Index* = object 12 | data: seq[Entry] 13 | 14 | proc sort*(idx: var Index) = 15 | idx.data.sort do (e, f: Entry) -> int: 16 | cmp(e.numbering, f.numbering) 17 | 18 | proc dump*(idx: Index) = 19 | var idx = idx 20 | sort(idx) 21 | writeFile("index.json", jsony.toJson(idx)) 22 | 23 | proc loadIndex*: Index = 24 | if fileExists("index.json"): 25 | result = "index.json".readFile.fromJson(Index) 26 | else: 27 | result = Index() 28 | sort result 29 | 30 | # requires nb: NbDoc 31 | proc addEntry*(nb: var NbDoc, numb: int, titl, desc: string) = 32 | let e = Entry(numbering: numb, title: titl, description: desc, filename: nb.filename) 33 | var idx = loadIndex() 34 | var found = false 35 | for f in idx.data.mitems: 36 | if f.filename == e.filename: 37 | f = e 38 | found = true 39 | if not found: 40 | idx.data.add e 41 | dump idx 42 | let text = "## " & $(e.numbering) & ". " & e.title & "\n" & e.description 43 | nbText: text 44 | 45 | when isMainModule: 46 | nbInit 47 | nbText: "## nimp5 documentation" 48 | let idx = loadIndex() 49 | var 50 | listEntries = "" 51 | for entry in idx.data: 52 | listEntries.add fmt"{entry.numbering}. [{entry.title}]({entry.filename}): {entry.description}" & "\n" 53 | nbText: listEntries 54 | nbSave -------------------------------------------------------------------------------- /src/p5/p5types.nim: -------------------------------------------------------------------------------- 1 | type 2 | Color* = ref object 3 | 4 | Touche* = ref object 5 | x {.importc.}: float 6 | y {.importc.}: float 7 | id {.importc.}: int 8 | File* = ref object 9 | 10 | Image* = ref object 11 | width* {.importc.}: float 12 | height* {.importc.}: float 13 | pixels* {.importc.}: seq[float] 14 | 15 | Vector* = ref object 16 | x* {.importc.}: float 17 | y* {.importc.}: float 18 | z* {.importc.}: float 19 | 20 | Graphics* = ref object 21 | pixels*{.importc.}: seq[float] 22 | width*{.importc.}: float 23 | height*{.importc.}: float 24 | mouseX* {.importc.}: float 25 | mouseY* {.importc.}: float 26 | pmouseX* {.importc.}: float 27 | pmouseY* {.importc.}: float 28 | winMouseX* {.importc.}: float 29 | winMouseY* {.importc.}: float 30 | pwinMouseX* {.importc.}: float 31 | pwinMouseY* {.importc.}: float 32 | mouseButton* {.importc.}: cstring 33 | mouseIsPressed* {.importc.}: bool 34 | 35 | Font* = ref object 36 | 37 | Geometry* = ref object 38 | 39 | Shader* = ref object 40 | 41 | XML* = ref object 42 | 43 | PrintWriter* = ref object 44 | 45 | Table* = ref object 46 | 47 | Blob* = ref object 48 | 49 | P5Instance* = ref object 50 | mouseX*: float 51 | mouseY*: float 52 | pmouseX*: float 53 | pmouseY*: float 54 | winMouseX*: float 55 | winMouseY*: float 56 | pwinMouseX*: float 57 | pwinMouseY*: float 58 | mouseButton*: cstring 59 | mouseIsPressed*: bool 60 | keyIsPressed*: bool 61 | key*: cstring 62 | keyCode*: int 63 | setup*: Closure 64 | draw*: Closure 65 | preload*: Closure 66 | 67 | InstanceClosure* = proc(s: P5Instance) {.closure.} 68 | Closure* = proc() {.closure.} 69 | -------------------------------------------------------------------------------- /src/p5/p5sugar.nim: -------------------------------------------------------------------------------- 1 | template setup*(body: untyped) {.dirty.} = 2 | proc setup {.exportc.} = 3 | body 4 | 5 | template draw*(body: untyped) {.dirty.} = 6 | proc draw {.exportc.} = 7 | body 8 | 9 | template keyPressed*(body: untyped) {.dirty.} = 10 | proc keyPressed {.exportc.} = 11 | body 12 | 13 | template preload*(body: untyped) {.dirty.} = 14 | proc preload {.exportc.} = 15 | body 16 | 17 | template remove*(body: untyped) {.dirty.} = 18 | proc remove {.exportc.} = 19 | body 20 | 21 | template preload*(body: untyped) {.dirty.} = 22 | proc preload {.exportc.} = 23 | body 24 | 25 | template mouseMoved*(body: untyped) {.dirty.} = 26 | proc mouseMoved {.exportc.} = 27 | body 28 | 29 | template mouseDragged*(body: untyped) {.dirty.} = 30 | proc mouseDragged {.exportc.} = 31 | body 32 | 33 | template mousePressed*(body: untyped) {.dirty.} = 34 | proc mousePressed {.exportc.} = 35 | body 36 | 37 | template mouseReleased*(body: untyped) {.dirty.} = 38 | proc mouseReleased {.exportc.} = 39 | body 40 | 41 | template mouseClicked*(body: untyped) {.dirty.} = 42 | proc mouseClicked {.exportc.} = 43 | body 44 | 45 | template doubleClicked*(body: untyped) {.dirty.} = 46 | proc doubleClicked {.exportc.} = 47 | body 48 | 49 | template mouseWheel*(body: untyped) {.dirty.} = 50 | proc mouseWheel {.exportc.} = 51 | body 52 | 53 | template keyPressed*(body: untyped) {.dirty.} = 54 | proc keyPressed {.exportc.} = 55 | body 56 | 57 | template keyReleased*(body: untyped) {.dirty.} = 58 | proc keyReleased {.exportc.} = 59 | body 60 | 61 | template keyTyped*(body: untyped) {.dirty.} = 62 | proc keyTyped {.exportc.} = 63 | body 64 | 65 | template touchStarted*(body: untyped) {.dirty.} = 66 | proc touchStarted {.exportc.} = 67 | body 68 | 69 | template touchMoved*(body: untyped) {.dirty.} = 70 | proc touchMoved {.exportc.} = 71 | body 72 | 73 | template touchEnded*(body: untyped) {.dirty.} = 74 | proc touchEnded {.exportc.} = 75 | body 76 | 77 | template deviceMoved*(body: untyped) {.dirty.} = 78 | proc deviceMoved {.exportc.} = 79 | body 80 | 81 | template deviceTurned*(body: untyped) {.dirty.} = 82 | proc deviceTurned {.exportc.} = 83 | body 84 | 85 | template deviceShaken*(body: untyped) {.dirty.} = 86 | proc deviceShaken {.exportc.} = 87 | body 88 | -------------------------------------------------------------------------------- /docs/index.json: -------------------------------------------------------------------------------- 1 | {"data":[{"filename":"./get_started.html","title":"Get started","description":"This program creates a canvas that is 400 pixels wide and 400 pixels high, and then starts drawing white circles at the position of the mouse. When a mouse button is pressed, the circle color changes to black.\n","numbering":1},{"filename":"./flashing_canvas.html","title":"Flashing canvas","description":"A canvas that flashes random colors (shows how to change frameRate)","numbering":2},{"filename":"./easing.html","title":"Easing","description":"Move the mouse across the screen and the symbol will follow.\n","numbering":3},{"filename":"./keyboard.html","title":"Keyboard","description":"Click on the image to give it focus and press the letter keys to create forms in time and space. Each key has a unique identifying number. These numbers can be used to position shapes in space.\n","numbering":4},{"filename":"./polygons.html","title":"Polygons","description":"What is your favorite? Pentagon? Hexagon? Heptagon? No? What about the icosagon? The polygon() function created for this example is capable of drawing any regular polygon. Also shows [possible inputs for colors](https://p5js.org/reference/#/p5/fill).\n","numbering":5},{"filename":"./okazz_220919a.html","title":"Okazz 220919a","description":"Art by [Okazz](https://openprocessing.org/user/128718?view=sketches&o=31),\noriginal at [openprocessing.org/sketch/1653811](https://openprocessing.org/sketch/1653811).\n","numbering":6},{"filename":"./okazz_221026a.html","title":"Okazz 221026a","description":"Art by [Okazz](https://openprocessing.org/user/128718?view=sketches&o=31),\noriginal at [openprocessing.org/sketch/1653811](https://openprocessing.org/sketch/1711659).\n","numbering":7},{"filename":"./sinewave.html","title":"Sine Wave","description":"Render a simple sine wave. Original by Daniel Shiffman \n","numbering":8},{"filename":"./instances.html","title":"Instance mode","description":"This program creates a canvas in a local p5 instance using\n[instance mode](https://github.com/processing/p5.js/wiki/Global-and-instance-mode).\n","numbering":9},{"filename":"./doorbell.html","title":"Doorbell","description":"How to load and play a sound (using [p5sound](https://p5js.org/reference/#/libraries/p5.sound)).\nAdapted from [p5.SoundFile](https://p5js.org/reference/#/p5.SoundFile) reference.\n","numbering":10}]} -------------------------------------------------------------------------------- /docsrc/instances.nim: -------------------------------------------------------------------------------- 1 | import nimib, index, p5 2 | nbInit 3 | nbUseP5 4 | nb.addEntry(9, "Instance mode", """ 5 | This program creates a canvas in a local p5 instance using 6 | [instance mode](https://github.com/processing/p5.js/wiki/Global-and-instance-mode). 7 | """) 8 | nbText: """ 9 | Instance mode allows to place a p5 instance into a specific `
` 10 | and to have multiple p5 canvas in the same document. 11 | 12 | Thanks to nim metaprogramming capabilities, we do not need 13 | to have a different api for instance mode, we can have the same api 14 | we use for global mode, we only need to wrap it in a `instance` call. 15 | This macro will make sure that all the required functions 16 | will have an additional `sketch` argument added. 17 | 18 | In this example we show two copies of `get_started` example. 19 | The syntax here is explicitly contrived to make sure the `instance` 20 | is able to check all possible syntax variations allowed by nim. 21 | """ 22 | nbRawHtml: "
" 23 | nbCodeDisplay(nbJsFromCode): 24 | # to test that a local proc is not converted 25 | proc foo(): int = 400 26 | 27 | instance("canvas1"): # call with `instance("")` to place into specific div 28 | let x = 5 29 | let y = 10 30 | setup: # `setup` and `draw` are templates shadowing the global templates 31 | let bar = foo() # this won't be replaced, as there's no `foo` in p5 that we wrapped 32 | createCanvas(bar, 400) # each call will be checked against calls wrapped in the p5nim wrapper. 33 | # If a name matches, it will be replaced by `p5Inst.` instead. 34 | background(200) 35 | 36 | draw: 37 | # similar to calls, idents are also compared with known fields of the 38 | # `P5Instance` type and replaced by `p5Inst.` in case of a match 39 | if mouseIsPressed: 40 | fill(0) 41 | else: 42 | let fillBy = 255 # store in variable to test rewriting of real `nnkDotExpr` 43 | fillBy.fill # not no `()` 44 | mouseX.ellipse(mouseY, 40, 40) # even if used in dot expression, rewrite works 45 | # (this is a `nnkCall` with `nnkDotExpr` first child) 46 | nbText: "In this second instance we make use of `nbP5Instance` nimib convenience template:" 47 | nbCodeDisplay(nbP5Instance): 48 | let x = 5 49 | let y = 10 50 | setup: # `setup` and `draw` are templates shadowing the global templates 51 | let bar = foo() # this won't be replaced, as there's no `foo` in p5 that we wrapped 52 | createCanvas(bar, 400) # each call will be checked against calls wrapped in the p5nim wrapper. 53 | # If a name matches, it will be replaced by `p5Inst.` instead. 54 | background(200) 55 | 56 | draw: 57 | # similar to calls, idents are also compared with known fields of the 58 | # `P5Instance` type and replaced by `p5Inst.` in case of a match 59 | if mouseIsPressed: 60 | fill(0) 61 | else: 62 | let fillBy = 255 # store in variable to test rewriting of real `nnkDotExpr` 63 | fillBy.fill # not no `()` 64 | mouseX.ellipse(mouseY, 40, 40) # even if used in dot expression, rewrite works 65 | # (this is a `nnkCall` with `nnkDotExpr` first child) 66 | nbSave 67 | -------------------------------------------------------------------------------- /docsrc/okazz_220919a.nim: -------------------------------------------------------------------------------- 1 | import nimib, index, p5 2 | nbInit 3 | nbUseP5 4 | nb.addEntry(6, "Okazz 220919a", """ 5 | Art by [Okazz](https://openprocessing.org/user/128718?view=sketches&o=31), 6 | original at [openprocessing.org/sketch/1653811](https://openprocessing.org/sketch/1653811). 7 | """) 8 | nbText: """ 9 | License is [CreativeCommons Attribution NonCommercial ShareAlike](https://creativecommons.org/licenses/by-nc-sa/3.0/). 10 | 11 | The original sketch has been ported from p5js to p5nim. 12 | """ 13 | # maybe this will be used to train an automatic translator 14 | let sourceOriginalJs = """ 15 | let forms = []; 16 | let colors = ['#f70640', '#f78e2c', '#fdd903', '#cae509', '#63be93', '#81cfe5', '#299dbf', '#38187d', '#a4459f', '#f654a9', '#2F0A30']; 17 | 18 | function setup() { 19 | createCanvas(900, 900); 20 | 21 | let c = 18; 22 | let w = width / c; 23 | for (let i = 0; i < c; i++) { 24 | for (let j = 0; j < c; j++) { 25 | let x = i * w + w / 2; 26 | let y = j * w + w / 2; 27 | if ((i + j) % 2 == 0) { 28 | for (let k = 0; k < 5; k++) { 29 | forms.push(new Form(x, y)); 30 | } 31 | } 32 | } 33 | } 34 | background(0); 35 | 36 | } 37 | 38 | function draw() { 39 | 40 | translate(width / 2, height / 2); 41 | scale(1.1); 42 | translate(-width / 2, -height / 2); 43 | background(255); 44 | for (let i of forms) { 45 | i.show(); 46 | i.move(); 47 | } 48 | } 49 | 50 | class Form { 51 | constructor(x, y) { 52 | this.x = x; 53 | this.y = y; 54 | this.x0 = x; 55 | this.y0 = y; 56 | this.r0 = random(10, 25); 57 | this.r = this.r0; 58 | this.d0 = random(15) * random() + 5; 59 | this.d = this.d0; 60 | this.n = int(random(3, 13)); 61 | this.a = random(100); 62 | this.t = random(10000); 63 | this.r1 = random(0.01); 64 | this.r2 = random(0.01); 65 | this.r3 = random(0.01); 66 | this.col = color(random(colors)); 67 | } 68 | 69 | show() { 70 | noStroke(); 71 | fill(this.col); 72 | push(); 73 | translate(this.x, this.y); 74 | rotate(this.a); 75 | for (let i = 0; i < this.n; i++) { 76 | let theta = map(i, 0, this.n, 0, TAU); 77 | ellipse(this.r * cos(theta), this.r * sin(theta), this.d, this.d); 78 | } 79 | pop(); 80 | } 81 | 82 | move() { 83 | this.t++; 84 | this.a = TAU * sin(this.t * this.r1); 85 | this.r = this.r0 * sin(this.t * this.r2); 86 | this.d = this.d0 * sin(this.t * this.r3); 87 | this.x += 0.5; 88 | this.y -= 0.5; 89 | if (this.x > width) { 90 | this.x = 0; 91 | } 92 | if (this.y < 0) { 93 | this.y = height; 94 | } 95 | } 96 | } 97 | """ 98 | 99 | nbCodeDisplay(nbJsFromCode): 100 | var colors = @["#f70640", "#f78e2c", "#fdd903", "#cae509", "#63be93", "#81cfe5", "#299dbf", "#38187d", "#a4459f", "#f654a9", "#2F0A30"]; 101 | 102 | type 103 | Form = ref object 104 | x, y, x0, y0, r0, r, d0, d: float 105 | a, t, r1, r2, r3: float 106 | n: int 107 | col: string 108 | 109 | var forms: seq[Form] 110 | 111 | proc newForm(x, y: float): Form = 112 | result = new Form 113 | result.x = x 114 | result.y = y 115 | result.x0 = x 116 | result.y0 = y 117 | result.r0 = random(10, 25) 118 | result.r = result.r0 119 | result.d0 = random(15) * random() + 5 120 | result.d = result.d0 121 | result.n = int(random(3, 13)) 122 | result.a = random(100) 123 | result.t = random(10000) 124 | result.r1 = random(0.01) 125 | result.r2 = random(0.01) 126 | result.r3 = random(0.01) 127 | result.col = random(colors) 128 | 129 | proc show(form: Form) = 130 | noStroke() 131 | fill(form.col) 132 | push() 133 | translate(form.x, form.y) 134 | rotate(form.a) 135 | for i in 0 ..< form.n: 136 | let theta = map(i, 0, form.n, 0, TAU) 137 | ellipse(form.r * cos(theta), form.r * sin(theta), form.d, form.d) 138 | pop() 139 | 140 | proc move(form: Form) = 141 | form.t += 1 142 | form.a = TAU * sin(form.t * form.r1); 143 | form.r = form.r0 * sin(form.t * form.r2); 144 | form.d = form.d0 * sin(form.t * form.r3); 145 | form.x += 0.5; 146 | form.y -= 0.5; 147 | if form.x > width: 148 | form.x = 0 149 | if form.y < 0: 150 | form.y = height 151 | 152 | setup: 153 | createCanvas(900, 900) 154 | 155 | var c = 18 156 | var w = width / c 157 | for i in 0 ..< c: 158 | for j in 0 ..< c: 159 | let x = i * w + w / 2; 160 | let y = j * w + w / 2; 161 | if ((i + j) mod 2 == 0): 162 | for k in 0 ..< 5: 163 | forms.add(newForm(x, y)) 164 | background(0) 165 | 166 | draw: 167 | 168 | translate(width / 2, height / 2) 169 | scale(1.1) 170 | translate(-width / 2, -height / 2) 171 | background(255) 172 | for f in forms: 173 | f.show() 174 | f.move() 175 | 176 | nbJsShowSource() 177 | nbJsFromCode(): 178 | keyPressed: 179 | if $key == "s": 180 | echo "saving gif okazz_229019a" 181 | saveGif(cstring("okazz_229019a"), 2) 182 | nbText: "press 's' to export a 2 second gif" 183 | nbSave -------------------------------------------------------------------------------- /docs/flashing_canvas.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | flashing_canvas.nim 5 | 6 | 7 | 8 | 9 | 10 | 11 | 34 | 35 | 36 | 37 |
38 |
39 | 🏡 40 | flashing_canvas.nim 41 | 42 |
43 |
44 |
45 | 46 |

2. Flashing canvas

47 |

A canvas that flashes random colors (shows how to change frameRate)

48 | 49 |
setup:
 50 |   createCanvas(500, 500)
 51 |   frameRate(1) # 1 frame every second (default is 60 frames per second)
 52 |   
 53 | draw:
 54 |   background(random()*255, random()*255, random()*255)
55 | 82 |
83 |
84 |
85 |
86 | made with nimib 🐳 87 | 88 | 89 |
90 |
91 |
92 |
import nimib, index, p5
 93 | nbInit
 94 | nbUseP5
 95 | nb.addEntry(2, "Flashing canvas", "A canvas that flashes random colors (shows how to change frameRate)")
 96 | nbCodeDisplay(nbJsFromCode):
 97 |   setup:
 98 |     createCanvas(500, 500)
 99 |     frameRate(1) # 1 frame every second (default is 60 frames per second)
100 |     
101 |   draw:
102 |     background(random()*255, random()*255, random()*255)
103 | 
104 | nbJsShowSource()
105 | nbSave
106 |
119 | -------------------------------------------------------------------------------- /docs/get_started.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | get_started.nim 5 | 6 | 7 | 8 | 9 | 10 | 11 | 34 | 35 | 36 | 37 |
38 |
39 | 🏡 40 | get_started.nim 41 | 42 |
43 |
44 |
45 | 46 |

1. Get started

47 |

This program creates a canvas that is 400 pixels wide and 400 pixels high, and then starts drawing white circles at the position of the mouse. When a mouse button is pressed, the circle color changes to black.

48 | 49 |
setup:
 50 |   createCanvas(400, 400)
 51 |   background(200)
 52 |   
 53 | draw:
 54 |   if mouseIsPressed:
 55 |     fill(0)
 56 |   else:
 57 |     fill(255)
 58 |   ellipse(mouseX, mouseY, 40, 40)
59 | 93 |
94 |
95 |
96 |
97 | made with nimib 🐳 98 | 99 | 100 |
101 |
102 |
103 |
import nimib, index, p5
104 | nbInit
105 | nbUseP5
106 | nb.addEntry(1, "Get started", """
107 | This program creates a canvas that is 400 pixels wide and 400 pixels high, and then starts drawing white circles at the position of the mouse. When a mouse button is pressed, the circle color changes to black.
108 | """)
109 | nbCodeDisplay(nbJsFromCode):
110 |   setup:
111 |     createCanvas(400, 400)
112 |     background(200)
113 |     
114 |   draw:
115 |     if mouseIsPressed:
116 |       fill(0)
117 |     else:
118 |       fill(255)
119 |     ellipse(mouseX, mouseY, 40, 40)
120 | nbSave
121 |
134 | -------------------------------------------------------------------------------- /docsrc/okazz_221026a.nim: -------------------------------------------------------------------------------- 1 | import nimib, index, p5 2 | nbInit 3 | nbUseP5 4 | nb.addEntry(7, "Okazz 221026a", """ 5 | Art by [Okazz](https://openprocessing.org/user/128718?view=sketches&o=31), 6 | original at [openprocessing.org/sketch/1653811](https://openprocessing.org/sketch/1711659). 7 | """) 8 | nbText: """ 9 | License is [CreativeCommons Attribution NonCommercial ShareAlike](https://creativecommons.org/licenses/by-nc-sa/3.0/). 10 | 11 | The original sketch has been ported from p5js to p5nim. 12 | """ 13 | # maybe this will be used to train an automatic translator 14 | let sourceOriginalJs = """ 15 | let palettes = [ 16 | ['#6b2d5c', '#f0386b', '#ff5376', '#e391db', '#f8c0c8'], 17 | ['#ffe957', '#eec170', '#f2a65a', '#f58549', '#ff6c3f'], 18 | ['#073b3a', '#0b6e4f', '#08a045', '#6bbf59', '#71deb2'], 19 | ['#c52233', '#a51c30', '#a7333f', '#74121d', '#580c1f'], 20 | ['#03045e', '#0077b6', '#00b4d8', '#90e0ef', '#caf0f8'], 21 | ['#f8f9fa', '#dee2e6', '#adb5bd', '#495057', '#212529'] 22 | ]; 23 | let SEED = Math.floor(Math.random() * 1000000); 24 | let forms = []; 25 | 26 | function setup() { 27 | createCanvas(900, 900); 28 | rectMode(CENTER); 29 | // randomSeed(SEED) 30 | // background(255); 31 | // forms.push(new Form(width/2, height/2, 100)); 32 | let seg = 25; 33 | let w = width / seg; 34 | for (let i = 0; i < seg; i++) { 35 | for (let j = 0; j < seg; j++) { 36 | let x = i * w + w / 2; 37 | let y = j * w + w / 2; 38 | forms.push(new Form(x, y, w)); 39 | } 40 | } 41 | } 42 | 43 | function draw() { 44 | background(255); 45 | for (let f of forms) { 46 | f.show(); 47 | f.move(); 48 | } 49 | // this.x = x; 50 | 51 | // randomSeed(SEED) 52 | // 53 | } 54 | 55 | function form(x, y, w) { 56 | // let num = int(random()); 57 | let num = int(5); 58 | let d = dist(width / 2, height / 2, x, y); 59 | let c = int(map(d, 0, sqrt(sq(width / 2) + sq(height / 2)), 0, palettes.length * 2) + frameCount * 0.5); 60 | let colors = palettes[c % palettes.length]; 61 | 62 | // shuffle(colors, true); 63 | for (let i = 0; i < num; i++) { 64 | let r = map(i, 0, num, 0, w / 2); 65 | let ww = map(i, 0, num, w, 0); 66 | let col = colors[i % colors.length]; 67 | fill(col); 68 | rect(x, y, ww, ww, r); 69 | } 70 | } 71 | 72 | class Form { 73 | constructor(x, y, w) { 74 | this.x = x; 75 | this.y = y; 76 | this.w = w; 77 | this.palettes = []; 78 | for (let i = 0; i < palettes.length; i++) { 79 | let c = [] 80 | shuffle(palettes[i], true); 81 | for (let j = 0; j < 5; j++) { 82 | c.push(palettes[i][j]); 83 | } 84 | this.palettes.push(c); 85 | } 86 | this.num = 5; 87 | this.colors = []; 88 | } 89 | 90 | show() { 91 | let d = dist(width / 2, height / 2, this.x, this.y); 92 | let c = int(map(d, sqrt(sq(width / 2) + sq(height / 2)), 0, 0, palettes.length) + frameCount * 0.02); 93 | this.colors = this.palettes[c % palettes.length]; 94 | for (let i = 0; i < this.num; i++) { 95 | let r = map(i, 0, this.num, 0, this.w / 2); 96 | let ww = map(i, 0, this.num, this.w, 0); 97 | let col = this.colors[i % this.colors.length]; 98 | fill(col); 99 | rect(this.x, this.y, ww, ww, r); 100 | } 101 | } 102 | 103 | move() { 104 | 105 | } 106 | 107 | } 108 | """ 109 | 110 | nbJsFromCode: 111 | import std / [random, sequtils] 112 | import std / math except sqrt 113 | 114 | var palettes = @[ 115 | @["#6b2d5c", "#f0386b", "#ff5376", "#e391db", "#f8c0c8"], 116 | @["#ffe957", "#eec170", "#f2a65a", "#f58549", "#ff6c3f"], 117 | @["#073b3a", "#0b6e4f", "#08a045", "#6bbf59", "#71deb2"], 118 | @["#c52233", "#a51c30", "#a7333f", "#74121d", "#580c1f"], 119 | @["#03045e", "#0077b6", "#00b4d8", "#90e0ef", "#caf0f8"], 120 | @["#f8f9fa", "#dee2e6", "#adb5bd", "#495057", "#212529"] 121 | ] 122 | let SEED = math.floor(random.rand(1.0) * 1000000) # using p5js.floor throws an error in console 123 | 124 | type 125 | Form = ref object 126 | x, y, w: float 127 | palettes: seq[seq[string]] 128 | num: int 129 | colors: seq[string] 130 | 131 | var forms: seq[Form] 132 | 133 | proc newForm(x, y, w: float): Form = 134 | result = new Form 135 | result.x = x 136 | result.y = y 137 | result.w = w 138 | result.palettes = newSeqWith(0, newSeq[string]()) 139 | for i in 0 ..< len(palettes): 140 | var c = palettes[i] 141 | shuffle(c) 142 | result.palettes.add(c) 143 | result.num = 5 144 | result.colors = newSeq[string]() 145 | 146 | proc show(form: Form) = 147 | let d = dist(width / 2, height / 2, form.x, form.y) 148 | let c = int(map(d, sqrt(sq(width / 2) + sq(height / 2)), 0, 0, palettes.len) + frameCount * 0.02) 149 | form.colors = form.palettes[c mod palettes.len] 150 | for i in 0 ..< form.num: 151 | let r = map(float(i), 0, form.num, 0, form.w / 2) 152 | let ww = map(float(i), 0, form.num, form.w, 0) 153 | let col = form.colors[i mod form.colors.len] 154 | fill(col) 155 | rect(form.x, form.y, ww, ww, r) 156 | 157 | # no move needed 158 | 159 | setup: 160 | createCanvas(900, 900) 161 | rectMode(CENTER) 162 | # randomSeed(SEED) 163 | # background(255) 164 | # forms.add(newForm(width/2, height/2, 100)) 165 | let seg = 25 166 | let w = width / seg 167 | for i in 0 ..< seg: 168 | for j in 0 ..< seg: 169 | let x = i * w + w / 2 170 | let y = j * w + w / 2 171 | forms.add(newForm(x, y, w)) 172 | 173 | draw: 174 | background(255) 175 | for f in forms: 176 | f.show() 177 | #f.move() 178 | 179 | nbJsShowSource() 180 | nbJsFromCode(): 181 | keyPressed: 182 | if $key == "s": 183 | echo "saving gif okazz_221026a" 184 | saveGif(cstring("okazz_221026a"), 5) 185 | nbText: "press 's' to export a 5 second gif" 186 | nbSave -------------------------------------------------------------------------------- /docs/easing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | easing.nim 5 | 6 | 7 | 8 | 9 | 10 | 11 | 34 | 35 | 36 | 37 |
38 |
39 | 🏡 40 | easing.nim 41 | 42 |
43 |
44 |
45 | 46 |

3. Easing

47 |

Move the mouse across the screen and the symbol will follow.

48 | 49 |
var x = 1.0
 50 | var y = 1.0
 51 | var easing = 0.05
 52 | 
 53 | setup:
 54 |   createCanvas(400, 400)
 55 |   noStroke()
 56 |   
 57 | draw:
 58 |   background(237, 34, 93)
 59 |   let targetX = mouseX
 60 |   let dx = targetX - x
 61 |   x += dx * easing
 62 | 
 63 |   let targetY = mouseY
 64 |   let dy = targetY - y
 65 |   y += dy * easing
 66 | 
 67 |   ellipse(x, y, 66, 66)
68 | 111 |
112 |
113 |
114 |
115 | made with nimib 🐳 116 | 117 | 118 |
119 |
120 |
121 |
import nimib, index, p5
122 | nbInit
123 | nbUseP5
124 | nb.addEntry(3, "Easing", """
125 | Move the mouse across the screen and the symbol will follow.
126 | """)
127 | nbCodeDisplay(nbJsFromCode):
128 |   var x = 1.0
129 |   var y = 1.0
130 |   var easing = 0.05
131 | 
132 |   setup:
133 |     createCanvas(400, 400)
134 |     noStroke()
135 |     
136 |   draw:
137 |     background(237, 34, 93)
138 |     let targetX = mouseX
139 |     let dx = targetX - x
140 |     x += dx * easing
141 | 
142 |     let targetY = mouseY
143 |     let dy = targetY - y
144 |     y += dy * easing
145 | 
146 |     ellipse(x, y, 66, 66)
147 | 
148 | nbJsShowSource()
149 | nbSave
150 |
163 | -------------------------------------------------------------------------------- /docs/doorbell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | doorbell.nim 5 | 6 | 7 | 8 | 9 | 10 | 11 | 34 | 35 | 36 | 37 |
38 |
39 | 🏡 40 | doorbell.nim 41 | 42 |
43 |
44 |
45 | 46 | 47 |

10. Doorbell

48 |

How to load and play a sound (using p5sound). 49 | Adapted from p5.SoundFile reference.

50 |

This uses the additional p5sound library. 51 | You will need to call nbUseP5Sound to use it in nimib. 52 | Loading the sound file does not work when opening the html locally in a browser, 53 | you will need to run a static server (e.g. nimhttpd).

54 | 55 |
const nimColor = "#ffc200"
 56 | var doorbell: SoundFile
 57 | 
 58 | preload:
 59 |   doorbell = loadSound("assets/doorbell.mp3")
 60 | 
 61 | setup:
 62 |   createCanvas(200, 100)
 63 |   background(nimColor)
 64 |   text("click to play the doorbell 🛎️", 10, 20)
 65 | 
 66 | mousePressed:
 67 |   play(doorbell)
68 | 103 |
104 |
105 |
106 |
107 | made with nimib 🐳 108 | 109 | 110 |
111 |
112 |
113 |
import nimib, index, p5
114 | nbInit
115 | nbUseP5
116 | nbUseP5Sound
117 | 
118 | nb.addEntry(10, "Doorbell", """
119 | How to load and play a sound (using [p5sound](https://p5js.org/reference/#/libraries/p5.sound)).
120 | Adapted from [p5.SoundFile](https://p5js.org/reference/#/p5.SoundFile) reference.
121 | """)
122 | nbText: """
123 | This uses the additional [p5sound](https://p5js.org/reference/#/libraries/p5.sound) library.
124 | You will need to call `nbUseP5Sound` to use it in nimib.
125 | Loading the sound file does not work when opening the html locally in a browser,
126 | you will need to run a static server (e.g. [nimhttpd](https://github.com/h3rald/nimhttpd)).
127 | """
128 | 
129 | nbCodeDisplay(nbJsFromCode):
130 |   const nimColor = "#ffc200"
131 |   var doorbell: SoundFile
132 | 
133 |   preload:
134 |     doorbell = loadSound("assets/doorbell.mp3")
135 | 
136 |   setup:
137 |     createCanvas(200, 100)
138 |     background(nimColor)
139 |     text("click to play the doorbell 🛎️", 10, 20)
140 | 
141 |   mousePressed:
142 |     play(doorbell)
143 | 
144 | nbSave
145 |
158 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # 🌸👑 p5nim 2 | 3 | **p5nim** is a gate in the [Nim]👑 universe that leads you to the magical world of [Processing] and [p5.js]🌸. 4 | Or, conversely, a potentially gateway drug from [Processing]/[p5.js] to the thoroughly enjoyable, 5 | dangerously addictive and sort of universal programming language [Nim]. 6 | 7 | More prosaically **p5nim** provides [Nim] bindings for the [p5.js] library, some syntactic sugar 8 | and convenience functions to use it with [nimib]🐳. 9 | In [p5nim example gallery](https://pietroppeter.github.io/p5nim/) you can also 10 | see live a number of [p5.js] examples and [openprocessing] artwork ported to Nim. 11 | Documentation is built with [nimib], a library to publish nim code to the web. 12 | 13 | [Processing] is language, a graphical library and IDE for artists and designers with the purpose 14 | of teaching non-programmers the fundamentals of programming in a visual context. 15 | The project was initiated in 2001 and it was built in Java, one of the reason being that [java applets] 16 | were a common technology at the time to show interactive applications on the web. 17 | [p5.js] was started in 2013 to reinterpret [Processing] for the modern web. 18 | From its website: 19 | 20 | > p5.js is a JavaScript library for creative coding, with a focus on making coding accessible and inclusive for artists, designers, educators, beginners, and anyone else! p5.js is free and open-source because we believe software, and the tools to learn it, should be accessible to everyone. 21 | 22 | Note that there is also an alternative pure nim implementation inspired by p5js (which also works for native target): https://github.com/GabrielLasso/drawim 23 | 24 | ## Status 25 | 26 | This project started as a fork of https://github.com/Foldover/nim-p5/, to fix some bugs. 27 | It has been renamed as `p5nim`, the goal is to clean more the api to make it more Nim idiomatic, 28 | port more examples, fix more stuff and add more convenience functions 29 | to use it with nimib. 30 | Also in scope are additional p5js libraries such as p5sound (see below). 31 | I will add stuff as I need it but you are more than welcome to jump in and help out (check issues)! 32 | 33 | It is available on nimble (replaced nimp5) and you can install it using: 34 | 35 | nimble install https://github.com/pietroppeter/p5nim/ 36 | 37 | You might want instead to clone it, in order to contribute fixing stuff and adding more ported examples: 38 | 39 | git clone https://github.com/pietroppeter/p5nim/ 40 | cd p5nim 41 | nimble develop 42 | 43 | ## Basic usage 44 | 45 | ```nim 46 | import p5 47 | 48 | setup: 49 | createCanvas(400, 400) 50 | background(200) 51 | 52 | draw: 53 | if mouseIsPressed: 54 | fill(0) 55 | else: 56 | fill(255) 57 | ellipse(mouseX, mouseY, 40, 40) 58 | ``` 59 | 60 | As you see from the code above ([live](https://pietroppeter.github.io/p5nim/get_started.html)), using p5nim is almost identical to using p5.js, save for the syntax. 61 | To learn how to use this the best way is to use [p5.js] documentation and port examples. 62 | 63 | ## p5sound 64 | 65 | [p5sound](https://p5js.org/reference/#/libraries/p5.sound) is an additional library from p5js which extends p5 with sound functionalities. 66 | A work-in-progress wrapper is available, see [doorbell](https://pietroppeter.github.io/p5nim/doorbell.html) for example of usage. 67 | 68 | ## Usage with nimib 69 | 70 | ```nim 71 | import nimib 72 | import p5 # only imports nimib utilities when not using js backend 73 | 74 | nbInit 75 | 76 | nbUseP5 # adds p5 js and imports p5 in JsGlobal 77 | nbUseP5Sound # optional in case you want also p5sound 78 | 79 | nbCodeDisplay(nbJsFromCode): # if you want to display the js code 80 | const nimColor = "#ffc200" 81 | var doorbell: SoundFile 82 | 83 | preload: 84 | doorbell = loadSound("assets/doorbell.mp3") 85 | 86 | setup: 87 | createCanvas(200, 100) 88 | background(nimColor) 89 | text("click to play the doorbell 🛎️", 10, 20) 90 | 91 | mousePressed: 92 | play(doorbell) 93 | 94 | nbSave 95 | ``` 96 | 97 | ## Instance mode 98 | 99 | [Instance mode](https://github.com/processing/p5.js/wiki/Global-and-instance-mode) allows to place a p5 instance into a specific `
` 100 | and to have multiple p5 canvas in the same document. 101 | 102 | Thanks to nim metaprogramming capabilities (and mostly [thanks to @Vindaar](https://github.com/pietroppeter/p5nim/pull/5)), 103 | we do not need 104 | to have a different api for instance mode, we can have the same api 105 | we use for global mode, we only need to wrap it in a `instance` call. 106 | 107 | See [instances](https://pietroppeter.github.io/p5nim/instances.html) for an example. 108 | 109 | **Note**: this feature is not yet complete, see https://github.com/pietroppeter/p5nim/issues/8 110 | 111 | ## Porting from p5js to Nim 112 | 113 | There are a number of javascript procedures that p5js needs to know about (like `setup` and `draw`) and they have to be declared with the {.exportc.} pragma. 114 | In p5nim we provide templates that remove the necessity to declare procs with {.exportc.}. The full list is in [src/p5/p5sugar.nim](src/p5/p5sugar.nim). 115 | 116 | Since [Number in javascript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) 117 | is actually a float, in p5nim all procedures are defined with input a `float`. 118 | For convenience `p5nim` exports 119 | [`lenientops`](https://nim-lang.org/docs/lenientops.html) from standard library 120 | and a [converter](https://nim-lang.org/docs/manual.html#converters) from int to float 121 | (in the original bindings `PNumber` - the generic number used by all functions - 122 | is defined as `int or float`; 123 | this behavior is currently still available under the compile time switch `p5IntOrFloat`; 124 | it will likely be removed at some point). 125 | 126 | When converting examples from p5js there are some common pitfalls: 127 | 128 | - some variables (e.g. `key`) are `cstring` (backend compatible strings) 129 | and you might need to convert them with `$` before using them 130 | - when porting an example containing objects you will need to change more stuff, see example [okazz_220919a](https://pietroppeter.github.io/p5nim/okazz_220919a.html): 131 | - nim support for forward declaration is experimental, better to define your object before its usage in setup and draw. 132 | - you will need to declare the type of object. You can pick it to be a `ref object` or a plain `object` (in the examples currently I am mostly using `ref object`, see [this commit](https://github.com/pietroppeter/p5nim/commit/c838f0be6b8871ed48e5c0c7f34f6696e68f1bd1#diff-741692b20db42d00eea54f57dc8b7536948a4dea0200c0c99fa7b9b5566195c9) for an example of changes between the two approaches) 133 | - some math/random functions that seem to be available in `p5js` (such as `floor`, `random` which are defined in the 134 | bindings) do not actually work, so code compiles but the canvas is not shown (you can see an error in the console). 135 | In those cases it is better to import `math` and `random` from stdlib 136 | (but you might run in conflicts, e.g. when using `sqrt` which is also defined in `p5js`) 137 | For an example see [okazz_221026a](https://pietroppeter.github.io/p5nim/okazz_221026a.html) 138 | and try removing the imports of `math` and `random`, use `floor` instead of `math.floor` 139 | or `random(1.0)` instead of `random.rand(1.0)`. 140 | This is actually something for which a solution should be found in this library (remove them from bindings?). 141 | 142 | ## Contribute 143 | 144 | - go through the [examples on p5js site](https://p5js.org/examples/), pick one that you like and convert it to nim. You might want to copy and adapt last example from `docsrc` (look at the index to find out what is the last one). You will need to increment the example number, add a title and a description. It is pretty fun! 145 | - you will have to commit the html in docs, and rerun also `nim r index` 146 | (which uses data from `docs\index.json`) 147 | - as you find out new conversion pitfalls update the readme up there 148 | - an excellent source of open art is [openprocessing] 149 | 150 | 151 | [Nim]: https://nim-lang.org 152 | [nimib]: https://github.com/pietroppeter/nimib/ 153 | [Processing]: https://processing.org 154 | [java applets]: https://en.wikipedia.org/wiki/Java_applet 155 | [p5.js]: https://p5js.org 156 | [openprocessing]: https://openprocessing.org/ 157 | -------------------------------------------------------------------------------- /src/p5/p5instance_logic.nim: -------------------------------------------------------------------------------- 1 | import std / [macros, macrocache] 2 | from std/strutils import `%` 3 | import strformat 4 | 5 | import p5types 6 | 7 | const Arg = "p5Inst" 8 | const WrappedProcs = CacheSeq"p5Procs" 9 | const P5InstanceFields = CacheSeq"p5Fields" 10 | 11 | static: 12 | var tmp = new P5Instance 13 | for field, typ in fieldPairs(tmp[]): 14 | P5InstanceFields.add ident(field) 15 | 16 | proc contains(ct: CacheSeq, s: NimNode): bool = 17 | ## XXX: think about this. Maybe better to use a real table so we don't pay the 18 | ## price for this. Or just use a CT table for O(1) lookup? 19 | for x in ct: 20 | if x == s: return true 21 | 22 | proc replaceCallsImpl(n: NimNode): NimNode 23 | proc maybeReplaceCall(n: NimNode): NimNode = 24 | ## Checks if this node refers to a known procedure wrapped by `globalAndLocal`. 25 | ## If so, generate a call: 26 | ## 27 | ## `n(p5Inst, )` 28 | ## 29 | ## to call the correct overload. 30 | doAssert n.kind in {nnkCall, nnkDotExpr} 31 | # Note: `nnkDotExpr` *may* be a call 32 | proc getName(n: NimNode): NimNode = 33 | ## helper to extract the name from the following cases: 34 | ## - nnkCall with an ident as first child: `ellipse(mouseX, mouseY, ...)` 35 | ## - nnkCall with a nnkDotExpr as first child: `mouseX.ellipse(mouseY, ...)` 36 | ## - pure nnkDotExpr (which may be a call): `fillBy.fill` 37 | case n.kind 38 | of nnkIdent, nnkSym: result = n 39 | of nnkCall: result = getName(n[0]) 40 | of nnkDotExpr: result = getName(n[1]) # if it refers to a call, 2nd child is call 41 | of nnkBracketExpr: result = getName(n[0]) # generic function like newSeq[float] 42 | else: doAssert false, &"Invalid branch ({n.kind}) as `maybeReplaceCall` expects nnkCall or nnkDotExpr" 43 | 44 | let name = getName(n) 45 | let nameIsWrapped = name in WrappedProcs 46 | if n.kind == nnkDotExpr and nameIsWrapped: 47 | # rewrite e.g.: `fillBy.fill` -> `fill(p5Inst, fillBy)` 48 | let dotArg0 = replaceCallsImpl(n[0]) # the initial first elemen, e.g. `fillBy` in above 49 | result = nnkCall.newTree(name, 50 | ident(Arg), 51 | dotArg0) 52 | # NOTE: If I'm not missing something we can never have an `nnkDotExpr`, where the 53 | # second child is a field of a P5Instance. In that case the first child would 54 | # already refer to some instance (or the code would just be invalid) 55 | elif n[0].kind == nnkDotExpr and nameIsWrapped: 56 | # rewrite `mouseX.ellipse(mouseY, ...)` to `ellipse(p5Inst, mouseX, ...)` (and checking args) 57 | let dotArg0 = replaceCallsImpl(n[0][0]) # the initial first element, e.g. `mouseX` in above 58 | result = nnkCall.newTree(name, 59 | ident(Arg), 60 | dotArg0) 61 | # now possibly replace remaining 62 | for i in 1 ..< n.len: 63 | let ch = n[i] 64 | result.add replaceCallsImpl(ch) 65 | # NOTE: this branch and the below could also be handled differently relying on recursing 66 | # back to `replaceCallsImpl`, but this way is more obvious I believe 67 | elif nameIsWrapped: 68 | result = nnkCall.newTree() 69 | # first replace all possible arguments 70 | for ch in n: 71 | result.add replaceCallsImpl(ch) 72 | # now insert the p5 instance argument 73 | result.insert(1, ident(Arg)) # insert argument as first (0 is name of fn) 74 | else: 75 | result = n 76 | 77 | proc maybeReplaceSym(n: NimNode): NimNode = 78 | ## Checks if this node refers to a field of the `P5Instance` type. If so 79 | ## wrap it in a 80 | ## 81 | ## `p5Inst.n` 82 | ## 83 | ## dot expression to access the appropriate field or else leave it alone. 84 | doAssert n.kind in {nnkSym, nnkIdent} 85 | if n in P5InstanceFields: 86 | result = nnkDotExpr.newTree(ident(Arg), n) 87 | else: 88 | result = n 89 | 90 | proc replaceCallsImpl(n: NimNode): NimNode = 91 | ## Recursively walks the NimNode tree and possibly replaces calls and 92 | ## syms if they appear either as a field of the `P5Instance` or 93 | ## they were wrapped by the `globalAndLocal` macro. 94 | if n.len == 0: 95 | case n.kind 96 | of nnkIdent, nnkSym: result = maybeReplaceSym(n) 97 | else: result = n 98 | else: 99 | result = newTree(n.kind) 100 | for ch in n: 101 | case ch.kind 102 | of nnkCall, nnkDotExpr: 103 | result.add maybeReplaceCall(ch) 104 | else: 105 | result.add replaceCallsImpl(ch) 106 | 107 | macro replaceCalls(body: untyped): untyped = 108 | ## Performs replacement of all function / field symbols used in the given 109 | ## `body` based on known fields of `P5Instance` / wrapped procedures with 110 | ## a `P5Instance` first argument for usage in `instance` templates. 111 | result = newStmtList() 112 | result.add replaceCallsImpl(body) 113 | 114 | macro globalAndLocal*(args: untyped): untyped = 115 | ## Generates all given procedures as `importc` once for the global case 116 | ## and once as methods / attributes of a `P5Instance`. 117 | ## 118 | ## Each generated procedure is added to the `WrappedProcs` `CacheSeq` to 119 | ## allow us to replace calls to them within the `instance` template. 120 | # basic pragma string for the instance case, as we ``have`` to generate the 121 | # call as a dot expression and not a regular call with a `p5` first argument 122 | let pragmaStr = "#.$#(@)" 123 | let localArg = nnkIdentDefs.newTree(ident"p5", ident"P5Instance", newEmptyNode()) 124 | result = newStmtList() 125 | for arg in args: # walk all given procs 126 | doAssert arg.kind == nnkProcDef 127 | result.add arg # global version 128 | # mutable copy and add argument 129 | var marg = arg.copy() 130 | let name = marg.name 131 | let pragmaArg = pragmaStr % [name.strVal] # generate correct importcpp pragma string 132 | let pragma = nnkPragma.newTree(nnkExprColonExpr.newTree(ident"importcpp", newLit(pragmaArg))) 133 | var params = marg.params 134 | # insert param at position 1, pushing all others back 135 | params.insert(1, localArg) 136 | marg.params = params 137 | marg.pragma = pragma 138 | result.add marg 139 | # add the name of this procedure to list of "save to lift" procedures 140 | WrappedProcs.add name 141 | 142 | template instanceImpl(): untyped {.dirty.} = 143 | # bind `replaceCalls`, because the whole AST of `instance` will appear in the 144 | # user's calling scope where `replaceCalls` is not defined 145 | bind replaceCalls 146 | # shadow global setup and draw with local one. It redefines the existing templates by 147 | # a version which sets the given body to a closure assigned to the instance field 148 | # of the same name. In the body of each we will replace calls & dot expressions by 149 | # the corresponding methods of the p5 instance. For symbols / idents we encounter 150 | # we also may replace them if their name matches a field of the `P5Instance` type. 151 | template setup(bd: untyped): untyped = 152 | p5Inst.setup = proc() = 153 | replaceCalls(bd) 154 | 155 | template draw(bd: untyped): untyped = 156 | p5Inst.draw = proc() = 157 | replaceCalls(bd) 158 | 159 | template preload(bd: untyped): untyped = 160 | p5Inst.preload = proc() = 161 | replaceCalls(bd) 162 | ## XXX: define all templates from `p5sugar.nim`? 163 | 164 | template instance*(divName: untyped, body: untyped): untyped = 165 | ## Generates an instance of a `P5Instance` for the "instance mode". The instance 166 | ## will be placed into the HTML
`divName`. The `divName` should be 167 | ## a `string`, but it's `untyped` to avoid early processnig of the two 168 | ## `instance` templates! 169 | # create the closure & generate a new instance from the given closure 170 | instanceImpl() 171 | # Inject the `p5Inst` argument so it's accessible by the user, but 172 | # importantly by the `setup`, `draw` templates 173 | let s = proc(p5Inst {.inject.}: P5Instance) {.closure.} = body 174 | let p5Inst = newInstance(s, divName) 175 | 176 | template instance*(body: untyped): untyped = 177 | ## Generates an instance of a `P5Instance` for the "instance mode". 178 | instanceImpl() 179 | # Inject the `p5Inst` argument so it's accessible by the user, but 180 | # importantly by the `setup`, `draw` templates 181 | let s = proc(p5Inst {.inject.}: P5Instance) {.closure.} = body 182 | let p5Inst = newInstance(s) 183 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | index.nim 5 | 6 | 7 | 8 | 9 | 10 | 11 | 34 | 35 | 36 | 37 |
38 |
39 | 🏡 40 | index.nim 41 | 42 |
43 |
44 |
45 |

nimp5 documentation

46 |
    47 |
  1. 48 |

    Get started: This program creates a canvas that is 400 pixels wide and 400 pixels high, and then starts drawing white circles at the position of the mouse. When a mouse button is pressed, the circle color changes to black.

    49 |
  2. 50 |
  3. 51 |

    Flashing canvas: A canvas that flashes random colors (shows how to change frameRate)

    52 |
  4. 53 |
  5. 54 |

    Easing: Move the mouse across the screen and the symbol will follow.

    55 |
  6. 56 |
  7. 57 |

    Keyboard: Click on the image to give it focus and press the letter keys to create forms in time and space. Each key has a unique identifying number. These numbers can be used to position shapes in space.

    58 |
  8. 59 |
  9. 60 |

    Polygons: What is your favorite? Pentagon? Hexagon? Heptagon? No? What about the icosagon? The polygon() function created for this example is capable of drawing any regular polygon. Also shows possible inputs for colors.

    61 |
  10. 62 |
  11. 63 |

    Okazz 220919a: Art by Okazz, 64 | original at openprocessing.org/sketch/1653811.

    65 |
  12. 66 |
  13. 67 |

    Okazz 221026a: Art by Okazz, 68 | original at openprocessing.org/sketch/1653811.

    69 |
  14. 70 |
  15. 71 |

    Sine Wave: Render a simple sine wave. Original by Daniel Shiffman https://p5js.org/examples/math-sine-wave.html

    72 |
  16. 73 |
  17. 74 |

    Instance mode: This program creates a canvas in a local p5 instance using 75 | instance mode.

    76 |
  18. 77 |
  19. 78 |

    Doorbell: How to load and play a sound (using p5sound). 79 | Adapted from p5.SoundFile reference.

    80 |
  20. 81 |
82 | 96 |
97 |
98 |
99 |
100 | made with nimib 🐳 101 | 102 | 103 |
104 |
105 |
106 |
import std / [os, algorithm, sugar, strformat, strutils, tables]
107 | import nimib except toJson  # we need to remove this from nimib exports! it breaks jsony!
108 | import jsony
109 | 
110 | type
111 |   Entry* = object
112 |     filename: string
113 |     title: string
114 |     description: string
115 |     numbering: int
116 |   Index* = object
117 |     data: seq[Entry]
118 | 
119 | proc sort*(idx: var Index) =
120 |   idx.data.sort do (e, f: Entry) -> int:
121 |     cmp(e.numbering, f.numbering)
122 | 
123 | proc dump*(idx: Index) =
124 |   var idx = idx
125 |   sort(idx)
126 |   writeFile("index.json", jsony.toJson(idx))
127 | 
128 | proc loadIndex*: Index =
129 |   if fileExists("index.json"):
130 |     result = "index.json".readFile.fromJson(Index)
131 |   else:
132 |     result = Index()
133 |   sort result
134 | 
135 | # requires nb: NbDoc
136 | proc addEntry*(nb: var NbDoc, numb: int, titl, desc: string) =
137 |   let e = Entry(numbering: numb, title: titl, description: desc, filename: nb.filename)
138 |   var idx = loadIndex()
139 |   var found = false
140 |   for f in idx.data.mitems:
141 |     if f.filename == e.filename:
142 |       f = e
143 |       found = true
144 |   if not found:
145 |     idx.data.add e
146 |   dump idx
147 |   let text = "## " &  $(e.numbering) & ". " & e.title & "\n" & e.description
148 |   nbText: text
149 | 
150 | when isMainModule:
151 |   nbInit
152 |   nbText: "## nimp5 documentation"
153 |   let idx = loadIndex()
154 |   var
155 |     listEntries = ""
156 |   for entry in idx.data:
157 |     listEntries.add fmt"{entry.numbering}. [{entry.title}]({entry.filename}): {entry.description}" & "\n"
158 |   nbText: listEntries
159 |   nbSave
160 |
173 | -------------------------------------------------------------------------------- /docs/polygons.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | polygons.nim 5 | 6 | 7 | 8 | 9 | 10 | 11 | 34 | 35 | 36 | 37 |
38 |
39 | 🏡 40 | polygons.nim 41 | 42 |
43 |
44 |
45 | 46 |

5. Polygons

47 |

What is your favorite? Pentagon? Hexagon? Heptagon? No? What about the icosagon? The polygon() function created for this example is capable of drawing any regular polygon. Also shows possible inputs for colors.

48 | 49 |
proc polygon(x, y, radius, npoints: PNumber) =
 50 |   let angle = TWO_PI / npoints
 51 |   beginShape()
 52 |   var a = 0.0
 53 |   while a < TWO_PI:
 54 |     let sx = x + cos(a) * radius
 55 |     let sy = y + sin(a) * radius
 56 |     a += angle
 57 |     vertex(sx, sy)
 58 |   endShape(CLOSE)
 59 | 
 60 | setup:
 61 |   createCanvas(720, 400)
 62 | 
 63 | draw:
 64 |   background(102) ## grayscale
 65 | 
 66 |   push()
 67 |   fill(255, 204, 0) ## RGB integer values
 68 |   translate(width * 0.2, height * 0.5)
 69 |   rotate(frameCount / 200.0)
 70 |   polygon(0, 0, 82, 3)
 71 |   pop()
 72 | 
 73 |   push()
 74 |   fill("red") ## Named SVG/CSS color string
 75 |   translate(width * 0.5, height * 0.5)
 76 |   rotate(frameCount / 50.0)
 77 |   polygon(0, 0, 80, 20)
 78 |   pop()
 79 | 
 80 |   push()
 81 |   fill("#fae") ## three-digit (or six-digit) hexadecimal RGB notation
 82 |   translate(width * 0.8, height * 0.5)
 83 |   rotate(frameCount / -100.0)
 84 |   polygon(0, 0, 70, 7)
 85 |   pop()
86 | 163 |
164 |
165 |
166 |
167 | made with nimib 🐳 168 | 169 | 170 |
171 |
172 |
173 |
import nimib, index, p5
174 | nbInit
175 | nbUseP5
176 | nb.addEntry(5, "Polygons", """
177 | What is your favorite? Pentagon? Hexagon? Heptagon? No? What about the icosagon? The polygon() function created for this example is capable of drawing any regular polygon. Also shows [possible inputs for colors](https://p5js.org/reference/#/p5/fill).
178 | """)
179 | nbCodeDisplay(nbJsFromCode):
180 |   proc polygon(x, y, radius, npoints: PNumber) =
181 |     let angle = TWO_PI / npoints
182 |     beginShape()
183 |     var a = 0.0
184 |     while a < TWO_PI:
185 |       let sx = x + cos(a) * radius
186 |       let sy = y + sin(a) * radius
187 |       a += angle
188 |       vertex(sx, sy)
189 |     endShape(CLOSE)
190 | 
191 |   setup:
192 |     createCanvas(720, 400)
193 | 
194 |   draw:
195 |     background(102) ## grayscale
196 | 
197 |     push()
198 |     fill(255, 204, 0) ## RGB integer values
199 |     translate(width * 0.2, height * 0.5)
200 |     rotate(frameCount / 200.0)
201 |     polygon(0, 0, 82, 3)
202 |     pop()
203 | 
204 |     push()
205 |     fill("red") ## Named SVG/CSS color string
206 |     translate(width * 0.5, height * 0.5)
207 |     rotate(frameCount / 50.0)
208 |     polygon(0, 0, 80, 20)
209 |     pop()
210 | 
211 |     push()
212 |     fill("#fae") ## three-digit (or six-digit) hexadecimal RGB notation
213 |     translate(width * 0.8, height * 0.5)
214 |     rotate(frameCount / -100.0)
215 |     polygon(0, 0, 70, 7)
216 |     pop()
217 | 
218 | nbJsShowSource()
219 | nbSave
220 |
233 | -------------------------------------------------------------------------------- /docs/keyboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | keyboard.nim 5 | 6 | 7 | 8 | 9 | 10 | 11 | 34 | 35 | 36 | 37 |
38 |
39 | 🏡 40 | keyboard.nim 41 | 42 |
43 |
44 |
45 | 46 |

4. Keyboard

47 |

Click on the image to give it focus and press the letter keys to create forms in time and space. Each key has a unique identifying number. These numbers can be used to position shapes in space.

48 | 49 |
var rectWidth: float
 50 | 
 51 | setup:
 52 |   createCanvas(720, 400)
 53 |   noStroke()
 54 |   background(230)
 55 |   rectWidth = width / 4
 56 | 
 57 | draw:
 58 |   ## keep draw() here to continue looping while waiting for keys
 59 |   discard
 60 | 
 61 | keyPressed:
 62 |   var keyIndex = -1
 63 |   if $key >= "a" and $key <= "z":
 64 |     keyIndex = ord(key[0]) - ord('a')
 65 |   if keyIndex == -1:
 66 |     ## If it's not a letter key, clear the screen
 67 |     background(230)
 68 |   else:
 69 |     ## It's a letter key, fill a rectangle
 70 |     let
 71 |       randFill_r = floor(random() * 255 + 1)
 72 |       randFill_g = floor(random() * 255 + 1)
 73 |       randFill_b = floor(random() * 255 + 1)
 74 |     fill(randFill_r, randFill_g, randFill_b)
 75 |     let x = map(keyIndex, 0, 25, 0, width - rectWidth)
 76 |     rect(x, 0, rectWidth, height)
77 | 202 |
203 |
204 |
205 |
206 | made with nimib 🐳 207 | 208 | 209 |
210 |
211 |
212 |
import nimib, index, p5
213 | nbInit
214 | nbUseP5
215 | nb.addEntry(4, "Keyboard", """
216 | Click on the image to give it focus and press the letter keys to create forms in time and space. Each key has a unique identifying number. These numbers can be used to position shapes in space.
217 | """)
218 | nbCodeDisplay(nbJsFromCode):
219 |   var rectWidth: float
220 |   
221 |   setup:
222 |     createCanvas(720, 400)
223 |     noStroke()
224 |     background(230)
225 |     rectWidth = width / 4
226 |   
227 |   draw:
228 |     ## keep draw() here to continue looping while waiting for keys
229 |     discard
230 |   
231 |   keyPressed:
232 |     var keyIndex = -1
233 |     if $key >= "a" and $key <= "z":
234 |       keyIndex = ord(key[0]) - ord('a')
235 |     if keyIndex == -1:
236 |       ## If it's not a letter key, clear the screen
237 |       background(230)
238 |     else:
239 |       ## It's a letter key, fill a rectangle
240 |       let
241 |         randFill_r = floor(random() * 255 + 1)
242 |         randFill_g = floor(random() * 255 + 1)
243 |         randFill_b = floor(random() * 255 + 1)
244 |       fill(randFill_r, randFill_g, randFill_b)
245 |       let x = map(keyIndex, 0, 25, 0, width - rectWidth)
246 |       rect(x, 0, rectWidth, height)
247 | 
248 | nbJsShowSource()
249 | nbSave
250 |
263 | -------------------------------------------------------------------------------- /docs/sinewave.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sinewave.nim 5 | 6 | 7 | 8 | 9 | 10 | 11 | 34 | 35 | 36 | 37 |
38 |
39 | 🏡 40 | sinewave.nim 41 | 42 |
43 |
44 |
45 | 46 |

8. Sine Wave

47 |

Render a simple sine wave. Original by Daniel Shiffman https://p5js.org/examples/math-sine-wave.html

48 | 49 |
const xspacing = 16 # Distance between each horizontal location
 50 | const amplitude = 75.0 # Height of wave
 51 | const period = 500.0 # How many pixels before the wave repeats
 52 | 
 53 | var theta: float #  Start angle at 0
 54 | var yvalues: seq[float]
 55 | 
 56 | proc calcWave =
 57 |   theta+=0.02
 58 |   let dx = (TWO_PI / period) * xspacing
 59 |   for x in 0..yvalues.len:
 60 |     yvalues[x] = sin(x*dx+theta)*amplitude
 61 | 
 62 | proc renderWave =
 63 |   noStroke()
 64 |   colorMode(HSB, 100)
 65 |   fill(0)
 66 |   for x in 0..yvalues.len:
 67 |     fill(25+x*2, 100, 50)
 68 |     ellipse(x*xspacing, height/2+yvalues[x], 16, 16)
 69 | 
 70 | setup:
 71 |   theta = 0.0
 72 |   createCanvas(710, 400)
 73 |   let nelements = (width+16)/xspacing
 74 |   yvalues = newSeq[float](nelements.int)
 75 | 
 76 | draw:
 77 |   background(0)
 78 |   calcWave()
 79 |   renderWave()
80 | 315 |
316 |
317 |
318 |
319 | made with nimib 🐳 320 | 321 | 322 |
323 |
324 |
325 |
import nimib, index, p5
326 | nbInit
327 | nbUseP5
328 | nb.addEntry(8, "Sine Wave", """
329 | Render a simple sine wave. Original by Daniel Shiffman <https://p5js.org/examples/math-sine-wave.html>
330 | """)
331 | nbCodeDisplay(nbJsFromCode):
332 |   const xspacing = 16 # Distance between each horizontal location
333 |   const amplitude = 75.0 # Height of wave
334 |   const period = 500.0 # How many pixels before the wave repeats
335 | 
336 |   var theta: float #  Start angle at 0
337 |   var yvalues: seq[float]
338 | 
339 |   proc calcWave =
340 |     theta+=0.02
341 |     let dx = (TWO_PI / period) * xspacing
342 |     for x in 0..yvalues.len:
343 |       yvalues[x] = sin(x*dx+theta)*amplitude
344 | 
345 |   proc renderWave =
346 |     noStroke()
347 |     colorMode(HSB, 100)
348 |     fill(0)
349 |     for x in 0..yvalues.len:
350 |       fill(25+x*2, 100, 50)
351 |       ellipse(x*xspacing, height/2+yvalues[x], 16, 16)
352 | 
353 |   setup:
354 |     theta = 0.0
355 |     createCanvas(710, 400)
356 |     let nelements = (width+16)/xspacing
357 |     yvalues = newSeq[float](nelements.int)
358 | 
359 |   draw:
360 |     background(0)
361 |     calcWave()
362 |     renderWave()
363 | 
364 | nbJsShowSource()
365 | nbSave
366 | 
367 |
380 | -------------------------------------------------------------------------------- /docs/okazz_220919a.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | okazz_220919a.nim 5 | 6 | 7 | 8 | 9 | 10 | 11 | 34 | 35 | 36 | 37 |
38 |
39 | 🏡 40 | okazz_220919a.nim 41 | 42 |
43 |
44 |
45 | 46 |

6. Okazz 220919a

47 |

Art by Okazz, 48 | original at openprocessing.org/sketch/1653811.

49 |

License is CreativeCommons Attribution NonCommercial ShareAlike.

50 |

The original sketch has been ported from p5js to p5nim.

51 | 52 |
var colors = @["#f70640", "#f78e2c", "#fdd903", "#cae509", "#63be93", "#81cfe5", "#299dbf", "#38187d", "#a4459f", "#f654a9", "#2F0A30"];
 53 | 
 54 | type
 55 |   Form = ref object
 56 |     x, y, x0, y0, r0, r, d0, d: float
 57 |     a, t, r1, r2, r3: float 
 58 |     n: int
 59 |     col: string
 60 | 
 61 | var forms: seq[Form]
 62 | 
 63 | proc newForm(x, y: float): Form =
 64 |   result = new Form
 65 |   result.x = x
 66 |   result.y = y
 67 |   result.x0 = x
 68 |   result.y0 = y
 69 |   result.r0 = random(10, 25)
 70 |   result.r = result.r0
 71 |   result.d0 = random(15) * random() + 5
 72 |   result.d = result.d0
 73 |   result.n = int(random(3, 13))
 74 |   result.a = random(100)
 75 |   result.t = random(10000)
 76 |   result.r1 = random(0.01)
 77 |   result.r2 = random(0.01)
 78 |   result.r3 = random(0.01)
 79 |   result.col = random(colors)
 80 | 
 81 | proc show(form: Form) =
 82 |   noStroke()
 83 |   fill(form.col)
 84 |   push()
 85 |   translate(form.x, form.y)
 86 |   rotate(form.a)
 87 |   for i in 0 ..< form.n:
 88 |     let theta = map(i, 0, form.n, 0, TAU)
 89 |     ellipse(form.r * cos(theta), form.r * sin(theta), form.d, form.d)
 90 |   pop()
 91 | 
 92 | proc move(form: Form) =
 93 |   form.t += 1
 94 |   form.a = TAU * sin(form.t * form.r1);
 95 |   form.r = form.r0 * sin(form.t * form.r2);
 96 |   form.d = form.d0 * sin(form.t * form.r3);
 97 |   form.x += 0.5;
 98 |   form.y -= 0.5;
 99 |   if form.x > width:
100 |     form.x = 0
101 |   if form.y < 0:
102 |     form.y = height
103 | 
104 | setup:
105 |   createCanvas(900, 900)
106 | 
107 |   var c = 18
108 |   var w = width / c
109 |   for i in 0 ..< c:
110 |     for j in 0 ..< c:
111 |       let x = i * w + w / 2;
112 |       let y = j * w + w / 2;
113 |       if ((i + j) mod 2 == 0):
114 |         for k in 0 ..< 5:
115 |           forms.add(newForm(x, y))
116 |   background(0)
117 | 
118 | draw:
119 | 
120 |   translate(width / 2, height / 2)
121 |   scale(1.1)
122 |   translate(-width / 2, -height / 2)
123 |   background(255)
124 |   for f in forms:
125 |     f.show()
126 |     f.move()
127 | 128 |

press 's' to export a 2 second gif

129 | 613 |
614 |
615 |
616 |
617 | made with nimib 🐳 618 | 619 | 620 |
621 |
622 |
623 |
import nimib, index, p5
624 | nbInit
625 | nbUseP5
626 | nb.addEntry(6, "Okazz 220919a", """
627 | Art by [Okazz](https://openprocessing.org/user/128718?view=sketches&o=31),
628 | original at [openprocessing.org/sketch/1653811](https://openprocessing.org/sketch/1653811).
629 | """)
630 | nbText: """
631 | License is [CreativeCommons Attribution NonCommercial ShareAlike](https://creativecommons.org/licenses/by-nc-sa/3.0/).
632 | 
633 | The original sketch has been ported from p5js to p5nim.
634 | """
635 | # maybe this will be used to train an automatic translator
636 | let sourceOriginalJs = """
637 | let forms = [];
638 | let colors = ['#f70640', '#f78e2c', '#fdd903', '#cae509', '#63be93', '#81cfe5', '#299dbf', '#38187d', '#a4459f', '#f654a9', '#2F0A30'];
639 | 
640 | function setup() {
641 | 	createCanvas(900, 900);
642 | 
643 | 	let c = 18;
644 | 	let w = width / c;
645 | 	for (let i = 0; i < c; i++) {
646 | 		for (let j = 0; j < c; j++) {
647 | 			let x = i * w + w / 2;
648 | 			let y = j * w + w / 2;
649 | 			if ((i + j) % 2 == 0) {
650 | 				for (let k = 0; k < 5; k++) {
651 | 					forms.push(new Form(x, y));
652 | 				}
653 | 			}
654 | 		}
655 | 	}
656 | 	background(0);
657 | 
658 | }
659 | 
660 | function draw() {
661 | 
662 | 	translate(width / 2, height / 2);
663 | 	scale(1.1);
664 | 	translate(-width / 2, -height / 2);
665 | 	background(255);
666 | 	for (let i of forms) {
667 | 		i.show();
668 | 		i.move();
669 | 	}
670 | }
671 | 
672 | class Form {
673 | 	constructor(x, y) {
674 | 		this.x = x;
675 | 		this.y = y;
676 | 		this.x0 = x;
677 | 		this.y0 = y;
678 | 		this.r0 = random(10, 25);
679 | 		this.r = this.r0;
680 | 		this.d0 = random(15) * random() + 5;
681 | 		this.d = this.d0;
682 | 		this.n = int(random(3, 13));
683 | 		this.a = random(100);
684 | 		this.t = random(10000);
685 | 		this.r1 = random(0.01);
686 | 		this.r2 = random(0.01);
687 | 		this.r3 = random(0.01);
688 | 		this.col = color(random(colors));
689 | 	}
690 | 
691 | 	show() {
692 | 		noStroke();
693 | 		fill(this.col);
694 | 		push();
695 | 		translate(this.x, this.y);
696 | 		rotate(this.a);
697 | 		for (let i = 0; i < this.n; i++) {
698 | 			let theta = map(i, 0, this.n, 0, TAU);
699 | 			ellipse(this.r * cos(theta), this.r * sin(theta), this.d, this.d);
700 | 		}
701 | 		pop();
702 | 	}
703 | 
704 | 	move() {
705 | 		this.t++;
706 | 		this.a = TAU * sin(this.t * this.r1);
707 | 		this.r = this.r0 * sin(this.t * this.r2);
708 | 		this.d = this.d0 * sin(this.t * this.r3);
709 | 		this.x += 0.5;
710 | 		this.y -= 0.5;
711 | 		if (this.x > width) {
712 | 			this.x = 0;
713 | 		}
714 | 		if (this.y < 0) {
715 | 			this.y = height;
716 | 		}
717 | 	}
718 | }
719 | """
720 | 
721 | nbCodeDisplay(nbJsFromCode):
722 |   var colors = @["#f70640", "#f78e2c", "#fdd903", "#cae509", "#63be93", "#81cfe5", "#299dbf", "#38187d", "#a4459f", "#f654a9", "#2F0A30"];
723 | 
724 |   type
725 |     Form = ref object
726 |       x, y, x0, y0, r0, r, d0, d: float
727 |       a, t, r1, r2, r3: float 
728 |       n: int
729 |       col: string
730 | 
731 |   var forms: seq[Form]
732 | 
733 |   proc newForm(x, y: float): Form =
734 |     result = new Form
735 |     result.x = x
736 |     result.y = y
737 |     result.x0 = x
738 |     result.y0 = y
739 |     result.r0 = random(10, 25)
740 |     result.r = result.r0
741 |     result.d0 = random(15) * random() + 5
742 |     result.d = result.d0
743 |     result.n = int(random(3, 13))
744 |     result.a = random(100)
745 |     result.t = random(10000)
746 |     result.r1 = random(0.01)
747 |     result.r2 = random(0.01)
748 |     result.r3 = random(0.01)
749 |     result.col = random(colors)
750 | 
751 |   proc show(form: Form) =
752 |     noStroke()
753 |     fill(form.col)
754 |     push()
755 |     translate(form.x, form.y)
756 |     rotate(form.a)
757 |     for i in 0 ..< form.n:
758 |       let theta = map(i, 0, form.n, 0, TAU)
759 |       ellipse(form.r * cos(theta), form.r * sin(theta), form.d, form.d)
760 |     pop()
761 | 
762 |   proc move(form: Form) =
763 |     form.t += 1
764 |     form.a = TAU * sin(form.t * form.r1);
765 |     form.r = form.r0 * sin(form.t * form.r2);
766 |     form.d = form.d0 * sin(form.t * form.r3);
767 |     form.x += 0.5;
768 |     form.y -= 0.5;
769 |     if form.x > width:
770 |       form.x = 0
771 |     if form.y < 0:
772 |       form.y = height
773 | 
774 |   setup:
775 |     createCanvas(900, 900)
776 | 
777 |     var c = 18
778 |     var w = width / c
779 |     for i in 0 ..< c:
780 |       for j in 0 ..< c:
781 |         let x = i * w + w / 2;
782 |         let y = j * w + w / 2;
783 |         if ((i + j) mod 2 == 0):
784 |           for k in 0 ..< 5:
785 |             forms.add(newForm(x, y))
786 |     background(0)
787 | 
788 |   draw:
789 | 
790 |     translate(width / 2, height / 2)
791 |     scale(1.1)
792 |     translate(-width / 2, -height / 2)
793 |     background(255)
794 |     for f in forms:
795 |       f.show()
796 |       f.move()
797 | 
798 | nbJsShowSource()
799 | nbJsFromCode():
800 |   keyPressed:
801 |     if $key == "s":
802 |       echo "saving gif okazz_229019a"
803 |       saveGif(cstring("okazz_229019a"), 2)
804 | nbText: "press 's' to export a 2 second gif"
805 | nbSave
806 |
819 | -------------------------------------------------------------------------------- /docs/okazz_221026a.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | okazz_221026a.nim 5 | 6 | 7 | 8 | 9 | 10 | 11 | 34 | 35 | 36 | 37 |
38 |
39 | 🏡 40 | okazz_221026a.nim 41 | 42 |
43 |
44 |
45 | 46 |

7. Okazz 221026a

47 |

Art by Okazz, 48 | original at openprocessing.org/sketch/1653811.

49 |

License is CreativeCommons Attribution NonCommercial ShareAlike.

50 |

The original sketch has been ported from p5js to p5nim.

51 |
import
 52 |   std / [random, sequtils]
 53 | 
 54 | import std / math except sqrt
 55 | 
 56 | var palettes = @[@["#6b2d5c", "#f0386b", "#ff5376", "#e391db", "#f8c0c8"],
 57 |                  @["#ffe957", "#eec170", "#f2a65a", "#f58549", "#ff6c3f"],
 58 |                  @["#073b3a", "#0b6e4f", "#08a045", "#6bbf59", "#71deb2"],
 59 |                  @["#c52233", "#a51c30", "#a7333f", "#74121d", "#580c1f"],
 60 |                  @["#03045e", "#0077b6", "#00b4d8", "#90e0ef", "#caf0f8"],
 61 |                  @["#f8f9fa", "#dee2e6", "#adb5bd", "#495057", "#212529"]]
 62 | let SEED = math.floor(random.rand(1.0) * 1000000)
 63 | type
 64 |   Form = ref object
 65 |     x, y, w: float
 66 |     palettes: seq[seq[string]]
 67 |     num: int
 68 |     colors: seq[string]
 69 | 
 70 | var forms: seq[Form]
 71 | proc newForm(x, y, w: float): Form =
 72 |   result = new Form
 73 |   result.x = x
 74 |   result.y = y
 75 |   result.w = w
 76 |   result.palettes = newSeqWith(0, newSeq[string]())
 77 |   for i in 0 ..< len(palettes):
 78 |     var c = palettes[i]
 79 |     shuffle(c)
 80 |     result.palettes.add(c)
 81 |   result.num = 5
 82 |   result.colors = newSeq[string]()
 83 | 
 84 | proc show(form: Form) =
 85 |   let d = dist(width / 2, height / 2, form.x, form.y)
 86 |   let c = int(map(d, sqrt(sq(width / 2) + sq(height / 2)), 0, 0, palettes.len) +
 87 |       frameCount * 0.02)
 88 |   form.colors = form.palettes[c mod palettes.len]
 89 |   for i in 0 ..< form.num:
 90 |     let r = map(float(i), 0, form.num, 0, form.w / 2)
 91 |     let ww = map(float(i), 0, form.num, form.w, 0)
 92 |     let col = form.colors[i mod form.colors.len]
 93 |     fill(col)
 94 |     rect(form.x, form.y, ww, ww, r)
 95 | 
 96 | setup:
 97 |   createCanvas(900, 900)
 98 |   rectMode(CENTER)
 99 |   let seg = 25
100 |   let w = width / seg
101 |   for i in 0 ..< seg:
102 |     for j in 0 ..< seg:
103 |       let x = i * w + w / 2
104 |       let y = j * w + w / 2
105 |       forms.add(newForm(x, y, w))
106 | draw:
107 |   background(255)
108 |   for f in forms:
109 |     f.show()
110 | 111 |

press 's' to export a 5 second gif

112 | 764 |
765 |
766 |
767 |
768 | made with nimib 🐳 769 | 770 | 771 |
772 |
773 |
774 |
import nimib, index, p5
775 | nbInit
776 | nbUseP5
777 | nb.addEntry(7, "Okazz 221026a", """
778 | Art by [Okazz](https://openprocessing.org/user/128718?view=sketches&o=31),
779 | original at [openprocessing.org/sketch/1653811](https://openprocessing.org/sketch/1711659).
780 | """)
781 | nbText: """
782 | License is [CreativeCommons Attribution NonCommercial ShareAlike](https://creativecommons.org/licenses/by-nc-sa/3.0/).
783 | 
784 | The original sketch has been ported from p5js to p5nim.
785 | """
786 | # maybe this will be used to train an automatic translator
787 | let sourceOriginalJs = """
788 | let palettes = [
789 |   ['#6b2d5c', '#f0386b', '#ff5376', '#e391db', '#f8c0c8'],
790 |   ['#ffe957', '#eec170', '#f2a65a', '#f58549', '#ff6c3f'],
791 |   ['#073b3a', '#0b6e4f', '#08a045', '#6bbf59', '#71deb2'],
792 |   ['#c52233', '#a51c30', '#a7333f', '#74121d', '#580c1f'],
793 |   ['#03045e', '#0077b6', '#00b4d8', '#90e0ef', '#caf0f8'],
794 |   ['#f8f9fa', '#dee2e6', '#adb5bd', '#495057', '#212529']
795 | ];
796 | let SEED = Math.floor(Math.random() * 1000000);
797 | let forms = [];
798 | 
799 | function setup() {
800 |   createCanvas(900, 900);
801 |   rectMode(CENTER);
802 |   // randomSeed(SEED)
803 |   //   background(255);
804 |   //   forms.push(new Form(width/2, height/2, 100));
805 |   let seg = 25;
806 |   let w = width / seg;
807 |   for (let i = 0; i < seg; i++) {
808 |     for (let j = 0; j < seg; j++) {
809 |       let x = i * w + w / 2;
810 |       let y = j * w + w / 2;
811 |       forms.push(new Form(x, y, w));
812 |     }
813 |   }
814 | }
815 | 
816 | function draw() {
817 |   background(255);
818 |   for (let f of forms) {
819 |     f.show();
820 |     f.move();
821 |   }
822 |   // this.x = x;
823 | 
824 |   // randomSeed(SEED)
825 |   // 
826 | }
827 | 
828 | function form(x, y, w) {
829 |   // let num = int(random());
830 |   let num = int(5);
831 |   let d = dist(width / 2, height / 2, x, y);
832 |   let c = int(map(d, 0, sqrt(sq(width / 2) + sq(height / 2)), 0, palettes.length * 2) + frameCount * 0.5);
833 |   let colors = palettes[c % palettes.length];
834 | 
835 |   // shuffle(colors, true);
836 |   for (let i = 0; i < num; i++) {
837 |     let r = map(i, 0, num, 0, w / 2);
838 |     let ww = map(i, 0, num, w, 0);
839 |     let col = colors[i % colors.length];
840 |     fill(col);
841 |     rect(x, y, ww, ww, r);
842 |   }
843 | }
844 | 
845 | class Form {
846 |   constructor(x, y, w) {
847 |     this.x = x;
848 |     this.y = y;
849 |     this.w = w;
850 |     this.palettes = [];
851 |     for (let i = 0; i < palettes.length; i++) {
852 |       let c = []
853 |       shuffle(palettes[i], true);
854 |       for (let j = 0; j < 5; j++) {
855 |         c.push(palettes[i][j]);
856 |       }
857 |       this.palettes.push(c);
858 |     }
859 |     this.num = 5;
860 |     this.colors = [];
861 |   }
862 | 
863 |   show() {
864 |     let d = dist(width / 2, height / 2, this.x, this.y);
865 |     let c = int(map(d, sqrt(sq(width / 2) + sq(height / 2)), 0, 0, palettes.length) + frameCount * 0.02);
866 |     this.colors = this.palettes[c % palettes.length];
867 |     for (let i = 0; i < this.num; i++) {
868 |       let r = map(i, 0, this.num, 0, this.w / 2);
869 |       let ww = map(i, 0, this.num, this.w, 0);
870 |       let col = this.colors[i % this.colors.length];
871 |       fill(col);
872 |       rect(this.x, this.y, ww, ww, r);
873 |     }
874 |   }
875 | 
876 |   move() {
877 | 
878 |   }
879 | 
880 | }
881 | """
882 | 
883 | nbJsFromCode:
884 |   import std / [random, sequtils]
885 |   import std / math except sqrt
886 | 
887 |   var palettes = @[
888 |     @["#6b2d5c", "#f0386b", "#ff5376", "#e391db", "#f8c0c8"],
889 |     @["#ffe957", "#eec170", "#f2a65a", "#f58549", "#ff6c3f"],
890 |     @["#073b3a", "#0b6e4f", "#08a045", "#6bbf59", "#71deb2"],
891 |     @["#c52233", "#a51c30", "#a7333f", "#74121d", "#580c1f"],
892 |     @["#03045e", "#0077b6", "#00b4d8", "#90e0ef", "#caf0f8"],
893 |     @["#f8f9fa", "#dee2e6", "#adb5bd", "#495057", "#212529"]
894 |   ]
895 |   let SEED = math.floor(random.rand(1.0) * 1000000) # using p5js.floor throws an error in console
896 | 
897 |   type
898 |     Form = ref object
899 |       x, y, w: float
900 |       palettes: seq[seq[string]] 
901 |       num: int
902 |       colors: seq[string]
903 | 
904 |   var forms: seq[Form]
905 | 
906 |   proc newForm(x, y, w: float): Form =
907 |     result = new Form
908 |     result.x = x
909 |     result.y = y
910 |     result.w = w
911 |     result.palettes = newSeqWith(0, newSeq[string]())
912 |     for i in 0 ..< len(palettes):
913 |       var c = palettes[i]
914 |       shuffle(c)
915 |       result.palettes.add(c)
916 |     result.num = 5
917 |     result.colors = newSeq[string]()
918 | 
919 |   proc show(form: Form) =
920 |     let d = dist(width / 2, height / 2, form.x, form.y)
921 |     let c = int(map(d, sqrt(sq(width / 2) + sq(height / 2)), 0, 0, palettes.len) + frameCount * 0.02)
922 |     form.colors = form.palettes[c mod palettes.len]
923 |     for i in 0 ..< form.num:
924 |       let r = map(float(i), 0, form.num, 0, form.w / 2)
925 |       let ww = map(float(i), 0, form.num, form.w, 0)
926 |       let col = form.colors[i mod form.colors.len]
927 |       fill(col)
928 |       rect(form.x, form.y, ww, ww, r)
929 | 
930 |   # no move needed
931 | 
932 |   setup:
933 |     createCanvas(900, 900)
934 |     rectMode(CENTER)
935 |     # randomSeed(SEED)
936 |     #   background(255)
937 |     #   forms.add(newForm(width/2, height/2, 100))
938 |     let seg = 25
939 |     let w = width / seg
940 |     for i in 0 ..< seg:
941 |       for j in 0 ..< seg:
942 |         let x = i * w + w / 2
943 |         let y = j * w + w / 2
944 |         forms.add(newForm(x, y, w))       
945 | 
946 |   draw:
947 |     background(255)
948 |     for f in forms:
949 |       f.show()
950 |       #f.move()
951 | 
952 | nbJsShowSource()
953 | nbJsFromCode():
954 |   keyPressed:
955 |     if $key == "s":
956 |       echo "saving gif okazz_221026a"
957 |       saveGif(cstring("okazz_221026a"), 5)
958 | nbText: "press 's' to export a 5 second gif"
959 | nbSave
960 |
973 | --------------------------------------------------------------------------------