Ply Engine

repository·main·Indexed 19 days ago

https://github.com/thereddeveloper/ply-engine

A cross-platform Rust engine for building GPU-accelerated user interfaces targeting Desktop (Linux, macOS, Windows), Mobile (Android, iOS), and Web (WASM). It features a builder pattern for UI elements, a dedicated CLI tool called plyx, and modular capabilities including accessibility (a11y), TinyVG vector graphics, built-in shaders, networking, and persistent storage.

Tokens
30.5K
Snippets
99
Records
147
Agent score
65%

What's inside ply-engine

  1. Adapt UI for Touch vs. Pointer interfaces

    main

    Ensure your interface is usable across different input methods by following these platform-specific constraints:

    Touch Interfaces:

    • Minimum Target Size: Use at least 44×44pt for touch targets.
    • Layout: Use simplified layouts.
    • Affordances: Remove affordances that assume hover states (e.g., don't rely on hover-to-reveal).

    Pointer (Mouse/Keyboard) Interfaces:

    • Precision: Support high density and precision.
    • Shortcuts: Provide keyboard shortcuts for efficiency.
  2. Apply the 4-Point Grid for spacing

    main

    To maintain visual consistency and professional spacing, use a 4-point grid system for all UI dimensions and margins.

    Spacing Rules:

    • Use multiples of 4: 4, 8, 16, 24, 32, 48, 64.
    • Section Breathing Room: Use a minimum of 32px between visually distinct sections.
    • Proximity: Group related elements (like a label and its input) tightly to maintain their relationship.
  3. Processing order and priority of text styles

    main

    When multiple tags are active, they are processed in this strict order. This determines how effects and properties interact:

    1. hide (Effect)
    2. type (Animation)
    3. fade (Animation)
    4. scale (Animation)
    5. transform (Effect)
    6. wave (Effect)
    7. pulse (Effect)
    8. swing (Effect)
    9. jitter (Effect)
    10. gradient (Effect)
    11. opacity (Property)
    12. color (Property)
    13. shadow (Effect)
  4. Handle Text Styling and Cursor Positions

    main

    When the text-styling feature is enabled, you can use the text_input::styling module to manage styled text. There are three distinct types of positions to understand:

    • Raw position: The index in the full string, including markup (e.g., {color=...|...}) and escape characters.
    • Cursor position: The visual index used for editing. This includes visible characters and structural positions the cursor can pass through.
    • Content position: The index in the plain, visible content after all styling is stripped.

    Key utility functions:

    • escape_str(s): Escapes style delimiters.
    • strip_styling(s): Removes all tags.
    • cursor_to_content(s, pos): Converts cursor pos to content index.
    • content_to_cursor(s, pos, snap_to_content): Converts content index to cursor pos.
    • cursor_to_raw(s, pos): Converts styled cursor pos to raw index.
    • raw_to_cursor(s, pos): Converts raw index to cursor position.
  5. Implement semantic color systems

    main

    Assign colors based on consistent meaning to ensure users can interpret the UI without instructional text.

    Standard Color Mappings:

    • Blue: Action / Interactive
    • Red: Error / Danger
    • Green: Success / Confirmation
    • Yellow: Warning / Caution
  6. How text styling categories work

    main

    Text styling is divided into three functional categories:

    1. Properties: Static attributes applied to the entire text block (e.g., color, opacity).
    2. Effects: Visual or geometric changes applied individually to each character (e.g., wave, jitter, shadow).
    3. Animations: Time-based transitions (entry or exit) that require a unique id to track state (e.g., type, fade, scale).
  7. Create reusable styling functions with ElementBuilder

    main

    Reusable styles should be implemented as plain Rust functions that accept and return an ElementBuilder. This allows for composable UI styling.

    Non-parameterized style:

    fn rounded(el: ElementBuilder<'_, ()>) -> ElementBuilder<'_, ()> {
      el.corner_radius(12.0)
    }

    Parameterized style:

    fn my_style(el: ElementBuilder<'_, ()>, bg: u32, radius: f32) -> ElementBuilder<'_, ()> {
      el.background_color(bg).corner_radius(radius)
    }
    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));
      });
  8. Configure Layout with LayoutBuilder

    main

    The .layout() method on an ElementBuilder accepts a closure providing a LayoutBuilder.

    Important: Builder closures must use chain-return style. Do not use curly braces with a trailing return; instead, chain the methods directly.

    // DO THIS:
    .layout(|l| l
      .direction(TopToBottom)
      .align(CenterX, Top)
      .gap(12)
      .padding(14)
    )
    
    // DO NOT DO THIS:
    .layout(|l| {
      l.direction(TopToBottom)
        .align(CenterX, Top)
        .gap(12)
        .padding(14);
      l
    })
  9. Manage the cursor icon with a deferred update pattern

    main

    To prevent the cursor from changing mid-frame and to allow for custom logic, use a thread_local! RefCell<CursorIcon> to store the desired state.

    1. Call set_cursor(icon) in event handlers to update the desired state.
    2. Call apply_cursor() at the end of the frame to actually update the system mouse cursor and reset the local state to CursorIcon::Default.
    use std::cell::RefCell;
    
    thread_local! {
      static CURSOR: RefCell<CursorIcon> = RefCell::new(CursorIcon::Default);
    }
    
    fn set_cursor(icon: CursorIcon) {
      CURSOR.with(|c| *c.borrow_mut() = icon);
    }
    
    fn apply_cursor() {
      CURSOR.with(|c| {
        set_mouse_cursor(*c.borrow());
        *c.borrow_mut() = CursorIcon::Default;
      });
    }
  10. Design for different user bases

    main

    Tailor your UX strategy based on the user's familiarity with the system:

    • New Users: Prioritize simplicity, clear 'getting started' pathways, and reduced cognitive load.
    • Returning Users: Surface current progress, quick-access features, and session continuations.
    • Power Users: Expose advanced statistics, shortcuts, and optimization tools without hiding capability behind beginner guardrails.
  11. Setup a Ply Engine application

    main

    To use Ply Engine, always include the prelude in your module to access core types and utilities:

    use ply_engine::prelude::*;

    An application typically follows a pattern of initializing Ply with a default font, running a main loop, beginning a Ui context, defining elements, and then calling ui.show() to render.

    Note that Ui dereferences to Ply, so all Ply methods are available directly on the ui object.

    use ply_engine::prelude::*;
    
    #[macroquad::main(window_conf)]
    async fn main() {
      static DEFAULT_FONT: FontAsset = FontAsset::Path("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;
      }
    }
  12. Manage fonts with plyx

    main

    If your project requires specific fonts that are not currently in your assets, you can use the plyx CLI to download them directly from Google Fonts.

    Usage:

    plyx add font <NAME>

    Replace <NAME> with the name of the font you wish to add.

    plyx add font NAME