Getting Started #

This guide builds the smallest useful Budo app first, then adds one idea at a time. You will start with one JavaScript file, draw something, animate it, react to the pointer, and add optional project metadata.

Create an app folder #

From the repository root, create a folder with a main.js file.

Terminal
mkdir hello-budo
touch hello-budo/main.js

Put this in hello-budo/main.js:

main.js
sys.canvas.clear('#ffffff');
sys.canvas.setFillColor('#111111');
sys.canvas.drawText('Hello, Budo!', 40, 80, 32);

Run it:

Terminal
./build/budo run hello-budo

That is a complete Budo app. Budo opens a window, loads main.js, and gives your code the global sys object.

Draw every frame #

The first file draws once. Most apps draw in a frame callback so they can update over time.

Replace main.js with this:

main.js
function frame(timestamp) {
  const width = sys.window.getWidth();
  const height = sys.window.getHeight();

  sys.canvas.clear('#101116');
  sys.canvas.setFillColor('#f7f2e8');
  sys.canvas.drawText('Hello, Budo!', 32, 56, 28);

  sys.canvas.setFillColor('#7c6ff7');
  sys.canvas.drawCircle(width * 0.5, height * 0.5, 40);

  sys.animation.requestFrame(frame);
}

sys.animation.requestFrame(frame);

sys.animation.requestFrame(frame) asks Budo to call frame on the next display tick. The callback receives a timestamp in milliseconds.

Animate something #

Use the timestamp to move without keeping much state.

Change the circle code inside frame to this:

main.js
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, 32);

Now the circle orbits around the center of the window. The whole file should still be easy to scan: read the window size, clear, draw text, compute a position, draw a circle, request the next frame.

Add pointer input #

Read input once near the start of the frame.

After width and height, add this:

main.js
const input = sys.input.get();

Then replace the x and y lines with this:

main.js
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:

main.js
function frame(timestamp) {
  const width = sys.window.getWidth();
  const height = sys.window.getHeight();
  const input = sys.input.get();

  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.clear('#101116');
  sys.canvas.setFillColor('#f7f2e8');
  sys.canvas.drawText('Hello, Budo!', 32, 56, 28);

  sys.canvas.setFillColor('#7c6ff7');
  sys.canvas.drawCircle(x, y, 32);

  sys.animation.requestFrame(frame);
}

sys.animation.requestFrame(frame);

Add app metadata #

An app.json file is optional, but it gives the app a name and stores platform settings in one place.

Create hello-budo/app.json:

app.json
{
  "app_name": "Hello Budo",
  "author": "Your Name",
  "version": "1.0.0"
}

Budo uses this metadata for app identity. Android packaging also reads fields such as package name, orientation, icon, permissions, signing, and store listing data.

Use the starter command #

For a real project, you may prefer Budo to write the starter files for you.

Terminal
./build/budo init my-app

This creates a project with main.js, app.json, jsconfig.json, budo.d.ts, and a local API reference snapshot. The generated budo.d.ts file gives editors autocomplete for the sys APIs.

Split 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:

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:

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.

main.ts
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.

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.