Getting Started #
This guide walks you creating a very small Budo app that displays a moving circle that reacts to mouse and touch gestures.
After you are done with it, the next sections of the documentation will explain each aspect of Budo in a deeper way. You can also look at the demos code.
App creation #
We will ask the command to initialize a new directory for a Budo application:
budo init my-budo-appIt should create a my-budo-app directory with a few files in it.
Particularly, the main.js file should look like this:
function frame(timestamp) {
sys.canvas.clear("#222831");
sys.canvas.setFillColor("#eeeeee");
sys.canvas.drawText("Hello, Budo!", 24, 48, 28);
sys.animation.requestFrame(frame);
}
sys.animation.requestFrame(frame);The code is simple: on every frame, it clears the canvas and draws the string "Hello, Budo!". The sys.animation.requestFrame(frame) line asks Budo to call your frame function on the next display tick. That's how applications can animate the screen.
Run your application:
budo run my-budo-appA window with the text should appear on your screen.
Draw a circle #
Now we will draw a circle at the center of the screen by adding calls to sys.window.getWidth(), sys.window.getHeight() and sys.canvas.drawCircle(...).
function frame(timestamp) {
const width = sys.window.getWidth();
const height = sys.window.getHeight();
sys.canvas.clear("#222831");
sys.canvas.setFillColor("#eeeeee");
sys.canvas.drawText("Hello, Budo!", 24, 48, 28);
sys.canvas.setFillColor('#7c6ff7');
sys.canvas.drawCircle(width * 0.5, height * 0.5, 40);
sys.animation.requestFrame(frame);
}
sys.animation.requestFrame(frame);Animate something #
Use the frame timestamp to move the circle.
Change the code to this:
function frame(timestamp) {
const width = sys.window.getWidth();
const height = sys.window.getHeight();
sys.canvas.clear("#222831");
sys.canvas.setFillColor("#eeeeee");
sys.canvas.drawText("Hello, Budo!", 24, 48, 28);
const seconds = timestamp / 1000;
const x = width * 0.5 + Math.cos(seconds) * 90;
const y = height * 0.5 + Math.sin(seconds) * 90;
sys.canvas.setFillColor('#7c6ff7');
sys.canvas.drawCircle(x, y, 40);
sys.animation.requestFrame(frame);
}
sys.animation.requestFrame(frame);Now the circle orbits around the center of the window.
Add pointer input #
Read input once near the start of the frame.
After width and height, add this:
const input = sys.input.get();Then replace the x and y lines with this:
let x = width * 0.5 + Math.cos(seconds) * 90;
let y = height * 0.5 + Math.sin(seconds) * 90;
if (input.pointer.down) {
x = input.pointer.x;
y = input.pointer.y;
}When the mouse button or primary touch is down, the circle follows the pointer. Otherwise it keeps orbiting.
Here is the full version so far:
function frame(timestamp) {
const width = sys.window.getWidth();
const height = sys.window.getHeight();
const input = sys.input.get();
sys.canvas.clear("#222831");
sys.canvas.setFillColor("#eeeeee");
sys.canvas.drawText("Hello, Budo!", 24, 48, 28);
const seconds = timestamp / 1000;
let x = width * 0.5 + Math.cos(seconds) * 90;
let y = height * 0.5 + Math.sin(seconds) * 90;
if (input.pointer.down) {
x = input.pointer.x;
y = input.pointer.y;
}
sys.canvas.setFillColor('#7c6ff7');
sys.canvas.drawCircle(x, y, 40);
sys.animation.requestFrame(frame);
}
sys.animation.requestFrame(frame);Split the code when it grows #
JavaScript entrypoints may use ES modules. Keep the first app in one file, then split helpers when the file starts to feel crowded.
Create color.js:
export function pulseColor(seconds) {
const value = Math.floor(160 + Math.sin(seconds * 2) * 80);
return (0xff << 24) | (value << 16) | (120 << 8) | 255;
}Import it from main.js:
import { pulseColor } from './color.js';
// Later, inside frame:
sys.canvas.setFillColor(pulseColor(seconds));Relative imports resolve from the importing file. Bare file names resolve from the project root. Paths must stay inside the project directory.
Try TypeScript later #
Budo can load main.ts too. It strips lightweight TypeScript syntax before running the file through QuickJS.
let radius: number = 32;
function clamp(value: number, min: number, max: number): number {
return Math.max(min, Math.min(max, value));
}This is useful for editor help, but it is not a full TypeScript build step. Budo does not type-check, bundle modules, or apply TypeScript-only runtime transforms.
A few useful principles #
Budo apps are ordinary folders. Put your code, shaders, fonts, images, databases, model files, and metadata beside each other, then refer to those files with project-relative paths.
The runtime is immediate enough to feel like a creative coding environment, but the platform APIs are real. Network access is policy-gated, file paths are sandboxed, shader paths are checked, and Android packaging reads the same metadata your app reads.
When a feature may not exist on every target, probe sys.capabilities before you depend on it.
Running from one input #
For a throwaway sketch, you can also pipe one file directly into run:
cat <<'EOF' | budo run --from-input js
let pulse = 0;
function frame(t) {
const width = sys.window.getWidth();
const height = sys.window.getHeight();
const radius = 32 + Math.sin(pulse) * 12;
sys.canvas.clear('#0e0e12');
sys.canvas.setFillColor('#7c6ff7');
sys.canvas.drawCircle(width * 0.5, height * 0.5, radius);
sys.canvas.setFillColor('#f7f2e8');
sys.canvas.drawText('Hello from stdin', 40, 80, 28);
pulse += 0.04;
sys.animation.requestFrame(frame);
}
sys.animation.requestFrame(frame);
EOFWhat to read next #
You have used the core loop: draw with sys.canvas, read dimensions from sys.window, read pointer input from sys.input, and schedule frames with sys.animation.
Drawing and layout covers shapes, text, fonts, SVGs, paths, and transforms. Project model explains entrypoints, assets, metadata, and capabilities. Packaging and deployment shows how this same folder becomes a web export or Android package.