Update V8 Version
openinterpreter/openinterpreter
Bumps the pinned v8 and rusty_v8 versions in Codex, validates the release-candidate path with the v8-canary check, and traces failures to upstream build changes.
Complete implementation and design guide for Ply. An agent skill from TheRedDeveloper/ply-engine.
$ npx skills add TheRedDeveloper/ply-engine --skill ply-engine -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install TheRedDeveloper/ply-engine ply-engine --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
Claude Code skills documentation · loads skills from .claude/skills/
Install the "ply-engine" agent skill from https://github.com/TheRedDeveloper/ply-engine/tree/main into .claude/skills/ply-engine/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ply-engine", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add TheRedDeveloper/ply-engine --skill ply-engine -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install TheRedDeveloper/ply-engine ply-engine --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "ply-engine" agent skill from https://github.com/TheRedDeveloper/ply-engine/tree/main into .agents/skills/ply-engine/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ply-engine", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add TheRedDeveloper/ply-engine --skill ply-engine -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install TheRedDeveloper/ply-engine ply-engine --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "ply-engine" agent skill from https://github.com/TheRedDeveloper/ply-engine/tree/main into .cursor/skills/ply-engine/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ply-engine", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add TheRedDeveloper/ply-engine --skill ply-engine -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install TheRedDeveloper/ply-engine ply-engine --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "ply-engine" agent skill from https://github.com/TheRedDeveloper/ply-engine/tree/main into .gemini/skills/ply-engine/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ply-engine", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install TheRedDeveloper/ply-engine ply-engineInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add TheRedDeveloper/ply-engine --skill ply-engine -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "ply-engine" agent skill from https://github.com/TheRedDeveloper/ply-engine/tree/main into .github/skills/ply-engine/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ply-engine", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add TheRedDeveloper/ply-engine --skill ply-engine -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install TheRedDeveloper/ply-engine ply-engine --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "ply-engine" agent skill from https://github.com/TheRedDeveloper/ply-engine/tree/main into .opencode/skills/ply-engine/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ply-engine", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
ply-engineComplete implementation and design guide for Ply. An agent skill from TheRedDeveloper/ply-engine.
Ply Engine is an agent skill from TheRedDeveloper/ply-engine. Complete implementation and design guide for Ply. Use this skill whenever a task involves ply-engine APIs, plyx workflows, or building UI in Rust with Ply.
Its SKILL.md is about 10k tokens, which your agent loads only when the skill is triggered. The skill folder holds 63 other files (for example `.github/FUNDING.yml`, `.github/ISSUE_TEMPLATE/bug_report.md` and `.github/ISSUE_TEMPLATE/feature_request.md`).
It works with Rust. The repository describes itself as: Beautiful UIs in Rust. Cross-platform. Dead simple. The licence is ISC.
7 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 52be132. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships script files (JavaScript, Java and Shell, from the files we listed), which the agent can run.
Shell commands in SKILL.md call:
cursorFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Ply Engine loads about 10k tokens when it runs. Until then it costs about 42 tokens; SKILL.md has 3,626 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from TheRedDeveloper/ply-engine at commit 52be132, republished under its ISC licence (© TheRedDeveloper). 3,626 words, ~10,207 tokens.
.claude/skills/ply-engine/SKILL.md (or your agent's skills folder). This skill also uses 59 other files; get the full folder from GitHub.This part is the ultimate guide for writing Ply applications.
use ply_engine::prelude::*;.layout(|l| l
.direction(TopToBottom)
.align(CenterX, Top)
.gap(12)
.padding(14)
).layout(|l| {
l.direction(TopToBottom)
.align(CenterX, Top)
.gap(12)
.padding(14);
l
})overflow, border, scrollbar, floating, effect, shader, text_input, and accessibility.Ply crate features:
a11y (default): accessibility supporttext-styling: inline styling and animation tagstinyvg: TinyVG vector renderingshader-build: shader build pipeline utilities for build.rsbuilt-in-shaders: built-in shader assetsnet: HTTP + WebSocket APIsnet-json: JSON deserialization helpers for net responsesaudio: macroquad audio re-exportsstorage: cross-platform persistent storage APIThis is the default project structure generated by plyx init:
use ply_engine::prelude::*;
fn window_conf() -> macroquad::conf::Conf {
macroquad::conf::Conf {
miniquad_conf: miniquad::conf::Conf {
window_title: "Hello Ply!".to_owned(),
window_width: 800,
window_height: 600,
high_dpi: true,
sample_count: 4,
platform: miniquad::conf::Platform {
webgl_version: miniquad::conf::WebGLVersion::WebGL2,
..Default::default()
},
..Default::default()
},
draw_call_vertex_capacity: 100000,
draw_call_index_capacity: 100000,
..Default::default()
}
}
#[macroquad::main(window_conf)]
async fn main() {
static DEFAULT_FONT: FontAsset = font!("assets/fonts/MyFont.ttf");
let mut ply = Ply::new(&DEFAULT_FONT).await;
loop {
clear_background(BLACK);
let mut ui = ply.begin();
ui.element()
.width(grow!())
.height(grow!())
.layout(|l| l.align(CenterX, CenterY))
.children(|ui| {
ui.text("Hello, Ply!", |t| t.font_size(32).color(0xFFFFFF));
});
ui.show().await;
next_frame().await;
}
}Of course this is often expanded with different files that contain different functionality and components.
Everything below is re-exported from ply_engine::prelude in this repository.
PlyUiElementBuilderNoIdWithIdIdGraphicAssetFontAssetShaderAssetLerpPointerDataPointerStatecrate::easing::*render_to_textureset_shader_sourcegrow!fit!fixed!percent!ratio!AlignX::{Left, CenterX, Right}AlignY::{Top, CenterY, Bottom}BorderPosition::{Outside, Middle, Inside}LayoutDirection::{LeftToRight, TopToBottom}WrapModeAccessibilityRolebuilt-in-shaders: *net: net, WsMessagestorage: Storagetext-styling: stylingaudio: *jobs modulemacroquad::prelude::*ColorMacroquadColor alias for macroquad colormacroquad crateCursorIconImportant: because full macroquad::prelude::* is re-exported, any macroquad-prelude function/type is also in scope when using Ply prelude.
Ply Core MethodsConstruction and lifecycle:
Ply::new(default_font).awaitPly::new_headless(dimensions)begin() -> Uieval() -> Vec<RenderCommand>show().awaitPointer, focus, and cursor:
pointer_over(id) -> boolpointer_over_ids() -> Vec<Id>focused_element() -> Option<Id>set_focus(id)clear_focus()is_pressed(id) -> boolis_just_pressed(id) -> boolis_just_released(id) -> boolset_cursor(CursorIcon)get_cursor() -> CursorIconText input state by ID:
get_text_value(id) -> &strset_text_value(id, value)get_cursor_pos(id) -> usizeset_cursor_pos(id, pos)get_selection_range(id) -> Option<(usize, usize)>set_selection(id, anchor, cursor)Layout, bounds, scroll:
set_layout_dimensions(dimensions)pointer_state(position, is_down)update_scroll_containers(drag_scrolling_enabled, scroll_delta, delta_time)bounding_box(id) -> Option<BoundingBox>scroll_container_data(id) -> Option<ScrollContainerData>set_scroll_position(id, position)Debug and performance:
set_debug_mode(bool)set_debug_view_width(f32)is_debug_mode() -> boolset_culling(bool)max_element_count(u32)max_measure_text_cache_word_count(u32)set_measure_text_function(|text, config| -> Dimensions)Ui Methodselement() -> ElementBuildertext(text, |TextConfig| ... )scroll_offset() -> Vector2hovered()pressed()just_pressed()just_released()focused()Ui dereferences to Ply, so all Ply methods are callable from ui too.
ElementBuilder Methods (Complete)Layout and sizing:
width(Sizing)height(Sizing)contain(f32)cover(f32)layout(|LayoutBuilder| ...)Visuals:
background_color(color)corner_radius(f32 | (f32, f32, f32, f32))border(|BorderBuilder| ...) (multiple possible)overflow(|OverflowBuilder| ...)image(ImageSource)effect(shader_asset, |ShaderBuilder| ...)shader(shader_asset, |ShaderBuilder| ...)rotate_visual(|VisualRotationBuilder| ...)rotate_shape(|ShapeRotationBuilder| ...)Structure and identity:
id(id_like) (NoId -> WithId)get_id() -> &Idfloating(|FloatingBuilder| ...)children(|ui| ...) -> Idempty() -> IdInteractivity:
with(|&mut Ui, ElementBuilder<WithId>| ...)capture()on_hover(|Id, PointerData| ...)on_press(|Id, PointerData| ...)on_release(|Id, PointerData, bool| ...)on_focus(|Id| ...)on_unfocus(|Id| ...)preserve_focus()Text input and accessibility:
text_input(|TextInputBuilder| ...)accessibility(|AccessibilityBuilder| ...)Sizing and Macrosfit!() or fit!(min, max) or named args (min:, max:)grow!() or grow!(min, max, weight) or named args (min:, max:, weight:)fixed!(px)percent!(fraction)ratio!(aspect_ratio)Notes:
grow!(weight: 0.0) behaves as fit internally.ratio!(r) derives the dimension from the opposite axis (height = width / ratio on height, width = height * ratio on width). Setting ratio! on both axes panics during layout.contain(ratio) and cover(ratio) adjust the element's resolved bounding box to fit or cover its allocated slot to respect the aspect ratio.LayoutBuildergap(u16)align(AlignX, AlignY)direction(LayoutDirection)wrap()wrap_gap(u16)padding(u16 | (top, right, bottom, left))OverflowBuilderclip_x(), clip_y(), clip()scroll_x(), scroll_y(), scroll()no_drag_scroll()scrollbar(|ScrollbarBuilder| ...)ScrollbarBuilderwidth(f32)corner_radius(f32)thumb_color(color)track_color(color)min_thumb_size(f32)hide_after_frames(u32)FloatingBuilderoffset(Vector2-like)z_index(i16)anchor((AlignX, AlignY), (AlignX, AlignY))attach_parent()attach_root()attach_id(id)clip_by_parent()passthrough()BorderBuildercolor(color)all(u16)left(u16)right(u16)top(u16)bottom(u16)between_children(u16)position(BorderPosition)Visual rotation (rotate_visual):
degrees(f32)radians(f32)pivot((x, y)) normalized in [0,1]flip_x()flip_y()Shape rotation (rotate_shape):
degrees(f32)radians(f32)flip_x()flip_y()ui.text + TextConfigText configuration methods:
color(color)font(&'static FontAsset)font_size(u16)letter_spacing(u16)line_height(u16)wrap_mode(WrapMode)alignment(AlignX)effect(shader_asset, |ShaderBuilder| ...)accessible()WrapMode variants:
WordsNewlineNonePly Color accepts:
0xRRGGBB integers(u8, u8, u8)(u8, u8, u8, u8)(f32, f32, f32)(f32, f32, f32, f32)Important: float channels are in 0..255 space, not 0..1.
TextInputBuilderplaceholder(&str)max_length(usize)password()multiline()drag_select()font(&'static FontAsset)font_size(u16)text_color(color)placeholder_color(color)cursor_color(color)selection_color(color)line_height(u16)scrollbar(|ScrollbarBuilder| ...)no_styles_movement()on_changed(|&str| ...)on_submit(|&str| ...)text_input::styling module (feature: text-styling)Important public functions:
escape_str(s): Escapes all style delimiters in a stringstrip_styling(s): Removes all style tags, returning plain contentcursor_to_content(s, pos): Converts cursor pos to content character indexcontent_to_cursor(s, pos, snap_to_content): Converts content character index to cursor pos. When snap_to_content is true, the cursor skips structural positions like } and lands on the next visible character.cursor_to_raw(s, pos): Converts styled cursor pos to raw index in the stringraw_to_cursor(s, pos): Converts raw string index to cursor position in the styled stringDifferent types of positions:
raw position: index in the full styled string, including markup such as {color=...|...} and escape characters.cursor position: visual cursor index used by text input editing. This includes visible characters and styling structure positions that the cursor can pass through.content position: index in plain visible content after styling is stripped.IdId::new(label)Id::new_index(label, index)Id::new_index_seed(label, index, seed)From<&'static str> and From<(&str, u32)>Inside the active .children(|ui| ...) scope:
ui.hovered()ui.pressed()ui.just_pressed()ui.just_released()ui.focused().on_hover(|id, pointer| ...).on_press(|id, pointer| ...).on_release(|id, pointer, hovered| ...).on_focus(|id| ...).on_unfocus(|id| ...)PointerData:
pointer.position: Vector2pointer.state: PointerState (Idle, PressedThisFrame, Pressed, ReleasedThisFrame)pointer.pressed() -> bool: checks if pointer is currently pressed (Pressed or PressedThisFrame)pointer.released() -> bool: checks if pointer is currently released (Idle or ReleasedThisFrame)pointer.just_pressed() -> bool: checks if pointer was just pressed this frame (PressedThisFrame)pointer.just_released() -> bool: checks if pointer was just released this frame (ReleasedThisFrame)Use .capture() on interactive elements (e.g. buttons inside a larger clickable area) to capture pointer input and prevent it from propagating to parent elements.
Use .preserve_focus() on toolbar-like controls that should not steal text-input focus.
is_pressed(id)is_just_pressed(id)is_just_released(id)pointer_over(id)pointer_over_ids()bounding_box(id)scroll_container_data(id)GraphicAsset:
graphic!("assets/images/logo.png") (macro embeds bytes relative to crate manifest)GraphicAsset { id, data }FontAsset:
font!("assets/fonts/lexend.ttf") (macro embeds bytes relative to crate manifest)FontAsset { id, data }Image inputs accepted by .image(...):
&'static GraphicAssetTexture2Dtinyvg::format::Image (feature: tinyvg)ShaderAsset variants:
shader!("assets/shaders/custom.frag.glsl") (macro embeds shader source)Source { id, fragment }Stored(&'static str)ShaderBuilder:
.uniform(name, value) where value is one of:f32[f32; 2][f32; 3][f32; 4]i32[[f32; 4]; 4]Runtime shader source updates:
set_shader_source(name, fragment_source)ShaderAsset::Stored(name)render_to_texture(width, height, || { draw calls }) -> Texture2Dbuilt-in-shaders)FOIL: u_time (required), u_speed (default 1.0), u_intensity (default 0.3)HOLOGRAPHIC: u_time (required), u_speed (default 1.0), u_saturation (default 0.7)DISSOLVE: u_threshold (required), u_edge_color (required), u_edge_width (default 0.05), u_seed (default 0.0)GLOW: u_glow_color (required), u_glow_radius (default 0.05), u_glow_intensity (default 1.0)CRT: u_line_count (default 100.0), u_intensity (default 0.3), u_time (recommended)GRADIENT_LINEAR: u_color_a (required), u_color_b (required), u_angle (default 0.0, radians)GRADIENT_RADIAL: u_color_a (required), u_color_b (required), u_center (default [0.5, 0.5]), u_radius (default 0.5)GRADIENT_CONIC: u_color_a (required), u_color_b (required), u_center (default [0.5, 0.5]), u_offset (default 0.0), u_hardness (default 0.0)AccessibilityRole variantsNoneButtonLinkHeading { level }LabelStaticTextTextInputTextAreaCheckboxRadioButtonSliderGroupListListItemMenuMenuItemMenuBarTabTabListTabPanelDialogAlertDialogToolbarImageProgressBarAccessibilityBuilder methodsbutton(label: &str)heading(label: &str, level: u8)link(label: &str)static_text(label: &str)checkbox(label: &str)slider(label: &str)image(alt: &str)role(role: AccessibilityRole)label(text: &str)description(text: &str)value(text: &str)value_min(min: f32)value_max(max: f32)checked(checked: bool)focusable()tab_index(index: i32)focus_right(target: impl Into<Id>)focus_left(target: impl Into<Id>)focus_up(target: impl Into<Id>)focus_down(target: impl Into<Id>)disable_ring()ring_color(color: impl Into<Color>)ring_width(width: u16)live_region_polite()live_region_assertive()net)HTTP:
net::get(id, url, |HttpConfig| ...)net::post(...)net::put(...)net::delete(...)net::request(id) -> Option<Request>HttpConfig methods:
header(key, value)body(&str)body_bytes(Vec<u8>)Request methods:
response() -> Option<Result<Arc<Response>, String>>cancel()Response methods:
status() -> u16text() -> &strbytes() -> &[u8]json<T>() (feature: net-json)WebSocket:
net::ws_connect(id, url, |WsConfig| ...)net::ws(id) -> Option<WebSocket>WsConfig methods:
header(key, value)insecure()WebSocket methods:
send(&[u8])send_text(&str)recv() -> Option<WsMessage>close()WsMessage variants:
ConnectedText(String)Binary(Vec<u8>)Error(String)Closedstorage)Storage::new(path).awaitsave_string(path, data).awaitsave_bytes(path, data).awaitload_string(path).awaitload_bytes(path).awaitremove(path).awaitexport(path).awaitjobs)jobs::spawn(id, || async move { ... }, |result| { ... })jobs::running(id)jobs::is_running(id)jobs::list()audio)When audio feature is enabled, macroquad audio APIs are in prelude.
Typical calls:
load_sound(path).awaitload_sound_from_bytes(bytes).awaitplay_sound_once(&sound)play_sound(&sound, PlaySoundParams { .. })stop_sound(&sound)set_sound_volume(&sound, volume)Lerp traitImplemented for:
f32u16Vector2macroquad::prelude::Vec2(f32, f32, f32, f32)(u16, u16, u16, u16)ColorColor-specific helpers:
Color::lerp_srgb(...)Color::lerp_oklab(...)ease_in_quad, ease_out_quad, ease_in_out_quadease_in_cubic, ease_out_cubic, ease_in_out_cubicease_in_quart, ease_out_quart, ease_in_out_quartease_in_sine, ease_out_sine, ease_in_out_sineease_in_expo, ease_out_expo, ease_in_out_expoease_in_back, ease_out_back, ease_in_out_backease_in_elastic, ease_out_elastic, ease_in_out_elasticease_out_bounce, ease_in_bounce, ease_in_out_bounceUse in build.rs with feature shader-build in build-dependencies.
Main API:
ShaderBuild::new().source_dir("...").output_dir("...").slangc_path("...").override_file_type_handler(ext, handler).build()Defaults:
shaders/assets/build/shaders/build/shaders/spirv/plyx CLI Reference for AIIf plyx is available, prefer it for setup and platform builds.
plyx initplyx add [args]plyx skill [--install]plyx apk [--native] [--install] [--auto]plyx webplyx ios [--device] [--actions] [--auto]plyx completions <shell> [--install]plyx initCreates a project, selects a font from Google Fonts, and selects feature flags.
Generates:
<font>.ttf.claude/skills/ply-engine/SKILL.mdplyx addInteractive mode (no args) or non-interactive forms:
plyx add <feature-key>plyx add font <font name>Feature keys recognized by plyx templates:
tinyvgbuilt-in-shadersshader-pipelinetext-stylingnetnet-json (depends on net)audiostorageskillskill and shader-pipeline are plyx concepts, not feature keys in [dependencies]. shader-pipeline adds build-dependencies and build.rs. skill installs .claude/skills/ply-engine/SKILL.md into the project.
plyx webBuilds release wasm and outputs to build/web/:
build/web/app.wasmbuild/web/index.htmlbuild/web/ply_bundle.jsassets/ if presentplyx apkModes:
--native)Useful flags:
--install to install with adb--auto non-interactive modeAPK output location:
target/android-artifacts/release/apk/<crate>.apkplyx iosModes:
--device--actionsRequirements:
xcrun)--device: signing identity, provisioning profile, and entitlementsplyx skillplyx skill prints the bundled skill text to stdout.plyx skill --install installs to ~/.claude/skills/ply-engine/SKILL.md.This section shows some common patterns that emerge when building with Ply. These are just examples, not requirements. You should feel free to deviate from these patterns when the situation calls for it and use appropriate values and variations. We show this with a lot of constants and default values like 24 that you would replace in a real app.
Ply provides engine-managed cursor handling via ui.set_cursor(CursorIcon) and ui.get_cursor() -> CursorIcon:
ui.set_cursor(CursorIcon::Pointer);
let current: CursorIcon = ui.get_cursor();CursorIcon::Pointer.ui.set_cursor(...) persists across frames until explicitly changed, and overwrites ui.get_cursor() immediately.ui.eval() / ui.show() (when not headless).Reusable styling should be plain Rust functions that take and return ElementBuilder.
fn rounded<'ply, S>(el: ElementBuilder<'ply, S>) -> ElementBuilder<'ply, S> {
el.corner_radius(12.0)
}
fn dark_bg<'ply, S>(el: ElementBuilder<'ply, S>) -> ElementBuilder<'ply, S> {
el.background_color(0x2E2A28)
}
dark_bg(rounded(ui.element()))
.width(grow!())
.height(fixed!(60.0))
.children(|ui| {
ui.text("Styled with functions", |t| t.font_size(20).color(0xFFFFFF));
});For parameterized styles:
fn my_style<'ply, S>(el: ElementBuilder<'ply, S>, bg: u32, radius: f32) -> ElementBuilder<'ply, S> {
el.background_color(bg).corner_radius(radius)
}
my_style(ui.element(), 0x181515, 10.0)
.width(fit!())
.height(fit!())
.empty();You might want your buttons to use something like this with .with():
fn button(ui: &mut Ui, label: &str, mut on_click: impl FnMut() + 'static) {
ui.element().width(fit!()).height(fit!())
.corner_radius(SOME_RADIUS)
.layout(|l| l.padding(SOME_PADDING).align(CenterX, CenterY))
.accessibility(|a| a.button(label))
.on_press(move |_, _| on_click())
.with(|ui, el| {
let bg = if ui.pressed() {
LIGHT_PRIMARY_COLOR
} else if ui.hovered() || ui.focused() {
DARK_PRIMARY_COLOR
} else {
PRIMARY_COLOR
};
el.background_color(bg)
})
.children(|ui| {
ui.text(label, |t| t.font_size(24).color(0xFFFFFF));
});
}.with() executes after the element's ID has been established in .empty() or .children(), so queries like ui.pressed(), ui.hovered(), and ui.focused() accurately refer to this element and el.get_id() is available without needing an extra wrapper element.
if user_data.is_none() {
net::get("user_data", "https://example.com/", |r| r
.header("Authorization", "Bearer token")
);
}
// ...
if user_data.is_none() {
if let Some(req) = net::request("user_data") {
match req.response() {
None => {
ui.text("{swing|Loading...}", |t| t.font_size(24).color(0xFFFFFF));
}
Some(Ok(resp)) => {
if resp.status() == 200 {
let data: UserData = resp.json().unwrap_or_else(|e| {
eprintln!("failed to parse user data: {e}");
UserData::default()
});
user_data = Some(data);
}
}
Some(Err(err)) => {
eprintln!("request failed: {err}");
}
}
}
}jobs::spawn(
"save_game",
|| async move {
Storage::new("my_app").await?
.save_string("save.json",
serde_json::to_string(&game_state).map_err(|e| e.to_string())?
).await
},
|result| {
if let Err(e) = result {
eprintln!("Failed to save game: {e}");
}
},
)?;Using Storage on the main thread will cause frame skips on the web. It's best to use it inside a job like this to avoid blocking the main thread.
Use a static shader asset for normal rendering flows.
static TINT_SHADER: ShaderAsset = ShaderAsset::Source {
id: "tint.frag.glsl",
fragment: r#"#version 100
precision mediump float;
varying vec2 uv;
uniform sampler2D Texture;
uniform vec4 u_tint_color;
uniform float u_tint_strength;
void main() {
vec4 base = texture2D(Texture, uv);
vec3 mixed_rgb = mix(base.rgb, u_tint_color.rgb, clamp(u_tint_strength, 0.0, 1.0));
gl_FragColor = vec4(mixed_rgb, base.a);
}"#,
};
let chart = render_to_texture(400.0, 200.0, || {
clear_background(BLANK);
draw_line(0.0, 180.0, 400.0, 40.0, 3.0, GREEN);
draw_circle(200.0, 110.0, 8.0, RED);
});
ui.element()
.width(fixed!(400.0))
.height(fixed!(200.0))
.image(chart)
.effect(&TINT_SHADER, |s| s
.uniform("u_tint_color", [1.0, 0.85, 0.75, 1.0])
.uniform("u_tint_strength", 0.25f32)
)
.empty();ui.element().id("first").width(fit!()).height(fit!())
.layout(|l| l.padding(SOME_PADDING))
.background_color(FIRST_BUTTON_COLOR)
.accessibility(|a| a.button("First").tab_index(1).focus_right("second"))
.on_press(|_, _| println!("first pressed"); )
.children(|ui| {
ui.text("First", |t| t.font_size(24).color(0xFFFFFF).accessible());
});
ui.element().id("second").width(fit!()).height(fit!())
.layout(|l| l.padding(SOME_PADDING))
.background_color(SECOND_BUTTON_COLOR)
.accessibility(|a| a.button("Second").tab_index(2).focus_left("first"))
.on_press(|_, _| println!("second pressed"); )
.children(|ui| {
ui.text("Second", |t| t.font_size(24).color(0xFFFFFF));
});ui.element().id("editor").width(fixed!(400.0)).height(fixed!(120.0))
.layout(|l| l.padding(BACKGROUND_PADDING))
.background_color(BACKGROUND_COLOR)
.text_input(|t| t
.multiline()
.font_size(24)
.drag_select()
.no_styles_movement()
.on_changed(|text| println!("changed: {text}"))
);
let raw = ui.get_text_value("editor").to_string();
let plain = styling::strip_styling(&raw);
let highlighted = plain.replace("TODO", "{color=#FFC32C|TODO}");
if raw != highlighted {
let cursor = ui.get_cursor_pos("editor");
let content_pos = styling::cursor_to_content(&raw, cursor);
ui.set_text_value("editor", &highlighted);
let new_cursor = styling::content_to_cursor(&highlighted, content_pos, true);
ui.set_cursor_pos("editor", new_cursor);
}.text_input() is a terminal method on ElementBuilder (returning Id), allowing developers to configure layout modifiers like .padding() directly on the housing element itself. The scrollable area includes the padding and scrollbars extend to include the padded perimeter. Also note how the .text_input closure is styled with the letter t on the first line and then the .arguments() on the following lines. This is how you should style builders that stretch across multiple lines.
ui.element().id("tooltip_target").width(fit!()).height(fit!())
.layout(|l| l.padding(5))
.corner_radius(6.0)
.background_color(BUTTON_COLOR)
.children(|ui| {
ui.text("Hover me", |t| t.font_size(24).color(0xE8E0DC));
if ui.hovered() {
ui.element().width(fit!()).height(fit!())
.floating(|f| f
.attach_parent()
.anchor((CenterX, Top), (CenterX, Bottom))
.offset((0.0, -4.0))
)
.background_color(TOOLTIP_COLOR)
.corner_radius(6.0)
.layout(|l| l.padding(5))
.children(|ui| {
ui.text("Tooltip text", |t| t.font_size(16).color(0xFFFFFF));
});
}
});ui.element().width(grow!()).height(grow!())
.layout(|l| l.wrap().wrap_gap(8).gap(8).padding(8))
.overflow(|o| o
.scroll_y()
.scrollbar(|s| s
.width(4.0)
.thumb_color(0xFFFFFF)
.track_color(0x222222)
.hide_after_frames(60)
)
)
.children(|ui| {
for i in 0..200 {
ui.element().id(("chip", i)).width(fit!()).height(fit!())
.layout(|l| l.padding(6))
.background_color(0x333333)
.corner_radius(6.0)
.children(|ui| {
ui.text(&format!("Chip {i}"), |t| t.font_size(16).color(0xFFFFFF));
});
}
});Declare graphic and font assets as static using graphic! and font! macros.
static LOGO: GraphicAsset = graphic!("assets/images/logo.png");
static ICON_TVG: GraphicAsset = graphic!("assets/images/icon.tvg");
static BODY_FONT: FontAsset = font!("assets/fonts/lexend.ttf");
// ...
ui.element().width(fixed!(96.0)).height(fixed!(96.0)).image(&LOGO).empty();
ui.element().width(fixed!(96.0)).height(fixed!(96.0)).image(&ICON_TVG).empty();
ui.text("Body", |t| t.font(&BODY_FONT).font_size(24).color(0xFFFFFF));This part is the definitive reference for producing interfaces and visuals that are both rigorously functional and genuinely memorable. It covers design thinking, aesthetic direction, foundational principles, common failure modes, advanced UX strategy, motion design, and game UI.
Before touching anything visual, understand the context and commit to a direction.
The single biggest failure mode in design is timidity, making choices that offend no one and inspire no one. Bold maximalism and refined minimalism are both valid. What fails is the middle: generic layouts, safe fonts, and evenly-distributed palettes that look like every other product.
Match execution depth to aesthetic vision. A maximalist design needs elaborate layering, rich animation, and complex composition. A minimalist design needs razor-sharp spacing, perfect type, and restraint exercised with intention.
Typography is communication architecture.
Font Personality by Category:
plyx add font NAME to download from Google Fonts.Pairing & Palette:
Move beyond standard grid layouts. Explore:
Repeat visual elements (color palette, header styles, icon library, corner radii, spacing patterns) throughout a design. Repetition is not laziness - it is branding. It lets users relax and engage with content rather than decode the interface.
These are common failure modes that immediately mark a design as amateur.
Avoid the convergent defaults that make designs indistinguishable from each other:
Every design should feel like it was made for this specific context.
Design for different user bases:
Elements that are visually "almost" aligned create tension that reads as disorder.
Motion is communication. All UI movement must justify itself against these five criteria:
Practical priorities:
Choose deliberately. Mixed approaches require strong thematic justification.
Define a visual "world", a set of recurring motifs (shape language, texture, color temperature, typographic character), and enforce it across every element: icons, typography, backgrounds, particle effects, and transitions. If the UI could be lifted out and placed in a different product without looking wrong, it isn't themed.
In high-intensity contexts, layout must allow the user to extract critical information in milliseconds without conscious eye movement. Proximity encodes meaning: a bar next to a name reads as "health"; a counter next to a bar reads as "progress." Design these relationships deliberately.
© TheRedDeveloper, ISC. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 59 other files in the repository root of TheRedDeveloper/ply-engine.
Open the folder on GitHubat commit 52be132
Ply Engine next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Ply Engine this skillTheRedDeveloper/ply-engine | 506 | — | ~10k | Automated safety check: Pass | ISC | |
| Update V8 Versionopeninterpreter/openinterpreter | 69k | 2 repos | ~845 | Automated safety check: Pass | Apache-2.0 | |
| Firecrawl Page Scrape Integrationfirecrawl/firecrawl | 190k | 1 repos | ~944 | Automated safety check: Pass | ISC | |
| Migrate Core Code to Submodulestinyhumansai/openhuman | 42k | — | ~2.6k | Automated safety check: Pass | GPL-3.0 | |
| Rust TDD Workflowrtk-ai/rtk | 83k | — | ~753 | Automated safety check: Notes | Apache-2.0 | |
| OpenLogi macOS Permissions TriageAprilNEA/OpenLogi | 23k | — | ~2.5k | Automated safety check: Notes | Apache-2.0 |
openinterpreter/openinterpreter
Bumps the pinned v8 and rusty_v8 versions in Codex, validates the release-candidate path with the v8-canary check, and traces failures to upstream build changes.
firecrawl/firecrawl
Adds Firecrawl's /scrape endpoint to application code to pull markdown, HTML, links, screenshots or structured data from a single known URL.
tinyhumansai/openhuman
Plans and carries out moving non-host-specific code and its tests from the OpenHuman core into vendored tiny submodule libraries, then releases the submodule and re-pins the host.
rtk-ai/rtk
Enforces red-green-refactor for Rust work, with idiomatic test patterns, a naming convention and a pre-commit gate of cargo fmt, clippy and test.
AprilNEA/OpenLogi
Decides whether an OpenLogi device problem on macOS is a privacy-permission (TCC) problem, using agent log lines, and says which identity needs which grant.
farm-fe/farm
Guide for writing idiomatic Rust code based on Apollo GraphQL's best practices handbook.
Works with
Complete implementation and design guide for Ply. An agent skill from TheRedDeveloper/ply-engine. Ply Engine is an agent skill from TheRedDeveloper/ply-engine. Complete implementation and design guide for Ply.
Ply Engine fits situations like: A task involves ply-engine APIs; building UI in Rust with Ply.
Run `npx skills add TheRedDeveloper/ply-engine --skill ply-engine -a claude-code`. Or copy the skill folder (the TheRedDeveloper/ply-engine repository) into .claude/skills/ply-engine in your project. Claude Code loads it when a task matches its description.
Run `npx skills add TheRedDeveloper/ply-engine --skill ply-engine -a codex`. Or copy the skill folder (the TheRedDeveloper/ply-engine repository) into .agents/skills/ply-engine in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add TheRedDeveloper/ply-engine --skill ply-engine -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/ply-engine, .gemini/skills/ply-engine, .github/skills/ply-engine and .opencode/skills/ply-engine in your project.
Going by SKILL.md and its folder, Ply Engine needs JavaScript, Java and a shell for the scripts in its folder and the command-line tools its instructions call (cursor). Our summary lists: Node.js; A Bash shell.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Ply Engine is published under the ISC licence (from the LICENSE file in the skill folder). It allows redistribution, so the full SKILL.md is shown on this page.
About 10k tokens (SKILL.md is roughly 41k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Ply Engine: Update V8 Version (openinterpreter/openinterpreter, 69k stars), Firecrawl Page Scrape Integration (firecrawl/firecrawl, 190k stars), Migrate Core Code to Submodules (tinyhumansai/openhuman, 42k stars) and Rust TDD Workflow (rtk-ai/rtk, 83k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
TheRedDeveloper (a GitHub user) maintains it in TheRedDeveloper/ply-engine, which has 506 GitHub stars. The repository was last updated on August 31, 2026.
Source: TheRedDeveloper/ply-engine on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.