Skip to content

Commit bbbed16

Browse files
committed
support for preloading fonts and images
1 parent 31e5c44 commit bbbed16

15 files changed

+650
-62
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
/build
44
erl_crash.dump
55
/**/dist/*.js
6+
.DS_Store
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: test
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
- main
8+
pull_request:
9+
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: erlef/setup-beam@v1
16+
with:
17+
otp-version: "26.0.2"
18+
gleam-version: "1.1.0"
19+
rebar3-version: "3"
20+
# elixir-version: "1.15.4"
21+
- run: gleam deps download
22+
- run: gleam test
23+
- run: gleam format --check src test

examples/loading_assets/.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.beam
2+
*.ez
3+
/build
4+
erl_crash.dump

examples/loading_assets/README.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# loading_assets
2+
3+
Example rendering a hello message to the screen with 3 different fonts loaded in 3 different ways. The first example uses a system default font requiring no loading. The second example uses a font loaded onto the page via an html link tag. The third example uses a font loaded in the preload function.
4+
5+
## Bundling the app
6+
7+
This example uses [esgleam](https://hexdocs.pm/esgleam/) to bundle the main gleam module for use in a static site.
8+
9+
To install esbuild run the following command. You should only need to run it once.
10+
11+
```bash
12+
gleam run -m esgleam/install
13+
```
14+
15+
To bundle the app run the following command after making code changes.
16+
17+
```bash
18+
gleam run -m esgleam/bundle
19+
```
20+
21+
## Serving the app
22+
23+
You can use any static site server to host the app but for development you can continue to use esgleam by running
24+
25+
```bash
26+
gleam run -m esgleam/serve
27+
```
73.4 KB
Binary file not shown.
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Loading Assets</title>
8+
<link
9+
href="http://fonts.googleapis.com/css?family=Walter+Turncoat&.css"
10+
rel="stylesheet"
11+
/>
12+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/p5.js"></script>
13+
<script type="module">
14+
import { main } from "./loading_assets.js";
15+
main();
16+
</script>
17+
</head>
18+
<body></body>
19+
</html>

examples/loading_assets/gleam.toml

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name = "loading_assets"
2+
version = "1.0.0"
3+
target = "javascript"
4+
5+
# Fill out these fields if you intend to generate HTML documentation or publish
6+
# your project to the Hex package manager.
7+
#
8+
# description = ""
9+
# licences = ["Apache-2.0"]
10+
# repository = { type = "github", user = "username", repo = "project" }
11+
# links = [{ title = "Website", href = "https://gleam.run" }]
12+
#
13+
# For a full reference of all the available options, you can have a look at
14+
# https://gleam.run/writing-gleam/gleam-toml/.
15+
16+
[dependencies]
17+
gleam_stdlib = "~> 0.34 or ~> 1.0"
18+
p5js_gleam = { path = "../../" }
19+
20+
[dev-dependencies]
21+
gleeunit = "~> 1.0"
22+
esgleam = "~> 0.6"

examples/loading_assets/manifest.toml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# This file was generated by Gleam
2+
# You typically do not need to edit this file
3+
4+
packages = [
5+
{ name = "esgleam", version = "0.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "simplifile"], otp_app = "esgleam", source = "hex", outer_checksum = "571D149ACFB7C7A81CF8C7A70ECFDA5A084ADBF706962353C0DA85787CB4DD24" },
6+
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
7+
{ name = "gleam_stdlib", version = "0.37.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "5398BD6C2ABA17338F676F42F404B9B7BABE1C8DC7380031ACB05BBE1BCF3742" },
8+
{ name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
9+
{ name = "p5js_gleam", version = "2.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "../.." },
10+
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
11+
]
12+
13+
[requirements]
14+
esgleam = { version = "~> 0.6" }
15+
gleam_stdlib = { version = "~> 0.34 or ~> 1.0" }
16+
gleeunit = { version = "~> 1.0" }
17+
p5js_gleam = { path = "../../" }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import p5js_gleam.{type Assets, type P5}
2+
import p5js_gleam/bindings as p5
3+
4+
fn preload(p: P5) -> Assets {
5+
let loaded_font = p5.load_font(p, "./Lato-Regular.ttf")
6+
7+
p5js_gleam.initialize_assets()
8+
|> p5js_gleam.insert_font("Lato", loaded_font)
9+
}
10+
11+
fn setup(p: P5) -> String {
12+
p5.create_canvas(p, 800.0, 600.0)
13+
"Hello, world!"
14+
}
15+
16+
fn draw(p: P5, state: String, assets: Assets) {
17+
p5.background(p, "#ffffff")
18+
p5.fill(p, "#000000")
19+
p5.text_size(p, 48)
20+
p5.text_font_from_string(p, "serif")
21+
p5.text(p, state, 400.0, 100.0)
22+
p5.text_font_from_string(p, "Walter Turncoat")
23+
p5.text(p, state, 400.0, 200.0)
24+
let assert Ok(font) = p5js_gleam.get_font(assets, "Lato")
25+
p5.text_font(p, font)
26+
p5.text(p, state, 400.0, 300.0)
27+
}
28+
29+
pub fn main() {
30+
p5js_gleam.create_sketch_with_preloading(
31+
preload: preload,
32+
init: setup,
33+
draw: draw,
34+
)
35+
|> p5.start_sketch
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import gleeunit
2+
import gleeunit/should
3+
4+
pub fn main() {
5+
gleeunit.main()
6+
}
7+
8+
// gleeunit test functions end in `_test`
9+
pub fn hello_world_test() {
10+
1
11+
|> should.equal(1)
12+
}

gleam.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name = "p5js_gleam"
2-
version = "2.0.2"
2+
version = "2.1.0"
33
target = "javascript"
44

55
description = "A simple game library providing p5.js bindings for Gleam in a functional style to make basic games and animations. Heavily inspired by the Racket library 2htdp/universe"

scripts/generate_p5.ts

+121-52
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,123 @@
1-
const elements = {
2-
createCanvas: ["width: Float", "height: Float"],
3-
text: ["text: String", "bottom_corner_x: Float", "bottom_corner_y: Float"],
4-
textSize: ["size: Int"],
5-
background: ["color: String"],
6-
ellipse: [
7-
"x_center: Float",
8-
"y_center: Float",
9-
"width: Float",
10-
"height: Float",
11-
],
12-
circle: ["x_center: Float", "y_center: Float", "radius: Float"],
13-
rect: [
14-
"top_left_x: Float",
15-
"top_left_y: Float",
16-
"width: Float",
17-
"height: Float",
18-
],
19-
square: ["top_left_x: Float", "top_left_y: Float", "side_length: Float"],
20-
line: [
21-
"point1_x: Float",
22-
"point1_y: Float",
23-
"point2_x: Float",
24-
"point2_y: Float",
25-
],
26-
quad: [
27-
"p1_x: Float",
28-
"p1_y: Float",
29-
"p2_x: Float",
30-
"p2_y: Float",
31-
"p3_x: Float",
32-
"p3_y: Float",
33-
"p4_x: Float",
34-
"p4_y: Float",
35-
],
36-
fill: ["color_hex: String"],
37-
stroke: ["color_hex: String"],
38-
noStroke: [],
39-
strokeWeight: ["weight: Int"],
1+
interface Binding {
2+
arguments: string[];
3+
returnType?: string; // defaults to P5
4+
bindingName?: string; // if not provided, the name of the function will be used
5+
}
6+
7+
const elements: Record<string, Binding> = {
8+
createCanvas: { arguments: ["width: Float", "height: Float"] },
9+
text: {
10+
arguments: [
11+
"text: String",
12+
"bottom_corner_x: Float",
13+
"bottom_corner_y: Float",
14+
],
15+
},
16+
textFont: { arguments: ["font: P5Font"] },
17+
textFontFromString: { arguments: ["font: String"], bindingName: "textFont" },
18+
textSize: { arguments: ["size: Int"] },
19+
background: { arguments: ["color: String"] },
20+
ellipse: {
21+
arguments: [
22+
"x_center: Float",
23+
"y_center: Float",
24+
"width: Float",
25+
"height: Float",
26+
],
27+
},
28+
circle: {
29+
arguments: ["x_center: Float", "y_center: Float", "radius: Float"],
30+
},
31+
rect: {
32+
arguments: [
33+
"top_left_x: Float",
34+
"top_left_y: Float",
35+
"width: Float",
36+
"height: Float",
37+
],
38+
},
39+
square: {
40+
arguments: ["top_left_x: Float", "top_left_y: Float", "side_length: Float"],
41+
},
42+
line: {
43+
arguments: [
44+
"point1_x: Float",
45+
"point1_y: Float",
46+
"point2_x: Float",
47+
"point2_y: Float",
48+
],
49+
},
50+
quad: {
51+
arguments: [
52+
"p1_x: Float",
53+
"p1_y: Float",
54+
"p2_x: Float",
55+
"p2_y: Float",
56+
"p3_x: Float",
57+
"p3_y: Float",
58+
"p4_x: Float",
59+
"p4_y: Float",
60+
],
61+
},
62+
image: {
63+
arguments: [
64+
"image: P5Image",
65+
"top_left_x: Float",
66+
"top_left_y: Float",
67+
"width: Float",
68+
"height: Float",
69+
],
70+
},
71+
fill: { arguments: ["color_hex: String"] },
72+
noFill: { arguments: [] },
73+
stroke: { arguments: ["color_hex: String"] },
74+
noStroke: { arguments: [] },
75+
strokeWeight: { arguments: ["weight: Int"] },
76+
erase: { arguments: ["strength: Int", "edge_strength: Int"] },
77+
noErase: { arguments: [] },
78+
loadImage: {
79+
arguments: ["path: String"],
80+
returnType: "P5Image",
81+
},
82+
loadFont: {
83+
arguments: ["path: String"],
84+
returnType: "P5Font",
85+
},
4086
};
4187

4288
const camelToSnakeCase = (str: string) =>
4389
str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
4490

45-
const js = (name: string) =>
46-
`export function ${name}(p, ...args) {
91+
const jsReturningP5 = (name: string) => `
4792
p.${name}(...args);
48-
return p;
93+
return p;`;
94+
const jsReturningOriginal = (name: string) => `
95+
return p.${name}(...args);`;
96+
97+
const js = (name: string, bindingName: string, isReturningOriginal: boolean) =>
98+
`export function ${name}(p, ...args) {${
99+
isReturningOriginal
100+
? jsReturningOriginal(bindingName)
101+
: jsReturningP5(bindingName)
102+
}
49103
}
50104
51105
`;
52106

53-
const gleam = (name: string, args: string[]) =>
54-
`/// A binding to the p5.js [\`${name}\`](https://p5js.org/reference/#/p5/${name}) function. Takes a p5 instance and the function's arguments and returns the p5 instance.
107+
const gleam = (name: string, binding: Binding) =>
108+
`/// A binding to the p5.js [\`${
109+
binding.bindingName ?? name
110+
}\`](https://p5js.org/reference/#/p5/${
111+
binding.bindingName ?? name
112+
}) function. Takes a p5 instance and the function's arguments and returns the p5 instance.
55113
@external(javascript, "../p5js_ffi.mjs", "${name}")
56-
pub fn ${camelToSnakeCase(name)}(p: P5${args.length ? ", " : ""}${args.join(
57-
", ",
58-
)}) -> P5
114+
pub fn ${camelToSnakeCase(name)}(p: P5${
115+
binding.arguments.length ? ", " : ""
116+
}${binding.arguments.join(", ")}) -> ${binding.returnType ?? "P5"}
59117
60118
`;
61119

62-
let outGleam = `import p5js_gleam.{type P5, type SketchConfig}
120+
let outGleam = `import p5js_gleam.{type P5, type P5Image, type P5Font, type SketchConfig}
63121
64122
/// Starts a p5.js sketch with the given configuration.
65123
@external(javascript, "../p5js_ffi.mjs", "startSketch")
@@ -71,13 +129,20 @@ let outJs = `import { is_some, unwrap } from "../gleam_stdlib/gleam/option.mjs";
71129
72130
export const startSketch = (config) => {
73131
let model;
132+
let assets;
74133
new p5(function (p) {
134+
if (config.preload) {
135+
p.preload = function () {
136+
assets = config.preload(p);
137+
};
138+
}
139+
75140
p.setup = function () {
76141
model = config.init(p);
77142
};
78143
79144
p.draw = function () {
80-
config.draw(p, model);
145+
config.draw(p, model, assets);
81146
if (is_some(config.on_tick)) {
82147
model = unwrap(config.on_tick)(model);
83148
}
@@ -111,9 +176,13 @@ export const startSketch = (config) => {
111176
112177
`;
113178

114-
for (const element of Object.entries(elements)) {
115-
outGleam += gleam(element[0], element[1]);
116-
outJs += js(element[0]);
179+
for (const [elementName, elementBinding] of Object.entries(elements)) {
180+
outGleam += gleam(elementName, elementBinding);
181+
outJs += js(
182+
elementName,
183+
elementBinding.bindingName ?? elementName,
184+
Boolean(elementBinding.returnType)
185+
);
117186
}
118187

119188
await Promise.all([

0 commit comments

Comments
 (0)