├── .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 |
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.
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 |
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).
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 |
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 |
50 |
51 |
Flashing canvas: A canvas that flashes random colors (shows how to change frameRate)
52 |
53 |
54 |
Easing: Move the mouse across the screen and the symbol will follow.
55 |
56 |
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 |
59 |
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.
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.
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 |
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