Events
Events are what runs your scripts (e.g. app.onUpdate is called every tick)
- - on<Event> functions run in the bubble phase
- - capture<Event> functions run in the capture phase
- - All events bind this to the block that has the handler
- - If you have multiple handlers for the same event, name them onUpdate, onUpdate2, onUpdate3, etc.
Run every tick
- - this Block: Target block
Run when the app is started
- - user Block: User who started it
- - args ...any (optional): Optional "command line" parameters
Run when the app is stopped
- - user Block: User who stopped it
Run when a user joins
- - Use to e.g. spawn a player character in a game
- - user Block: User that joined
Run when a user leaves
- - user Block: User that left
Run when the pointer moves (if over this block)
- - Return true to stop bubbling
- - user Block
- - x number: World x-coordinate
- - y number: World y-coordinate
- - hit Block: Block under the pointer
Run when the pointer is pressed (if over this block)
- - Return true to stop bubbling
- - user Block
- - x number: World x-coordinate
- - y number: World y-coordinate
- - hit Block: Block under the pointer
Run when the pointer is released (if over this block)
- - Return true to stop bubbling
- - user Block
- - x number: World x-coordinate
- - y number: World y-coordinate
- - hit Block: Block under the pointer
Run when the pointer is pressed and released on this block
- - Return true to stop bubbling
- - user Block
- - x number: World x-coordinate
- - y number: World y-coordinate
- - hit Block: Block under the pointer
Run when the pointer enters this block
- - Return true to stop bubbling
- - user Block
- - x number: World x-coordinate
- - y number: World y-coordinate
- - hit Block: Block under the pointer
Run when the pointer leaves this block
- - Return true to stop bubbling
- - user Block
- - x number: World x-coordinate
- - y number: World y-coordinate
- - hit Block: Block under the pointer
Run when the mouse wheel scrolls (if over this block)
- - Return true to stop bubbling
- - user Block
- - deltaX number
- - deltaY number
- - hit Block: Block under the pointer
Run when a key is pressed (if this block has keyboard focus)
- - user Block
- - key string
- - repeat boolean (optional): true for a held key's repeats
Run when a key is released (if this block has keyboard focus)
- - user Block
- - key string
Run when the user copies/cuts to clipboard (if this block has keyboard focus)
- - user Block
- - returns string|undefined: Text to place on the clipboard (or nothing to leave the clipboard alone, e.g. if no selection to copy)
Run when text is pasted, or typed on the on-screen keyboard (if this block has keyboard focus)
- - user Block
- - text string: The pasted text (or an empty string if cut)
Run when a pointer drag starts on this block
- - Return true to accept the drag and receive more drag events
- - user Block
- - x number: World x-coordinate where the drag started
- - y number: World y-coordinate where the drag started
- - hit Block: Block where the drag started
- - data Block: Shared drag data
Run while an accepted drag moves this block
- - Return true to stop bubbling
- - user Block
- - x number: World x-coordinate
- - y number: World y-coordinate
- - hit Block: Block under the pointer
- - data Block: Shared drag data
Run on the current drop target while an accepted drag moves
- - Return true to stop bubbling
- - user Block
- - x number: World x-coordinate
- - y number: World y-coordinate
- - hit Block: Block under the pointer
- - data Block: Shared drag data
Run on the dragged block when the pointer enters a new drop target
- - Return true to stop bubbling
- - user Block
- - x number: World x-coordinate
- - y number: World y-coordinate
- - newHit Block: New block under the pointer
- - data Block: Shared drag data
Run on the new drop target when the pointer enters it during a drag
- - Return true to stop bubbling
- - user Block
- - x number: World x-coordinate
- - y number: World y-coordinate
- - newHit Block: New block under the pointer
- - data Block: Shared drag data
Run on the dragged block when the pointer leaves a drop target
- - Return true to stop bubbling
- - user Block
- - x number: World x-coordinate
- - y number: World y-coordinate
- - oldHit Block: Previous block under the pointer
- - data Block: Shared drag data
Run on the old drop target when the pointer leaves it during a drag
- - Return true to stop bubbling
- - user Block
- - x number: World x-coordinate
- - y number: World y-coordinate
- - oldHit Block: Previous block under the pointer
- - data Block: Shared drag data
Run on the dragged block when an accepted drag ends
- - Return true to stop bubbling
- - user Block
- - x number: World x-coordinate
- - y number: World y-coordinate
- - hit Block: Block under the pointer
- - data Block: Shared drag data
Run on the final drop target when an accepted drag ends
- - Return true to stop bubbling
- - user Block
- - x number: World x-coordinate
- - y number: World y-coordinate
- - hit Block: Block under the pointer
- - data Block: Shared drag data
Run after this block is resized (e.g. the app window changed)
- - Use e.g. to update the layout of child blocks
- - oldWidth number
- - oldHeight number
Run after the OS loads a project snapshot
Send an event to block
- - type string: E.g. "onResize"
- - block Block: The target block
- - args ...any: The event's arguments
Send an event to root and every enabled block inside it
- - type string: E.g. "onUpdate"
- - root Block: E.g. an app
- - args ...any: The event's arguments
Block
State is stored in a single tree of Block nodes
- - Each block has a set of key-value properties
- - You can put any property you want on a block, like speed: 3, and use it from a script with this.speed
- - The following are built-in properties:
Horizontal position (in the parent)
Vertical position (down is positive)
Depth (higher is in front)
Horizontal scale factor
Vertical scale factor
Depth scale factor
Rotation around the X-axis (in degrees)
Rotation around the Y-axis (in degrees)
Rotation around the Z-axis (in degrees)
What to draw (if anything)
- - Typically a path to a shape function, like "/shapes/circle"
- - Or a path to a block (will batch its whole tree)
- - Or a function returning a Mesh
Clip children to this block's bounds
Set false to hide the block (and ignore clicks)
Set false to disable clicks, keys, and onUpdate
Create a new block
- - properties object (optional): Add these key: value pairs to the new block
- - returns Block
Deep-copy this block
- - properties object (optional): Add these key: value pairs to the clone (or use undefined as the value to *remove* a property)
- - returns Block
Deserialize from JSON
- - json *
- - returns *: Objects become blocks, @-strings become functions, others pass through
Serialize block to JSON
- - returns object
Return block's parent (or null)
- - returns Block
Iterate over this block's properties as [key, value] pairs
- - returns IterableIterator<[string, any]>
Check whether this block has a property named key
- - key string
- - returns boolean
Count this block's properties
- - returns number
Iterate over this block's property keys
- - returns IterableIterator<string>
Iterate over this block's property values
- - returns IterableIterator<any>
Convenience function for subscribing to all keys in this block
- - A no-op outside a shape function
- - keys ...string
Get this block's key in its parent
- - returns string|null
If "key" already exists, return "key2", etc.
- - key string
- - returns string
Rename an existing property (preserving its index)
- - oldKey string
- - newKey string
Get this block's index in its parent
- - returns number
Insert a property at index (an existing key moves there)
- - key string
- - value *
- - index number (optional)
Get a path reference to this block
- - returns string: E.g. "/apps/pong/icon"
Check if block is a descendant of this block (or *is* this block)
- - block Block
- - returns boolean
Convert this block's local coordinates to world space
- - x number (optional)
- - y number (optional)
- - z number (optional)
- - returns [number, number, number]
Convert world coordinates to this block's local space
- - x number
- - y number
- - z number (optional)
- - returns [number, number, number]
Find all blocks satisfying predicate (including this)
- - predicate function(Block): boolean
- - returns Block[]
Return block's shape bounding box (in local space)
- - returns [number, number, number, number]: [minX, minY, maxX, maxY]
Like getBounds but expanded to include all children
- - A turned child adds the box around its turned box: it holds all of it, but can be bigger than it (getBoundsTreeTransformed is exact)
- - returns [number, number, number, number]: [minX, minY, maxX, maxY]
Like getBoundsTree but rotation/3D transforms applied
- - Every shape's own corners go through the whole chain of transforms: boxing each level in turn would inflate the result, as a box around a turned box is bigger than one around what it holds
- - returns [number, number, number, number]: [minX, minY, maxX, maxY]
Get the value stored at path
- - Safe to pass an undefined path (so you can do Block.get(block.path) without having to check if block *has* a path)
- - path string (optional): A path like "/path/to/value"
- - returns *: The value (or undefined)
Check if value is a path (like "/path/to/value")
- - value *
- - returns boolean
Get the callable function stored at path
- - path string
- - returns Function
Set the value at path
- - path string
- - value *
Create a deep copy of value (if it's a block)
- - returns *
Replace oldPath by newPath in every path
- - E.g. after moving a value, so any references to it update
- - oldPath string
- - newPath string
- - block Block (optional): Subtree to update (defaults to OS.state)
OS
Main module that boots the OS
Root of the multiplayer-synced state tree
Loaded project (e.g. "17/1")
Local user ID (differs per client, use with care to not desync!)
Tick counter
All connected users, e.g. for (const user of OS.users.values())
Lock the local view to a block: pan/zoom to fit it on screen (tweened), follow it every frame, and suspend view gestures (pinch-zoom) and the touch input delay, so e.g. games get full-speed touch input
- - The view is a *local* camera, so this never touches shared state — but that also means scripts (which run on every client) must gate the call to the acting user: if (user === OS.getUserByID(OS.userID)) ...
- - win Block: Block to fit: the maximize button (and Build's play button, for touch users) fits the whole window, the kiosk shell fits the bare app
- - crop boolean (optional): Crop the render to the fitted block: the rest of the world is scissored away to flat wallpaper, and presses out there are dropped. The maximize button's focus mode, also the kiosk shell's whole world
- - margin number (optional): Extra ring of world-units to fit around win's rect. Outlines ride *outside* a block's rect, so windows pass their outline width to keep the frame line on screen
- - tween boolean (optional): Animate the camera to the fit. Pass false when there's no prior view to depart from: the kiosk shell locks before its first frame and should just open at the right zoom
Undo lockView: restore the view (tweened) to where it was before locking
- - win Block (optional): Only unlock if locked to this window (pass it when cleaning up after a specific window; omit to unlock unconditionally)
Render a block (with its children) to a PNG image
- - The image covers the block's bounds, scaled so its longest side is size pixels -- e.g. await OS.renderImage('/apps/snek/icon') makes a square 1024px share preview from an app's icon
- - Renders offscreen with the same meshes and shader as the screen, and never touches shared state, so it's safe to call from scripts anytime
- - block Block|string: Block (or path to one), e.g. an app icon
- - size number (optional): Longest image side in pixels
- - background string (optional): Backdrop color (a /colors name or "#RRGGBB"), or leave undefined for a transparent backdrop
- - returns Promise<Blob>: PNG image data
Find the user with a given ID
- - id number|string
- - returns Block
Find the user with a given color
- - Users are seated in Settings.USER_COLORS order as they join: alone, you're "yellow", the next player "pink", then "purple" and "cyan"
- - A user who disconnects keeps their color for a minute, so a phone lock doesn't reshuffle anyone
- - color string
- - returns Block: The user block (or undefined)
Did the value at path change this tick?
- - For editors showing a *copy* of shared data (Build's canvas, a file list): poll this in onUpdate and reload when true. Every client runs the same commands each tick, so they all reload at the same tick
- - True if a key along the path was written (the value was replaced or deleted, or a holder swapped out), or the block at path had a key written
- - deep also counts writes to any block inside that block's subtree
- - "This tick" is every write since the previous tick's onUpdate finished, e.g. the tick's commands and onUpdate handlers that ran before the caller
- - example: if (OS.didChange(this.path, true)) this.reload();
- - path string: Absolute path, e.g. "/apps/pong"
- - deep boolean (optional): Also count writes inside the subtree
- - returns boolean
Settings
Shared settings
Each user.color is one of these "seats", dealt by the server on join
- - Each user gets a unique color, which you can use e.g. to color their player sprite, to tell them apart
- - Seats fill in this order: alone, you're always yellow
- - If there are more connected users than colors, some will be reused
- - A user who disconnects keeps their seat for a minute, so a phone lock or a resync gets the same color back
- - Every name must exist in the default palette (Color._HSL): a seat is sent by name to kernels of every version, which resolve it through their snapshot's /colors
Color
Indexed palette
- - Used globally, for a consistent look
- - Also installed in the state tree at /colors, as name: '#RRGGBB' pairs, so users can customize them. Blocks reference colors by name (color: 'yellow'), so editing an entry recolors everything that uses it
- - Key order is the picker's display order
Check whether value is a color: a /colors palette name
- - value *
- - returns boolean
Look up a palette name's RGB
- - Anyone can edit or delete /colors/cyan, so dangling names are a steady state: they come out loud magenta (plus a deduped chat error), same medicine as missing shapes
- - A shape being built gets subscribed to the entry (see Block.watch), so editing it rebuilds exactly the shapes using it
- - name string: Palette name
- - returns number[]: [r, g, b] in [0,1]
Convert HSL to a '#RRGGBB' hex color
- - h number: Hue in [0,360]
- - s number: Saturation in [0,100]
- - l number: Lightness in [0,100]
- - returns string
Convert a '#RRGGBB' hex color to HSL
- - hex string
- - returns [number, number, number]: h in [0,360], s and l in [0,100]
Sound
Play SFXR style sound effects
Play a sound effect
- - sound string|Block: Path to a sound effect (e.g. "/sounds/coin") or a SFXR definition
- - volume number (optional): Volume multiplier
- - pitch number (optional): Speed multiplier
- - loopID string (optional): Keep the same loop alive by calling play every tick
Mouse
Browser mouse event listeners
Drop pointermove commands at the source
- - Scripts set this when a game reads no pointer movement (e.g. from Controls.detect): a thumb resting on glass jitters out a move per frame, and every client would pay a broadcast and a rollback-replay for each
Re-aim every pointer at what's under it now
- - For scripts that change what's under a still pointer: a view rebuilt, content scrolling by, a window opening. Hover then follows, and onMouseOut/onMouseOver fire as if the pointer had moved
- - Not for a lifted finger (no mouse.hit), nor mid-drag, where the next move retargets the drop anyway
- - example: Mouse.retarget()
Find the block at x,y (if any)
- - block Block: Start search from this block
- - x number: Coordinate to check (relative to block)
- - y number: Coordinate to check (relative to block)
- - ignore Block (optional): Block to skip (e.g. the dragged block)
- - sleeping boolean (optional, default false): Hit enabled: false blocks too: an editor picks what's drawn, not what's clickable (a button's label)
- - returns Block: Hit block (block itself if none of its descendants)
Keyboard
Browser keyboard event listeners
Show the native on-screen keyboard (on the user's own device only)
- - Phones and tablets summon their built-in keyboard; computers show nothing, physical typing just keeps working
- - What the keyboard produces is sent as regular key/text commands to the user's focused block, so typing stays deterministic for everyone
- - Call this from the text target's press handler (ui/input does, for a press on its text), not from wherever it takes focus: focus also moves to a text field's scroll bar, or back from a toolbar button, and neither is a wish to type
- - The keyboard rises when the finger lifts. Deferring to the tap's own pointer-up keeps the focus change inside a user gesture (required to show the keyboard), while letting a second finger cancel it into a pinch-zoom (a buffered press never executes then, see Mouse)
- - example: Keyboard.summon(user)
- - user Block: Who gets the keyboard (no-op for everyone but the local user: each device keeps its keyboard to itself)
Put away the native on-screen keyboard (if summoned) when the tap completes: call this wherever the focus moves
- - A no-op with the focus back on the text target that summoned it: a toolbar button's press focuses its window, but its click hands the focus back to the editor, and a keyboard that was up stays up
- - Asked for by a focus that has no use for it, so if the same tap moves the focus on, the request lapses and the new focus decides anew
- - example: Keyboard.dismiss(user)
- - user Block: Whose device to dismiss (no-op for everyone but the local user)
Copy as if by Cmd+C, for devices without one: the focused block's onCopy to the device's clipboard (the local user's only: each device keeps its clipboard to itself)
- - Call this from a button's click handler: the browser only writes inside a tap, which the click's command runs inside of (see Mouse)
- - example: Keyboard.copy(user)
- - user Block: Whose device (no-op for everyone but the local user)
Paste for devices without a Cmd+V: opens the chat with a /paste command typed, for the user to paste the clipboard after. Sent, it types the text into the focused block (onText), see Agent._submit
- - The user pastes, never a script: no page lets scripts read the clipboard (no clipboard-read), so what's on it stays the user's
- - Nothing on a published game's page, which has no chat input
- - example: Keyboard.paste(user)
- - user Block: Whose device (no-op for everyone but the local user)
Chat
Message history dropdown (for logs, chat, and AI assistant)
- - Logs will automatically show and hide the Chat dropdown
- - Click or drag the tab just below the header to pull the Chat view down
- - When opened manually it'll include the input where you can ask questions to the AI assistant
Add a system message to the chat
- - args ...any (optional): One or more values to print
Show a chat message from user
- - user Block: Sender
- - text string: Message text
Report an exception: a deduped red chat entry carrying the stack trace (selectable and copyable even on browsers without a devtools console), plus a one-time console.error
- - The entry is local chrome, so it's fine that browser engines word messages and stacks differently; never feed it back into world state!
- - label string: Origin, e.g. "/ball/onUpdate" or "render"
- - error Error: The exception
Mesh
Renderable WebGL mesh with a VBO
- - vertices number[]: [x, y, z, r, g, b, x, y, z, r, g, b, ...]
Font
WebGL text rendering helpers
Measure a text's width: its longest line's character cells
- - text string
- - size number (optional): Font size
Measure a text's height: its line boxes (size each, LINE_HEIGHT apart)
- - Not the glyph ink, so a descender doesn't make one line taller than the next
- - text string
- - size number (optional): Font size
Get vertices for a text string
- - text string
- - size number: Font size
- - color string|null (optional): Fill color (palette name), or null for no fill
- - alignX number: 0.0 = left, 0.5 = center, 1.0 = right
- - alignY number: 0.0 = top, 0.5 = middle, 1.0 = bottom
- - outline string|null (optional): Outline color (palette name)
- - outlineWidth number (optional): Outline width
Code
Eval user scripts into JavaScript function objects
- - Functions are stored as actual callable function objects, interned by source: identical code is always the identical object, so no-op change detection, autosave comparison, and === all work by identity
- - Snapshots serialize them (via the toJSON stamped on each function) as "@" + source strings (source may start with a JSDoc comment), and Block.fromJSON interns them back
Get the canonical function object for JavaScript source
- - Invalid source returns a NOP function that still carries the source, so half-typed code in an editor round-trips losslessly until it parses
- - source string: E.g. "function onUpdate() {}"
- - returns Function
Lex JavaScript-ish source into source-preserving tokens
- - Total: unknown characters become unknown tokens instead of throwing, so half-typed or non-JS text still lexes (editors and the assistant colorize best-effort)
- - source string
- - returns {type: string, value: string, start: number, end: number}[]
Geometry
Geometry helpers
Intersect two line segments
- - x1 number: First line start X
- - y1 number: First line start Y
- - x2 number: First line end X
- - y2 number: First line end Y
- - x3 number: Second line start X
- - y3 number: Second line start Y
- - x4 number: Second line end X
- - y4 number: Second line end Y
- - returns {x: number, y: number, nx: number, ny: number, dx: number, dy: number, time: number} | null
Intersect a line segment and an axis-aligned bounding box (i.e. "raycast")
- - x1 number: Line start X
- - y1 number: Line start Y
- - x2 number: Line end X
- - y2 number: Line end Y
- - boxX number: Box center X
- - boxY number: Box center Y
- - boxWidth number: Box width
- - boxHeight number: Box height
- - returns {x: number, y: number, normalX: number, normalY: number, deltaX: number, deltaY: number, time: number} | null
Intersect two circles
- - x1 number: Circle center X
- - y1 number: Circle center Y
- - radius1 number: Circle radius
- - x2 number: Circle center X
- - y2 number: Circle center Y
- - radius2 number: Circle radius
- - returns {x: number, y: number, nx: number, ny: number, dx: number, dy: number, depth: number} | null
Intersect a circle and a line segment
- - cx number: Circle center X
- - cy number: Circle center Y
- - radius number: Circle radius
- - x1 number: Segment start X
- - y1 number: Segment start Y
- - x2 number: Segment end X
- - y2 number: Segment end Y
- - returns {x: number, y: number, nx: number, ny: number, dx: number, dy: number, depth: number} | null
Intersect a circle and an axis-aligned bounding box
- - circleX number: Circle center X
- - circleY number: Circle center Y
- - circleRadius number: Circle radius
- - boxX number: Box center X
- - boxY number: Box center Y
- - boxWidth number: Box width
- - boxHeight number: Box height
- - returns {x: number, y: number, nx: number, ny: number, dx: number, dy: number, depth: number} | null
Triangulate a closed contour with ear clipping
- - points number[][]: Closed contour points in clockwise screen-space order
- - returns number[][]: Triangle vertices, with every 3 points forming one triangle
Triangulate the strip between two matching contours
- - inner number[][]: Inner contour points
- - outer number[][]: Outer contour points
- - returns number[][]: Triangle vertices for the strip mesh
Offset a closed contour by a fixed distance
- - points number[][]: Closed contour points in clockwise screen-space order
- - distance number: Offset distance; positive moves outward, negative inward
- - returns number[][]: Offset contour points
Pack 2D points into mesh vertices with a shared color and Z
- - points number[][]: 2D points
- - color string: Palette name
- - z number (optional): Z coordinate
- - returns number[]: Packed vertex data as x, y, z, r, g, b per point
Build packed mesh vertices for a closed contour's fill and/or outline
- - points number[][]: Closed contour points in clockwise screen-space order
- - color string | null (optional): Fill color (palette name), or null for no fill
- - outline string | null (optional): Outline color (palette name), or null for no outline
- - outlineWidth number (optional): Outline width
- - returns number[]: Packed vertex data
Compute axis-aligned bounds for a point set
- - points number[][]: 2D points
- - returns number[][]: Bounding corners as [[minX, minY], [maxX, maxY]]
Build or reuse a uniform-grid spatial index for blocks with bounds
- - Bounds may be explicit minX/minY/maxX/maxY, or an AABB x/y/width/height
- - items Block: Block whose children should be indexed
- - cellSize number (optional): World-space grid cell size
- - returns { queryCircle(x: number, y: number, radius: number, out?: Block[]): Block[], querySegment(x1: number, y1: number, x2: number, y2: number, out?: Block[]): Block[], queryAABB(minX: number, minY: number, maxX: number, maxY: number, out?: Block[]): Block[], }
Documentation
Searchable index of API documentation
- - Built entries come from the generated api-documentation.js: the kernel, whose JSDoc the minified build drops, and the desktop's building blocks (Input, Box, Desktop...), whose module banners never enter the tree. With them, the modules in curated order, each with its members
- - Tree entries are read live, so they include what users write themselves and follow every edit: a function whose source opens with a JSDoc. Named like a built entry, a live one replaces it
- - Tree entries are named the way scripts reach them: Grid.create for /scripts/grid/create, through the globals aliasing a block (one entry per alias when several do); input.setValue for a method of a type, the one its module's @typedef names (built into API_TYPES) or its own @this {Input} does; the bare path when neither applies. A script whose JSDoc opens a type (@typedef {Shape} Text on /shapes/text) documents the blocks it serves: it's that type's entry, found by its path too. @ignore leaves a function out, as in the source files
- - search reports on the index and the state tree in one text, the AI assistant's search tool and the Documentation app alike
Look up one name
- - name string: E.g. "Util.clamp", "onUpdate", or "Grid.create"
- - returns string|undefined: Its JSDoc and signature
Every documented name, with its documentation: JSDoc and signature for a function, text and member list for a module
- - returns Map<string, string>
Report what matches query, in the documentation and the state tree: text for a reader who then searches a name or gets a path
- - Entries and tree hits rank alike (see _rank): matching more of the query's words comes first, then matching them in the name (a tree hit's parent/key), then by how much of the name they match, then by mentions
- - A name (onMouseDown, Block.get) gets its full entry, and a module its members too, one line each: the signature, then the summary of its documentation as a comment. So does the one name matching every word, when there is exactly one. The other matches follow as such lines, with a count of the ones left out. An empty query lists the modules, by importance
- - Tree hits read like grep: the path, then what matched there — a key's value, a matching string, or a script's matching lines with their numbers
- - query string: A name, or words to look for
- - excludePaths string[] (optional): Subtrees to leave out of the tree hits
- - returns string
Random
Deterministic random number generator
- - min number (optional)
- - max number (optional)
- - returns number: A number in [min, max)
- - min number (optional)
- - max number
- - returns number: An integer in [min, max]
Util
Utility functions
Deterministic version of Math.sin
Deterministic version of Math.sin (for angles in degrees)
Deterministic version of Math.cos
Deterministic version of Math.cos (for angles in degrees)
Deterministic version of Math.atan2
Deterministic version of Math.atan2 (for angles in degrees)
Deterministic version of Math.pow
Deterministic version of Math.hypot
Deterministic version of Math.exp
Deterministic version of Math.PI
Deterministic version of Math.max
Deterministic version of Math.min
Deterministic version of Math.abs
Deterministic version of Math.floor
Deterministic version of Math.ceil
Deterministic version of Math.round
Deterministic version of Math.sqrt
Compute n modulo m
- - Like n % m but gracefully handles negative numbers (e.g. mod(-1, 5) returns 4)
- - n number
- - m number
- - returns number
Convert degrees to radians
- - degrees number
- - returns number: Radians
Convert radians to degrees
- - radians number
- - returns number: Degrees
Round a number to the nearest step
- - number number
- - step number
- - returns number: Rounded number
Clamp a number between min and max
- - number number
- - min number
- - max number
- - returns number: Clamped number
Calculate the length of a vector
- - x number
- - y number
- - returns number: Length of the vector
Calculate the distance between two points
- - x1 number
- - y1 number
- - x2 number
- - y2 number
- - returns number: Distance between the two points
Split a path into prefix and key
- - path string: A path like "/data/icons/play"
- - all boolean (optional): Split into *all* possible parts
- - returns string[]: A prefix/key pair like ["/data/icons", "play"] (or ["/", "system", "images", "app"] if all is true)
Join parts into one path
- - parts ...string: Path parts like "/data/icons", "play"
- - returns string: A full path like "/data/icons/play"
Copy text to clipboard
- - text string
- - returns Promise<boolean>
Check if str is an allowed display name like "First Last3" or "My Game"
- - The one rule for user, project, and shared game names alike
- - Not ridiculously long
- - Safe to display in HTML (no <, no XSS injection)
- - All characters can be displayed by our WebGL font (no emojis!)
- - No attention-calling punctuation like "---( Magni )---"
- - No zero-length or space-only strings (an invisible name is no name)
- - str string
- - returns boolean
Hash str into a string of exactly 6 chars, like "k3v9p2"
- - cyrb53 (https://stackoverflow.com/a/52171480) mixing, ~30 bits -- plenty when uniqueness only needs to hold within one scope, like an author's publish IDs
- - The first char is always a letter, so hash IDs can never look like a number (e.g. publish IDs are distinct from numeric project IDs by design, no namespace prefix needed)
- - Deterministic (Math.imul and bit ops only), so multiplayer clients running the same command always agree on the result
- - str string
- - seed number (optional)
- - returns string
Define a global variable (or ignore if already defined)
- - name string
- - descriptor PropertyDescriptor
Shape
A shape is any block whose shape names a script in /shapes
- - E.g. shape: '/shapes/circle'. The script builds the block's mesh from the properties below, which every shape script reads, plus its own (Circle's segments, Text's alignX), and rebuilds it when one changes
- - A shape is bounds first, drawing second: without a color (or an outline) it draws nothing, and is a hit area, a collider, or the bare frame of a graphic (shapes on a root shape, which colored is also their background)
Width in pixels (text and obj size themselves instead)
Height in pixels
Fill: a palette name
- - None by default: a colorless shape draws nothing (obj excepted, see Obj)
- - An outline on a shape *without* a color stands alone: a ring
Outline color (none by default)
Outline thickness, drawn outward
Panel
shape: '/shapes/panel' draws a width x height rectangle hanging from its top-left corner at the origin (windows, buttons)
Rectangle
shape: '/shapes/rectangle' draws a width x height rectangle centered on the origin
Circle
shape: '/shapes/circle' draws a disc filling width x height, centered on the origin
Triangle count
- - 3 is a triangle, 6 a hexagon (default: enough to look round at its size)
Star
shape: '/shapes/star' draws a star filling width x height, centered on the origin
Number of points
Inner radius over the outer
- - 0.3 with 4 points is a sparkle
Triangle
shape: '/shapes/triangle' draws a width x height triangle centered on the origin, apex up
Wedge
shape: '/shapes/wedge' draws a width x height right triangle centered on the origin, its right angle at the bottom left
Text
shape: '/shapes/text' draws a string, sized to fit it
- - color fills the glyphs and outline rings them, like any shape; width and height are ignored
- - alignX and alignY place the text around the block's origin: at 0.5 it's centered there, at 0 it hangs right and down from it
The string to draw (\n breaks lines)
Font size in pixels
Horizontal alignment, 0 (left) to 1 (right): 0.5 centers it
Vertical alignment, 0 (top) to 1 (bottom): 0.5 centers it
Obj
shape: '/shapes/obj' draws a /meshes model, at the model's own size
- - The model's o names pick its colors: fill takes the block's color (white by default), outline its outline, and any palette name itself
Path to the model, e.g. /meshes/plus
Apps
The app definitions
- - Shell.startApp(user, Apps.pong) runs a clone of one in a window, a shortcut's path names one (/apps/pong), and a published project runs one full screen
Inventory
Curated set of blocks in /inventory shown by the Build app
Scripts
Functions shared by reference, in modules under /scripts
- - A block points at one with Block.script('/scripts/input/setValue'), so a template and all its clones run the same function, and an edit in the Code app reaches them all
- - A module is a block of functions; the desktop aliases a few as globals: Box, Controls
Parse an OBJ file
- - obj string: The OBJ to parse, e.g. exported from Blender with:
- - Export as "Wavefront (.obj)"
- - Model in screen coords (X+ right, Y+ down)
- - No UV Coordinates
- - No normals
- - Triangulated Mesh
- - No materials
- - returns [number[][], Map<string, number[][]>]: Array of [x,y,z] vertices, and a Map of object names to their [a,b,c] (vertex index) triangles
Dock this window to owner: from now on the two (and whatever is docked to either) move, rise, and close as one group, and only the group's root keeps its maximize and close buttons. This window shows the undock button in their place
- - A title bar drag that lands a window against another docks on its own (see onDragEndTitleBar); Build docks its palettes explicitly
- - The app hears onDock(owner), and onUndock(owner) on the way out: the Inspector is its Explorer's for as long as it's docked to it
- - owner Block: The window to dock to
Undock this window (and whatever is docked to it), getting its maximize and close buttons back: popped a cascade step down and right, so it clearly left, and no longer sits in a docked position
Controls
Standard game controls
Check whether this user is holding a game control
- - user Block
- - control string (optional): "left" or "right" (or undefined for any tap/key)
- - returns boolean: True while the control is held
Check whether this user pressed a game control this tick
- - user Block
- - control string (optional): "left" or "right" (or undefined for any tap/key)
- - returns boolean: True on the first tick the control is pressed
Read a virtual joystick as a direction vector
- - user Block
- - side string (optional): "left" or "right" screen half and arrow-keys/WASD (or undefined for either)
- - radius number (optional): Max drag distance in pixels
- - returns number[]: [x, y] from -1 to +1
Button
Push button: dips down while pressed
- - Edit its onClick to make it do something (the starter just logs)
The text shape (set its text, or replace it with a shape of your own for an icon button)
Slider
Slider: drag the thumb (or click the track) to pick a value, or step it with the buttons at the ends
The current value
Value at the start of the track
Value at the end of the track
Round value to a multiple of this (from min)
- - Also how far the buttons step (else a tenth of the range)
'y' for a vertical slider
The handle (which you can style)
The button at the min end
The button at the max end
Event sent when setValue changes the value
Set the value (rounded to step and clamped to min..max), sending onChange if it changed
- - To set it silently, assign value and call layout instead
- - value number
- - returns number: The value actually set
Update layout after changing value, width, or height directly
Scroll
Scroll view: wheel the content around (with bounce), or drag its bars
- - No drag-to-scroll, on purpose: a swipe a pixel off the background would drag a block off to who-knows-where instead, so fingers get the bars
- - The bounce is loosely based on https://github.com/zynga/scroller
Put your blocks in here, size it (width and height) to fit them, then call updateBounds
- - Its x and y are the scroll position (set them with scrollTo)
How far negative content.x scrolls (its overflow past the view), and minY likewise: 0 is the start
Scroll horizontally without a bottom bar (e.g. a single-line text input)
The bottom slider (which you can style), once the content has overflowed the view
The right slider, likewise
Update the scroll range after resizing the view or its content
Scroll to put content at (x, y): 0 shows its start, minX and minY its end (clamped to that range), and any bounce stops
- - x number
- - y number
Scroll by delta along axis ("X" or "Y"): freely within the range, and past an edge against a rubber band that stiffens the further out it is
- - The wheel and a bounce's momentum both push through it
- - onUpdate springs the stretch back
- - axis string
- - delta number
Input
Text input widget
The text
Enter inserts a newline (else it sends onSubmit)
Hint shown if there's no text
Fixed text drawn before the value, on its first line: not part of it, so neither editable nor selectable (a path's folder, a prompt)
Custom text formatting (e.g. syntax highlighting)
Text to select and copy, not edit: no caret or on-screen keyboard, and only setValue changes it (the Documentation app's results)
The text cursor's color (else the text's)
Behind selected text
Where the text cursor is (move it with setCursor)
The selection's other end, while text is selected
The edits to undo, while there are any (redoStack likewise)
The text is always drawn in a scroll view (in case it's too long to fit)
- - scroll.content.text is the text shape (which you can style)
Event sent after every edit (except setValue)
Event sent on Enter (unless multiline)
Event sent on Escape
Replace the text
- - value string: The new text
- - caretIndex number (optional): Move the text cursor here
Fit the text area to the input's width and height
- - Call it after changing them from a script
Move the text cursor (scrolling it into view)
- - index number: New position
- - keepAnchor boolean (optional): Keep and extend the current selection
Undo the last edit
Redo the last undone edit
Insert text at the current cursor position
- - text string
Delete the character at the cursor (or the selected text)
- - beforeCursor boolean (optional): Backspace instead of Delete
Scoreboard
Scoreboard: a text shape listing each user's name and points, best first
- - Style it like any text (size, alignX, outline...): the lines take the users' colors
- - The text follows scores and the users: it's re-listed every tick
Points by user ID (which you can edit directly)
Add points to a user's score
- - user Block: Scoring user (nothing happens if they've left)
- - points number: Points to add (negative to subtract)
Clear all scores, e.g. for a new round (departed users' points too)
Box
Box any key-value property to make it a drag-and-drop thumbnail + label
- - Every app in the entire OS (including the desktop) uses boxes as the common data transfer format, so you can drag a box from anywhere and drop it on anything.
Drag data contract (data is the shared drag Block)
- - dragType: 'box'
- - key: Property key the value was dragged from (dropping inserts key: value)
- - value: The dragged value (a clone, for blocks)
- - source: Data path the value came from (if any) — a move or trash deletes it in onDragEnd, and watchers rebuild any views
- - drag: The clone (also Shell.dragClone). It follows the cursor, so it receives the drag events, and hit testing ignores it
- - locked: The source keeps its value (a shelf with its pen unlit, see the Explorer): a drop that would move it copies instead
- - dropAction: The target's claim, 'move' (a shelf taking a box, both ends on screen). Without one a drop is a copy (Build's canvas stamping a prefab). Shift makes any drop a copy, and Alt a link (see getAction)
- - dropParent/dropIndex: Where the drop will insert (no index: last)
- - Shell.dropPreview: Marks the drop target, as a ring around a box it would land inside (see updateDragClone), or styled by whoever shows it (a caret in a shelf's gap, a square on a desktop slot)
Shell
/shell: the editing shell, which runs apps in windows next to the tools that build them, on the desktop: the folder /desktop shown as free-floating boxes (see desktop), so a link to an app is a launcher, and anything dropped on the desktop is parked there
- - The kernel only ever talks to /system/shell, which is this block on the editing desktop and /kiosk in a published app; both answer to Shell, so the same app runs under either
Start an app
- - example: Shell.startApp(user, Apps.pong)
- - user Block: User who's starting the app
- - app Block: App definition or detached app copy
- - args ...any (optional): "command line arguments" to pass to the started app
- - returns Block: The started window
Stop an app
- - user Block|null: User who's stopping the app, or null if the app stops itself
- - win Block: The app's window
Open the value at path with the app that handles it, like a file: a sound in Sound, a color in Color, a script in Code, a shape or an app in Build, a folder in a new Explorer (see getOpener)
- - The *editing* half of "open with": a desktop icon runs an app instead
- - example: Shell.open(user, '/graphics/ball')
- - user Block: Who's opening it
- - path string: Data path of the value
- - app Block (optional): Open with this app (a definition, e.g. a desktop icon's) instead, if it can
- - returns Block|undefined: The window, or undefined if nothing opens the value (a number, a plain string -- or app can't)
Move a user's keyboard focus
- - Focus raises: the focused window comes to the front
- - The title bar lights up while anyone's focus is inside the window
- - Works in a published app too, where there is no window to raise
- - example: Shell.setFocus(user, myInput)
- - user Block: Whose focus to move
- - block Block (optional): Block to receive the user's key events (omit to just clear focus)
Restore (un-maximize) whatever window user has maximized: the flip side of the title bar's maximize button
- - user Block
Get the running app a block belongs to
- - undefined outside any window: shape functions render blocks anywhere, including inventory shelf copies
- - For tools: an app reaches itself through this.parent, which also works published
- - block Block: A block inside the app
- - returns Block|undefined
Find every window running app
- - Instances are clones with no link back to their app, so name stands in for its identity
- - app Block: App definition
- - returns Block[]: The windows
A key no window has had, for a new one: prefix numbered by a count that never goes back
- - For getProcessKey hooks that open a fresh window per launch. Not Windows.getUniqueKey, which hands a closed window's key to the next one: a path kept to a window that's gone (Build's Properties palette, an Explorer's Inspector) then stays dead, as any path to something deleted does, instead of quietly naming a stranger
- - Apps with one shared window key it by name instead, so that a relaunch *is* the running window (see startApp)
- - example: return win?.getKey() ?? Windows.newKey('code')
- - prefix string
- - returns string: E.g. 'code7'
Move the value at path into the Trash (/desktop/trash, a folder like any other: browse it to take things back, or drag them out), references following (a trashed graphic still in use keeps working until the Trash is emptied)
- - Already in the Trash (or the Trash itself): deleted for good
- - A folder whatever it holds, by its path (see Box.getType): it keeps the keys it's given, shape and icon included
- - example: Shell.trash('/graphics/ball')
- - path string
Rename the value at path to name, as a key code can type ("My circle" becomes "myCircle"), references following (see Block.rewritePaths)
- - What's left that code still can't type (a built-in like "parent", a leading digit like "2ndPlayer") is refused with a hint, and an empty or unchanged name changes nothing, like a file manager
- - example: Shell.rename('/graphics/ball', 'Blue ball') // '/graphics/blueBall'
- - path string
- - name string
- - returns string: The path after the rename (path itself if nothing changed)