| |
| <meta charset="utf-8" lang="en"><style class="fallback"> |
| body{visibility:hidden;} |
| table { |
| border-collapse: collapse; |
| } |
| |
| .interpolation td { |
| border: 0px solid #888; |
| margin: 0; |
| padding: 0.5em; |
| } |
| |
| td { |
| border: 1px solid #888; |
| margin: 0; |
| padding: 0.5em; |
| } |
| th { |
| background-color: #AAA; |
| color: #FFF; |
| border: 1px solid #888; |
| margin: 0; |
| padding: 0.5em; |
| text-align: left; |
| } |
| |
| tr:nth-child(even) { |
| background-color: #EEE; |
| } |
| |
| .md h1, .md .nonumberh1 {page-break-before:always} |
| |
| @media screen and (min-width: 110em) { |
| .md .longTOC, .md .mediumTOC, .md .shortTOC { |
| max-width: 50%; |
| width: 25%; |
| max-height: 100%; |
| float: left; |
| position: fixed; |
| left: 20px; |
| top: 20px; |
| overflow-y: auto; |
| } |
| |
| .md table { |
| margin-left: 0 !important; |
| margin-right: auto !important; |
| } |
| |
| .md canvas { |
| display: block !important; |
| margin-left: auto !important; |
| margin-right: auto !important; |
| padding-bottom: 20px; /* Optional: adds some breathing room */ |
| } |
| } |
| </style> |
| <script type="module"> |
| import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; |
| mermaid.initialize({ startOnLoad: true }); |
| </script> |
| |
| **RemoteCompose Wire Format v1.1.0** |
| February 09, 2026 |
| |
| |
| *Current number of operations: 141 (0 not fully documented)* |
| |
| !!! ERROR |
| This is work in progress and subject to modifications. |
| |
| RemoteCompose is a standalone content format for Android. It consists |
| of a list of operations providing rendering, layout, animation, expression evaluation and bindings. |
| |
| Wire Format overview |
| ====== |
| |
| The RemoteCompose wire format is a simple flat list of Operations, |
| serialized in a binary format (see next sections for a detailed description of each operation). |
| |
| !!! WARNING |
| This wire format is intended to be used for fast generation and transport. For display purposes, |
| a more runtime-appropriate representation (i.e. a tree of operations and containers) should be created and used instead. |
| |
| Each list of operations contains as first element a Header (see section [Header]). |
| |
| |Operations| |
| |:----:| |
| | **Header** | |
| | *Operation 1* | |
| | *Operation 2* | |
| | *Operation 3* | |
| | ... | |
| | *Operation n* | |
| |
| Encoding |
| -------- |
| |
| Operations are encoded in binary, using the following types: |
| |
| | Type | Size (in bytes) | |
| |:----:|:----:| |
| | BYTE | 1 | |
| | BOOLEAN | 1 | |
| | SHORT | 2 | |
| | INT | 4 | |
| | FLOAT | 4 | |
| | LONG | 8 | |
| | DOUBLE | 8 | |
| | BUFFER | 4 + Size | |
| | UTF8 | BUFFER | |
| |
| A Buffer is simply encoded as the size of the buffer in bytes (encoded as an INT, so 4 bytes) |
| followed by the byte buffer itself. UTF8 payload is simply encoded as a byte Buffer using encodeToByteArray(). |
| |
| |
| |
| Components & Layout |
| ------------------- |
| |
| Operations can further be grouped in components, surrounded by ComponentStart (section [ComponentStart]) |
| and ComponentEnd (section [ComponentEnd]). |
| |
| |Operations| |
| |:----:| |
| | ... | |
| | **ComponentStart** | |
| | *Operation 1* | |
| | *Operation 2* | |
| | *Operation 3* | |
| | ... | |
| | *Operation n* | |
| | **ComponentEnd** | |
| | ... | |
| |
| More specialized components can be created by using alternative layout operations instead of ComponentStart: |
| |
| - RootLayoutComponent |
| - BoxLayout |
| - RowLayout |
| - ColumnLayout |
| |
| Layout managers such as BoxLayout, RowLayout, ColumnLayout provide a way to layout Components. |
| Such layoutable/resizable components are structured slightly differently: |
| |
| |Operations| | | |
| |:----: |:----:| :----: | |
| | **Header** | || |
| | ... | ... | ... | |
| | **RootLayoutComponent** | | | |
| | | **RowLayout** | | |
| | ... | ... | ... | |
| | | **RowLayout** | | |
| | | *Modifier 1* | | |
| | | *Modifier 2* | | |
| | ... | ... | ... | |
| | | *Modifier n* | | |
| | | | **LayoutComponentContent** | |
| | | | *Component 1* | |
| | | | *Component 2* | |
| | | | ... | |
| | | | *Component n* | |
| | | | **ComponentEnd** | |
| | | **ComponentEnd** | | |
| | **ComponentEnd** | | | |
| |
| |
| In the example above, you can see that RowLayout is comprised of two sections -- a list of Modifier operations followed by a LayoutComponentContent component, |
| containing the components to be laid out. |
| |
| # Layout overview |
| |
| ## Overview |
| |
| The layout system work with **LayoutManager** that can layout **LayoutComponent** and **Component**. |
| LayoutComponents are able to be laid out as well as resized, while Components can only be laid out. |
| |
| The following diagram shows the runtime class hiearchy: |
| |
| <pre> |
| <div class="mermaid"> |
| --- |
| config: |
| class: |
| hideEmptyMembersBox: true |
| --- |
| classDiagram |
| Component <|-- RootLayoutComponent |
| Component <|-- LayoutComponent |
| LayoutComponent <|-- LayoutManager |
| LayoutManager <|-- BoxLayout |
| LayoutManager <|-- ColumnLayout |
| LayoutManager <|-- RowLayout |
| LayoutManager <|-- FitBoxLayout |
| LayoutManager <|-- CoreText |
| BoxLayout <|-- CanvasLayout |
| ColumnLayout <|-- CollapsibleColumnLayout |
| RowLayout <|-- CollapsibleRowLayout |
| RowLayout <|-- FlowLayout |
| LayoutManager <|-- ImageLayout |
| LayoutManager <|-- StateLayout |
| </div> |
| </pre> |
| |
| ## Components |
| |
| **Component** is the base class representing Origami Components. |
| |
| We create components when loading the document, out of the **ComponentStart** |
| and **ComponentEnd** operations in the original operations stream, grouping a list of operations: |
| |
| <pre> |
| <div class="mermaid"> |
| graph LR |
| operations["`ComponentStart |
| Operation 1 |
| ... |
| Operation n |
| ComponentEnd 3`"] |
| operations --> Result |
| |
| Result |
| subgraph Result [Runtime Component] |
| Component --> OP1[Operation 1] |
| Component --> OP2[...] |
| Component --> OP3[Operation n] |
| end |
| </div> |
| </pre> |
| |
| ## LayoutComponents |
| |
| **LayoutComponent** represents layoutable components, which can be measured and may layout sub-components. |
| It keeps track of a list of modifiers as well as a list of child components. |
| |
| <pre> |
| <div class="mermaid"> |
| graph TD |
| LC[LayoutComponent] |
| LC --> Mods[Modifiers] |
| Mods --> M1[modifier 1] |
| Mods --> M2[modifier 2] |
| Mods --> M3[...] |
| Mods --> MN[modifier n] |
| |
| LC --> Children[children] |
| Children --> C1[component 1] |
| Children --> C2[component 2] |
| Children --> C3[...] |
| Children --> CN[component n] |
| </div> |
| </pre> |
| |
| ### Modifiers |
| |
| Modifiers are a list of operations that change the way a component can be displayed. |
| |
| We have several modifiers that are related to the layout itself: |
| |
| | Modifiers | Role | |
| |--------------------------|---------------------------| |
| | WidthModifierOperation | width as fixed dimension | |
| | HeightModifierOperation | height as fixed dimension | |
| | PaddingModifierOperation | padding information | |
| |
| As well as modifiers that are able to change the way the component paint itself: |
| |
| | Modifiers | Role | |
| |----------------------------------|------------------------------------| |
| | BackgroundModifierOperation | Draw a plain color background | |
| | BorderModifierOperation | Draw a border around the component | |
| | ClipRectModifierOperation | Add a clipping rect | |
| | RoundedClipRectModifierOperation | Add a clipping round rect | |
| |
| Modifiers are being applied in order, with Size and Padding impacting the draw-related |
| modifiers. Modifiers are doing a simple version of the layout pass (i.e. we don't ask modifiers |
| to measure and then layout them, we only layout them). |
| |
| Padding modifiers can be used before or after the content modifiers, which means that we do not |
| need a "margin" concept as in other UI systems. Instead we can set up something like: |
| |
| <pre> |
| <div class="mermaid"> |
| graph LR |
| P1[Padding 4] --> BG[Background] |
| BG --> P2[Padding 8] |
| P2 --> Content[/Content/] |
| </div> |
| </pre> |
| |
| Which would result in a "margin" of 4 followed by painting the background, with a "padding" of 8 |
| for the actual content. |
| |
| #### Sizing |
| |
| If a component is set to compute its own size (WRAP), Padding will be added to the computed size. |
| |
| But to note, if a component uses a modifier for indicating a fixed dimension, such as: |
| |
| <pre> |
| <div class="mermaid"> |
| graph LR |
| P1[Padding 4] --> S[Size 10x10] |
| S --> P2[Padding 2] |
| </div> |
| </pre> |
| |
| The computed size of the component for its parent layout manager will be 14x14. |
| |
| |
| #### Borders |
| |
| Borders are always applied on top of the content, so putting the border before a background or after would result in the same visuals: |
| |
| |
| <pre> |
| <div class="mermaid"> |
| graph LR |
| B1[Border] --> BG1[Background] |
| BG1 == "visual equivalent" ==> BG2[Background] |
| BG2 --> B2[Border] |
| </div> |
| </pre> |
| |
| Borders are purely a visual decoration, they do not impact sizing/padding, but they are impacted |
| by them, so have to be evaluated in order. |
| |
| |
| |
| ## Layout Managers |
| |
| The actual layout managers are implemented as subclass of LayoutManager: |
| |
| | Layout Manager | Role | |
| |-------------------------|---------------------------------------------------| |
| | BoxLayout | Layout elements in the same place | |
| | RowLayout | Layout elements in a row | |
| | ColumnLayout | Layout elements in a column | |
| | CollapsibleRowLayout | as RowLayout, but skip elements that don't fit | |
| | CollapsibleColumnLayout | as ColumnLayout, but skip elements that don't fit | |
| | FitBoxLayout | pick the first child that fits | |
| | FlowLayout | as RowLayout, but wrap elements if they don't fit | |
| |
| ### Scroll areas |
| |
| Scroll areas are handled as subclasses of LayoutContent: |
| |
| <pre> |
| <div class="mermaid"> |
| graph TD |
| SCH[ScrollContentHost] |
| SCH --> SC[ScrollContent] |
| SCH --> LC[LayoutContent] |
| </div> |
| </pre> |
| |
| ## Layout / Measure cycle |
| |
| For a runtime document, we end up with the following structure for the layout operations: |
| |
| <pre> |
| <div class="mermaid"> |
| graph TD |
| RLC[RootLayoutComponent] |
| RLC --> LM1[LayoutManager] |
| LM1 --> LM2[LayoutManager] |
| LM2 --> C1[Component] |
| LM2 --> C2[Component] |
| LM1 --> C3[Component] |
| </div> |
| </pre> |
| |
| The general layout / measure cycle works as follow: |
| - for each LayoutComponent, measure their children recursively |
| - capture that measure in a MeasurePass object |
| - then recursively layout the components by using the MeasurePass information. |
| |
| <pre> |
| <div class="mermaid"> |
| graph TD |
| M[Measure] |
| M -- "ideally single pass, using intrinsic sizes (multi-measure is discouraged, but allowed)" --> M |
| M --> MP[MeasurePass] |
| MP -- "contains position+size of all the components" --> L[Layout] |
| L -- "we can animate upon receiving a new layout(measurePass) request " --> A[Animate] |
| </div> |
| </pre> |
| |
| 1. RootLayoutComponent.layout(width, height) will measure all its children (Components), and gather |
| the result of the measure in a MeasurePass object. The MeasurePass is then used to layout |
| the components. The measure on its own never change the components, it's only role is to |
| gather the resulting dimensions / positions. |
| |
| 2. Components implements the Measurable interface. |
| |
| 3. The measure pass is ideally done as a single pass, asking each child to measure itself |
| given min/max constraints for width and height: the contract for the child is to enforce |
| those min/max constraints and set a size that is within those. |
| |
| 4. By default, when a component receive a new layout with a measure information, we animate |
| the change. |
| |
| ## Measure |
| |
| Measure is done by asking components to add a ComponentMeasure to MeasurePass; |
| ComponentMeasure contains the position, dimension and visibility of the component. |
| Each component has an implicit ID that we use to map a given ComponentMeasure to a component. |
| |
| The measure itself consists in passing a set of (min, max) for width and height to the component. |
| |
| As part of the measurement, the list of modifiers may need to be evaluated (in order), to take in account |
| dimension modifiers (size, padding..). |
| |
| ### Multi-measure |
| |
| In order to limit the needs for multi-measures, Measurable components have the concept |
| of intrinsic sizes (min & max) -- they should be handled as equivalent to a measure query, |
| but should be cached (or even pre-cached) by the component. |
| |
| Layout managers then can ask each child to measure themselves given min/max constraints, |
| or if they need to, ask the child for their intrinsic sizes, then measure them. |
| |
| Layout managers can call measure multiple times on children (i.e. there is |
| nothing preventing multi-measure) but this should be avoided as possible, given the |
| potential performance impact (re-measuring a layout high in the hierarchy has the potential |
| for a large performance hit). |
| |
| A component can indicate that its size/content changed and thus a layout pass is needed |
| by calling invalidateMeasure(); this will flag the chain of component until the root |
| and trigger the layout pass. |
| |
| ## Request layout / Request measure |
| |
| Generally, the layout system will react to change in dimensions, propagated from the root. |
| Another important mechanism is the ability for components to invalidate themselves and trigger |
| a relayout/remeasure pass. |
| |
| Components can do that simply by calling `invalidateMeasure()` -- this will mark themselves |
| as needing both a repaint and a remeasure, and will walk the tree up until the root component, |
| invalidating them as the tree is traversed. The root component then uses this to trigger a measure pass. |
| |
| # Scrolling Architecture in RemoteCompose |
| |
| RemoteCompose provides a physics-based, hierarchical scrolling system that supports orthogonal nesting (e.g., horizontal rows inside vertical columns) and dynamic state synchronization. |
| |
| ## Core Concepts |
| |
| ### 1. Coordinate Spaces |
| To handle deep nesting and scrolling reliably, the interaction engine uses three distinct coordinate spaces: |
| |
| | Space | Reference | Usage | |
| | :--- | :--- | :--- | |
| | **Root-Relative** | Document origin (0,0) | Used for top-level hit testing (`contains(x, y)`). | |
| | **Viewport-Relative** (`lx, ly`) | Component top-left on screen | Used by Modifiers (ripples, scroll logic). | |
| | **Content-Relative** (`cx, cy`) | Component's scrollable origin | Passed to children so they can ignore parent scroll. | |
| |
| ### 2. The Scroll Delegate |
| Scrolling is not hardcoded into layouts. Instead, `LayoutComponent` uses a `ScrollDelegate` interface. If a component has a `ScrollModifierOperation` in its modifier list, it registers itself as the delegate for horizontal or vertical axes. |
| |
| - **Measure Phase**: The layout manager queries the delegate for the "content dimension" (total scrollable area). |
| - **Layout Phase**: The delegate updates the component's internal scroll offsets based on user interaction or bound variables. |
| - **Paint Phase**: `LayoutComponent` applies a translation transformation (`getScrollX()`, `getScrollY()`) before painting its children. |
| |
| ## The Interaction Pipeline |
| |
| ### 1. Event Entry (`CoreDocument`) |
| Touch events enter via `CoreDocument.touchDown/Drag/Up`. The document tracks "applied touch operations"—components currently being interacted with—to bypass hit-testing during a drag. |
| |
| ### 2. Recursive Dispatch (`Component`) |
| Events propagate down the tree in **reverse drawing order** (top-most component first). |
| - A component first performs a **Root-Relative** hit test. |
| - If it contains the point, it calculates the **Viewport** and **Content** offsets. |
| - It dispatches to children using the **Content** space. |
| - It then dispatches to its own modifiers using the **Viewport** space. |
| |
| ### 3. Event Consumption |
| Interaction handlers return a `boolean`. |
| - If a child component consumes an event (e.g., a button click), sibling components are ignored. |
| - **Crucially**, parent modifiers (like a `ScrollModifier`) are still notified even if a child consumed the event. This allows a nested list to scroll even if the user started the drag on a button. |
| |
| ## Physics and State (`TouchExpression`) |
| |
| The `ScrollModifierOperation` typically hosts a `TouchExpression`. This engine handles: |
| - **Velocity Tracking**: Calculates pixels-per-second during a drag. |
| - **Fling/Easing**: When the user releases, it uses a `VelocityEasing` curve to continue the scroll. |
| - **Notches**: Supports snapping to specific positions (even spacing, absolute points, or percentages). |
| |
| ## Nested Scrolling Logic |
| |
| RemoteCompose handles nested scrolls by utilizing the "Orthogonal Capture" rule: |
| 1. A vertical scroll only consumes the **Y** component of a drag. |
| 2. A horizontal scroll only consumes the **X** component. |
| 3. Because propagation continues up the parent chain for modifiers, a vertical swipe inside a horizontal row will be ignored by the row's scroll logic but captured by the parent column's scroll logic. |
| |
| ## Layout Integration |
| |
| Layout managers like `ColumnLayout` and `RowLayout` are "scroll-aware": |
| - During `internalLayoutMeasure`, they check `mComponentModifiers.hasVerticalScroll()`. |
| - If true, they allow the content to exceed the host's viewport height. |
| - They then call `setVerticalScrollDimension()` to inform the modifier of the total scrollable range, which the `TouchExpression` uses for boundary clamping. |
| # Click Handling and Actions in RemoteCompose |
| |
| RemoteCompose provides a flexible click system based on modifiers that can trigger both internal state changes and host-side callbacks. |
| |
| ## Click Propagation Process |
| |
| Click events follow a similar propagation path to touch events but are specifically triggered on the "up" phase of a gesture if the movement threshold hasn't been exceeded. |
| |
| ### 1. Root Dispatch (`CoreDocument`) |
| When a user clicks, `CoreDocument.onClick(x, y)` is called with root-relative coordinates. |
| |
| ### 2. Backward Search (`Component`) |
| The document iterates through its component tree in **reverse drawing order** (top-to-bottom). For each component: |
| - It checks if the point `(x, y)` is within the component's bounds via `contains(x, y)`. |
| - If contained, it recursively calls `onClick` on its children. |
| - If no child handles the click, it dispatches to its own `onClick` handlers (usually via `ComponentModifiers`). |
| |
| ### 3. Local Transformation |
| Before reaching a `ClickModifierOperation`, the coordinates are transformed: |
| - **Root Space**: Used for initial hit detection. |
| - **Content Space**: Passed to children and modifiers, accounting for parent scrolling and padding. |
| |
| ## Click Modifiers (`ClickModifierOperation`) |
| |
| The primary way to make a component interactive is by adding a click modifier. |
| |
| ### Interaction Features |
| - **Ripple Animation**: Triggers a visual feedback ripple at the exact touch location. The ripple uses `Easing.CUBIC_STANDARD` and animates both color and radius over 1000ms. |
| - **Haptic Feedback**: Automatically triggers a host haptic effect (type 3) upon a successful click. |
| - **Role & Semantics**: Identifies the component as a `Role.BUTTON` for accessibility services (e.g., TalkBack). |
| |
| ## Actions |
| |
| A `ClickModifierOperation` acts as a container for one or more `ActionOperation`s. |
| |
| | Action Type | Description | |
| | :--- | :--- | |
| | **ValueChange** | Updates a RemoteCompose variable (Float, String, or Boolean). Useful for toggling states like `isExpanded` or `clickCount`. | |
| | **HostAction** | Sends a metadata string back to the host application (Android/iOS). This is used for navigation or triggering native logic. | |
| | **Custom Actions** | Can be implemented to perform complex sequences, such as multiple variable updates or conditional logic. | |
| |
| ## Interaction with Scrolling |
| |
| The system ensures that clicks and scrolls don't conflict: |
| 1. **Threshold**: If a touch move exceeds a small distance threshold (e.g., 5-10 pixels), the gesture is "captured" by a scroll parent. |
| 2. **Cancellation**: Once captured by a scroll, any pending click animations or actions on child components are cancelled. |
| 3. **Consumption**: If a click is handled by a child, the event returns `true`, stopping further propagation to parent click handlers. |
| # Document Protocol Operations |
| Core document metadata, versioning, themes, and high-level behaviors. |
| |
| Operations in this category: |
| | ID | Name | Version | Size (bytes) |
| | ---- | ---- | ---- | ---- | |
| | 0 | Header | v6 | 29 |
| | 63 | Theme | v6 | 5 |
| | 185 | Rem | v7 | 1 |
| | 214 | ContainerEnd | v6 | 1 |
| |
| ## Header |
| Document metadata, containing the version, original size & density, capabilities mask |
| |
| 6 Fields, total size 29 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>Header</td><td>Value: 0</td></tr> |
| <tr><td>INT</td><td>majorVersion</td><td>Major version</td></tr><tr><td>INT</td><td>minorVersion</td><td>Minor version</td></tr><tr><td>INT</td><td>patchVersion</td><td>Patch version</td></tr><tr><td>INT</td><td>width</td><td>Document width in pixels</td></tr><tr><td>INT</td><td>height</td><td>Document height in pixels</td></tr><tr><td>LONG</td><td>capabilities</td><td>Capabilities mask</td></tr></table> |
| |
| |
| ## Theme |
| Set a theme |
| |
| 1 Fields, total size 5 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>Theme</td><td>Value: 63</td></tr> |
| <tr><td>INT</td><td>THEME</td><td>theme id</td></tr></table> |
| |
| ### THEME |
| | Name | Value | |
| | ---- | ---- | |
| | UNSPECIFIED | -1 |
| | DARK | -2 |
| | LIGHT | -3 |
| ### Theme Illustration |
| |
| The `Theme` operation allows the document to adapt its visual appearance (colors, styles) based on the active theme (e.g., Light or Dark mode). |
| |
| <div id="themeContainer"> |
| <canvas id="themeCanvas_v1" width="500" height="200" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var themeTimer = 0; |
| function draw() { |
| var canvas = document.getElementById('themeCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var isDark = (Math.floor(themeTimer / 100) % 2 === 1); |
| themeTimer++; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444'; |
| var bg = isDark ? '#333' : '#fff'; |
| var textCol = isDark ? '#eee' : '#444'; |
| var primaryCol = isDark ? '#8ab4f8' : '#0047AB'; |
| |
| ctx.fillStyle = bg; |
| ctx.fillRect(0, 0, 500, 200); |
| |
| // Component Representation |
| ctx.fillStyle = primaryCol; |
| ctx.fillRect(150, 50, 200, 100); |
| ctx.strokeStyle = textCol; |
| ctx.lineWidth = 2; |
| ctx.strokeRect(150, 50, 200, 100); |
| |
| ctx.fillStyle = textCol; |
| ctx.font = 'bold 24px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText(isDark ? 'DARK MODE' : 'LIGHT MODE', 250, 110); |
| |
| ctx.font = 'bold 18px Arial'; |
| ctx.fillStyle = GRAY; |
| ctx.fillText('Theme switching automatically updates linked colors', 250, 180); |
| |
| requestAnimationFrame(draw); |
| } |
| draw(); |
| })(); |
| </script> |
| |
| |
| |
| ## Rem (added in v7) |
| Embed a remark or comment string in the document |
| |
| 1 Fields, total size 1 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>Rem</td><td>Value: 185</td></tr> |
| <tr><td>UTF8</td><td>text</td><td>The comment string</td></tr></table> |
| |
| |
| ## ContainerEnd |
| End tag for a container component (Row, Column, etc.) |
| |
| 0 Fields, total size 1 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ContainerEnd</td><td>Value: 214</td></tr> |
| </table> |
| |
| # Data Operations |
| Definitions of static constants, named variables, collections (lists/maps), and resource data (Bitmaps, Fonts). |
| |
| Operations in this category: |
| | ID | Name | Version | Size (bytes) |
| | ---- | ---- | ---- | ---- | |
| | 80 | FloatConstant | v6 | 9 |
| | 101 | BitmapData | v6 | 13 |
| | 137 | NamedVariable | v6 | 9 |
| | 140 | IntegerConstant | v6 | 9 |
| | 143 | BooleanConstant | v6 | 6 |
| | 145 | DataMapIds | v6 | 9 |
| | 146 | IdListData | v6 | 9 + null x 4 |
| | 147 | IdListData | v6 | 9 + null x 4 |
| | 148 | LongConstant | v6 | 13 |
| | 154 | DataMapLookup | v6 | 13 |
| | 189 | FontData | v7 | 9 |
| | 197 | DataDynamicListFloat | v7 | 9 |
| | 198 | UpdateDynamicFloatList | v7 | 13 |
| |
| ## FloatConstant |
| A float and its associated id |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>FloatConstant</td><td>Value: 80</td></tr> |
| <tr><td>INT</td><td>id</td><td>id of the Float constant</td></tr><tr><td>FLOAT</td><td>value</td><td>32-bit float value</td></tr></table> |
| |
| |
| ## BitmapData |
| Embed or reference bitmap image data |
| |
| 4 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>BitmapData</td><td>Value: 101</td></tr> |
| <tr><td>INT</td><td>imageId</td><td>The ID of the bitmap</td></tr><tr><td>INT</td><td>widthAndType</td><td>Encoded width and image type</td></tr><tr><td>INT</td><td>heightAndEncoding</td><td>Encoded height and data encoding</td></tr><tr><td>BYTE[]</td><td>bitmap</td><td>The raw or encoded bitmap data</td></tr></table> |
| |
| |
| ## NamedVariable |
| Add a string name for an ID |
| |
| 3 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>NamedVariable</td><td>Value: 137</td></tr> |
| <tr><td>INT</td><td>varId</td><td>id to label</td></tr><tr><td>INT</td><td>varType</td><td>The type of variable</td></tr><tr><td>UTF8</td><td>name</td><td>String</td></tr></table> |
| |
| |
| ## IntegerConstant |
| A integer and its associated id |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>IntegerConstant</td><td>Value: 140</td></tr> |
| <tr><td>INT</td><td>id</td><td>id of the Int constant</td></tr><tr><td>INT</td><td>value</td><td>32-bit int value</td></tr></table> |
| |
| |
| ## BooleanConstant |
| A boolean and its associated id |
| |
| 2 Fields, total size 6 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>BooleanConstant</td><td>Value: 143</td></tr> |
| <tr><td>INT</td><td>id</td><td>id of Int</td></tr><tr><td>BYTE</td><td>value</td><td>8-bit 0 or 1</td></tr></table> |
| |
| |
| ## DataMapIds |
| Encode a collection of named variable IDs |
| |
| 3 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DataMapIds</td><td>Value: 145</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the map</td></tr><tr><td>INT</td><td>length</td><td>Number of entries</td></tr><tr><td>REPEATED DATA</td><td colspan="2"><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>UTF8</td><td>name</td><td>The name of the entry</td></tr><tr><td>INT</td><td>type</td><td>The type of the entry</td></tr><tr><td>INT</td><td>id</td><td>The ID of the variable</td></tr></table></td></tr></table> |
| |
| |
| ## IdListData |
| A list of IDs |
| |
| 3 Fields, total size 9 + null x 4 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>IdListData</td><td>Value: 146</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the list</td></tr><tr><td>INT</td><td>length</td><td>Number of IDs</td></tr><tr><td>INT[]</td><td>ids</td><td>The array of IDs</td></tr></table> |
| |
| |
| ## IdListData |
| A list of floats |
| |
| 3 Fields, total size 9 + null x 4 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>IdListData</td><td>Value: 147</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the list</td></tr><tr><td>INT</td><td>length</td><td>Number of floats</td></tr><tr><td>FLOAT[]</td><td>values</td><td>The array of floats</td></tr></table> |
| |
| |
| ## LongConstant |
| A long and its associated id |
| |
| 2 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>LongConstant</td><td>Value: 148</td></tr> |
| <tr><td>INT</td><td>id</td><td>id of the Long constant</td></tr><tr><td>LONG</td><td>value</td><td>The long Value</td></tr></table> |
| |
| |
| ## DataMapLookup |
| Look up a value in a data map |
| |
| 3 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DataMapLookup</td><td>Value: 154</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the output value</td></tr><tr><td>INT</td><td>dataMapId</td><td>The ID of the data map</td></tr><tr><td>INT</td><td>stringId</td><td>The ID of the string to look up</td></tr></table> |
| |
| |
| ## FontData (added in v7) |
| Embed raw font data in the document |
| |
| 3 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>FontData</td><td>Value: 189</td></tr> |
| <tr><td>INT</td><td>fontId</td><td>The ID of the font</td></tr><tr><td>INT</td><td>type</td><td>The type of the font (unused)</td></tr><tr><td>BYTE[]</td><td>fontData</td><td>The raw font file data</td></tr></table> |
| |
| |
| ## DataDynamicListFloat (added in v7) |
| A dynamic list of floats |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DataDynamicListFloat</td><td>Value: 197</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the list</td></tr><tr><td>FLOAT</td><td>length</td><td>The length of the list</td></tr></table> |
| |
| |
| ## UpdateDynamicFloatList (added in v7) |
| Update a value in a dynamic float list |
| |
| 3 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>UpdateDynamicFloatList</td><td>Value: 198</td></tr> |
| <tr><td>INT</td><td>arrayId</td><td>The ID of the array</td></tr><tr><td>FLOAT</td><td>index</td><td>The index to update</td></tr><tr><td>FLOAT</td><td>value</td><td>The new value</td></tr></table> |
| |
| # Paint & Styles Operations |
| Management of paint properties, shaders, and complex color definitions (themes, expressions). |
| |
| Operations in this category: |
| | ID | Name | Version | Size (bytes) |
| | ---- | ---- | ---- | ---- | |
| | 40 | PaintData | v6 | 1 + null x 4 |
| | 45 | ShaderData | v6 | 25 + null x 4 + null x 4 |
| | 134 | ColorExpression | v6 | 25 |
| | 138 | ColorConstant | v6 | 9 |
| | 180 | ColorAttribute | v6 | 11 |
| | 196 | ColorTheme | v7 | 21 |
| |
| ## PaintData |
| Encode a Paint object with various properties |
| |
| 1 Fields, total size 1 + null x 4 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>PaintData</td><td>Value: 40</td></tr> |
| <tr><td>INT[]</td><td>paintBundle</td><td>The encoded paint properties</td></tr></table> |
| |
| ### PaintData Illustration |
| |
| The `PaintData` operation encodes properties like style (Fill/Stroke), color, and stroke width. |
| |
| #### Fill vs Stroke |
| <div id="paintStylesContainer"> |
| <canvas id="paintStylesCanvas_v1" width="500" height="250" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('paintStylesCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 500, 250); |
| |
| // STYLE_FILL |
| ctx.beginPath(); |
| ctx.arc(125, 125, 65, 0, 2 * Math.PI); |
| ctx.fillStyle = BLUE; |
| ctx.fill(); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillText('STYLE_FILL', 65, 220); |
| |
| // STYLE_STROKE |
| ctx.beginPath(); |
| ctx.arc(375, 125, 65, 0, 2 * Math.PI); |
| ctx.lineWidth = 7; |
| ctx.strokeStyle = BLUE; |
| ctx.stroke(); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillText('STYLE_STROKE', 300, 220); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## ShaderData |
| Define a shader with associated uniforms |
| |
| 11 Fields, total size 25 + null x 4 + null x 4 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ShaderData</td><td>Value: 45</td></tr> |
| <tr><td>INT</td><td>shaderID</td><td>The ID of the shader</td></tr><tr><td>INT</td><td>shaderTextId</td><td>The ID of the shader source text</td></tr><tr><td>INT</td><td>sizes</td><td>Encoded sizes of uniform maps (float, int, bitmap)</td></tr><tr><td>UTF8</td><td>floatUniformName[0..n]</td><td>Name of float uniform</td></tr><tr><td>INT</td><td>floatUniformLength[0..n]</td><td>Length of float uniform</td></tr><tr><td>FLOAT[]</td><td>floatUniformValues[0..n]</td><td>Values of float uniform</td></tr><tr><td>UTF8</td><td>intUniformName[0..n]</td><td>Name of int uniform</td></tr><tr><td>INT</td><td>intUniformLength[0..n]</td><td>Length of int uniform</td></tr><tr><td>INT[]</td><td>intUniformValues[0..n]</td><td>Values of int uniform</td></tr><tr><td>UTF8</td><td>bitmapUniformName[0..n]</td><td>Name of bitmap uniform</td></tr><tr><td>INT</td><td>bitmapUniformValue[0..n]</td><td>ID of bitmap uniform</td></tr></table> |
| |
| ### ShaderData Illustration |
| |
| The `ShaderData` operation defines complex color effects like gradients or image-based patterns. |
| |
| #### Linear Gradient Example |
| <div id="shaderGradientContainer"> |
| <canvas id="shaderGradientCanvas_v1" width="500" height="160" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('shaderGradientCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 500, 160); |
| |
| var gradient = ctx.createLinearGradient(80, 0, 420, 0); |
| gradient.addColorStop(0, BLUE); |
| gradient.addColorStop(1, GRAY); |
| |
| ctx.fillStyle = gradient; |
| ctx.fillRect(80, 30, 340, 100); |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 20px Arial'; |
| ctx.fillText('Start Color (Blue)', 60, 150); |
| ctx.fillText('End Color (Gray)', 320, 150); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## ColorExpression |
| Define a color via dynamic expression (HSV, ARGB, or Interpolation) |
| |
| 6 Fields, total size 25 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ColorExpression</td><td>Value: 134</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the resulting color</td></tr><tr><td>INT</td><td>mode</td><td>The color calculation mode</td></tr><tr><td>INT</td><td>param1</td><td>First parameter (color1, hue, or alpha depending on mode)</td></tr><tr><td>INT</td><td>param2</td><td>Second parameter (color2, saturation, or red depending on mode)</td></tr><tr><td>INT</td><td>param3</td><td>Third parameter (tween, value, or green depending on mode)</td></tr><tr><td>INT</td><td>param4</td><td>Fourth parameter (blue, only used in ARGB modes)</td></tr></table> |
| |
| ### mode |
| | Name | Value | |
| | ---- | ---- | |
| | COLOR_COLOR_INTERPOLATE | 0 |
| | ID_COLOR_INTERPOLATE | 1 |
| | COLOR_ID_INTERPOLATE | 2 |
| | ID_ID_INTERPOLATE | 3 |
| | HSV_MODE | 4 |
| | ARGB_MODE | 5 |
| | IDARGB_MODE | 6 |
| |
| ## ColorConstant |
| Define a static color and associate it with an ID |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ColorConstant</td><td>Value: 138</td></tr> |
| <tr><td>INT</td><td>colorId</td><td>The ID of the color</td></tr><tr><td>INT</td><td>color</td><td>32-bit ARGB color value</td></tr></table> |
| |
| |
| ## ColorAttribute |
| Extract components (Hue, RGB, Alpha) from a color |
| |
| 3 Fields, total size 11 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ColorAttribute</td><td>Value: 180</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID to output the result to</td></tr><tr><td>INT</td><td>colorId</td><td>The ID of the source color</td></tr><tr><td>SHORT</td><td>type</td><td>The component to extract</td></tr></table> |
| |
| ### type |
| | Name | Value | |
| | ---- | ---- | |
| | COLOR_HUE | 0 |
| | COLOR_SATURATION | 1 |
| | COLOR_BRIGHTNESS | 2 |
| | COLOR_RED | 3 |
| | COLOR_GREEN | 4 |
| | COLOR_BLUE | 5 |
| | COLOR_ALPHA | 6 |
| |
| ## ColorTheme [EXPERIMENTAL] (added in v7) |
| !!! WARNING |
| Experimental operation |
| |
| Define a color that adapts to the current theme (light/dark) |
| |
| 6 Fields, total size 21 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ColorTheme</td><td>Value: 196</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the color</td></tr><tr><td>INT</td><td>groupId</td><td>The ID of the color group name string</td></tr><tr><td>SHORT</td><td>lightModeIndex</td><td>The ID of the color in the light group</td></tr><tr><td>SHORT</td><td>darkModeIndex</td><td>The ID of the color in the dark group</td></tr><tr><td>INT</td><td>lightModeFallback</td><td>32-bit ARGB fallback color for light mode</td></tr><tr><td>INT</td><td>darkModeFallback</td><td>32-bit ARGB fallback color for dark mode</td></tr></table> |
| |
| # Canvas Operations |
| Low-level drawing primitives for shapes, paths, text, and bitmaps, as well as clipping and layer management. |
| |
| Operations in this category: |
| | ID | Name | Version | Size (bytes) |
| | ---- | ---- | ---- | ---- | |
| | 42 | DrawRect | v6 | 17 |
| | 44 | DrawBitmap | v6 | 25 |
| | 46 | DrawCircle | v6 | 13 |
| | 47 | DrawLine | v6 | 17 |
| | 51 | DrawRoundRect | v6 | 25 |
| | 52 | DrawSector | v6 | 25 |
| | 56 | DrawOval | v6 | 17 |
| | 66 | DrawBitmapInt | v6 | 41 |
| | 124 | DrawPath | v6 | 5 |
| | 125 | DrawTweenPath | v6 | 21 |
| | 139 | DrawContent | v6 | 1 |
| | 149 | DrawBitmapScaled | v6 | 49 |
| | 152 | DrawArc | v6 | 25 |
| | 158 | PathTween | v6 | 17 |
| | 159 | PathCreate | v6 | 13 |
| | 160 | PathAppend | v6 | 9 + null x 4 |
| | 173 | CanvasOperations | v6 | 1 |
| | 175 | PathCombine | v6 | 14 |
| | 190 | DrawToBitmap | v7 | 13 |
| |
| ## DrawRect |
| Draw the specified rectangle |
| |
| 4 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawRect</td><td>Value: 42</td></tr> |
| <tr><td>FLOAT</td><td>left</td><td>The left side of the rectangle</td></tr><tr><td>FLOAT</td><td>top</td><td>The top of the rectangle</td></tr><tr><td>FLOAT</td><td>right</td><td>The right side of the rectangle</td></tr><tr><td>FLOAT</td><td>bottom</td><td>The bottom of the rectangle</td></tr></table> |
| |
| ### DrawRect Illustration |
| |
| The `DrawRect` operation renders a rectangle defined by its `left`, `top`, `right`, and `bottom` coordinates. |
| |
| <div id="drawRectContainer"> |
| <canvas id="drawRectCanvas_v1" width="500" height="500" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('drawRectCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 500); |
| |
| var left = 100, top = 100, right = 400, bottom = 350; |
| var width = right - left; |
| var height = bottom - top; |
| |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fillRect(left, top, width, height); |
| ctx.lineWidth = 5; |
| ctx.strokeStyle = BLUE; |
| ctx.strokeRect(left, top, width, height); |
| |
| ctx.font = 'bold 20px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText('left, top (' + left + ', ' + top + ')', left - 80, top - 25); |
| ctx.fillText('right, bottom (' + right + ', ' + bottom + ')', right - 180, bottom + 50); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## DrawBitmap |
| Draw a bitmap |
| |
| 6 Fields, total size 25 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawBitmap</td><td>Value: 44</td></tr> |
| <tr><td>INT</td><td>imageId</td><td>The ID of the bitmap</td></tr><tr><td>FLOAT</td><td>left</td><td>The left side of the image</td></tr><tr><td>FLOAT</td><td>top</td><td>The top of the image</td></tr><tr><td>FLOAT</td><td>right</td><td>The right side of the image</td></tr><tr><td>FLOAT</td><td>bottom</td><td>The bottom of the image</td></tr><tr><td>INT</td><td>descriptionId</td><td>The ID of the content description string</td></tr></table> |
| |
| |
| ## DrawCircle |
| Draw a Circle |
| |
| 3 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawCircle</td><td>Value: 46</td></tr> |
| <tr><td>FLOAT</td><td>centerX</td><td>The x-coordinate of the center of the circle to be drawn</td></tr><tr><td>FLOAT</td><td>centerY</td><td>The y-coordinate of the center of the circle to be drawn</td></tr><tr><td>FLOAT</td><td>radius</td><td>The radius of the circle to be drawn</td></tr></table> |
| |
| ### DrawCircle Illustration |
| |
| The `DrawCircle` operation renders a circle centered at `(centerX, centerY)` with a given `radius`. |
| |
| <div id="drawCircleContainer"> |
| <canvas id="drawCircleCanvas_v1" width="500" height="500" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('drawCircleCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 500); |
| |
| var cx = 250, cy = 250, radius = 125; |
| |
| // Main Circle |
| ctx.beginPath(); |
| ctx.arc(cx, cy, radius, 0, 2 * Math.PI, false); |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fill(); |
| ctx.lineWidth = 5; |
| ctx.strokeStyle = BLUE; |
| ctx.stroke(); |
| |
| // Center Anchor |
| ctx.beginPath(); |
| ctx.arc(cx, cy, 7, 0, 2 * Math.PI, false); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fill(); |
| |
| // Construction Line |
| ctx.beginPath(); |
| ctx.moveTo(cx, cy); |
| ctx.lineTo(cx + radius, cy); |
| ctx.strokeStyle = GRAY; |
| ctx.setLineDash([12, 8]); |
| ctx.lineWidth = 2; |
| ctx.stroke(); |
| ctx.setLineDash([]); |
| |
| // Text |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText('Center (cx, cy)', cx + 15, cy - 15); |
| ctx.font = '20px Arial'; |
| ctx.fillText('radius: ' + radius, cx + 20, cy + 35); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## DrawLine |
| Draw a line segment |
| |
| 4 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawLine</td><td>Value: 47</td></tr> |
| <tr><td>FLOAT</td><td>startX</td><td>The x-coordinate of the start point of the line</td></tr><tr><td>FLOAT</td><td>startY</td><td>The y-coordinate of the start point of the line</td></tr><tr><td>FLOAT</td><td>endX</td><td>The x-coordinate of the end point of the line</td></tr><tr><td>FLOAT</td><td>endY</td><td>The y-coordinate of the end point of the line</td></tr></table> |
| |
| ### DrawLine Illustration |
| |
| The `DrawLine` operation renders a single straight line segment between `(startX, startY)` and `(endX, endY)`. |
| |
| <div id="drawLineContainer"> |
| <canvas id="drawLineCanvas_v1" width="500" height="500" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('drawLineCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 500, 500); |
| |
| var x1 = 100, y1 = 100, x2 = 400, y2 = 400; |
| |
| ctx.beginPath(); |
| ctx.moveTo(x1, y1); |
| ctx.lineTo(x2, y2); |
| ctx.lineWidth = 10; |
| ctx.strokeStyle = BLUE; |
| ctx.lineCap = 'round'; |
| ctx.stroke(); |
| |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText('Start (' + x1 + ', ' + y1 + ')', x1 - 40, y1 - 30); |
| ctx.fillText('End (' + x2 + ', ' + y2 + ')', x2 - 120, y2 + 50); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## DrawRoundRect |
| Draw the specified round-rect |
| |
| 6 Fields, total size 25 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawRoundRect</td><td>Value: 51</td></tr> |
| <tr><td>FLOAT</td><td>left</td><td>The left side of the rect</td></tr><tr><td>FLOAT</td><td>top</td><td>The top of the rect</td></tr><tr><td>FLOAT</td><td>right</td><td>The right side of the rect</td></tr><tr><td>FLOAT</td><td>bottom</td><td>The bottom of the rect</td></tr><tr><td>FLOAT</td><td>rx</td><td>The x-radius of the oval used to round the corners</td></tr><tr><td>FLOAT</td><td>ry</td><td>The y-radius of the oval used to round the corners</td></tr></table> |
| |
| ### DrawRoundRect Illustration |
| |
| The `DrawRoundRect` operation renders a rectangle with rounded corners, defined by its bounds and the radii `rx` and `ry`. |
| |
| <div id="drawRoundRectContainer"> |
| <canvas id="drawRoundRectCanvas_v1" width="600" height="400" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('drawRoundRectCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 600, 400); |
| |
| var x = 100, y = 60, w = 300, h = 220, r = 50; |
| |
| ctx.beginPath(); |
| ctx.moveTo(x + r, y); |
| ctx.lineTo(x + w - r, y); |
| ctx.quadraticCurveTo(x + w, y, x + w, y + r); |
| ctx.lineTo(x + w, y + h - r); |
| ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); |
| ctx.lineTo(x + r, y + h); |
| ctx.quadraticCurveTo(x, y + h, x, y + h - r); |
| ctx.lineTo(x, y + r); |
| ctx.quadraticCurveTo(x, y, x + r, y); |
| ctx.closePath(); |
| |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fill(); |
| ctx.lineWidth = 5; |
| ctx.strokeStyle = BLUE; |
| ctx.stroke(); |
| |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.textAlign = 'left'; |
| ctx.fillText('rx, ry = ' + r, x + w + 15, y + r); |
| |
| ctx.font = '18px Arial'; |
| ctx.fillStyle = GRAY; |
| ctx.textAlign = 'center'; |
| ctx.fillText('bounds (left, top, right, bottom)', x + w/2, y + h + 45); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| ## DrawSector |
| Draw the specified sector (pie shape)which will be scaled to fit inside the specified oval |
| |
| 6 Fields, total size 25 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawSector</td><td>Value: 52</td></tr> |
| <tr><td>FLOAT</td><td>left</td><td>The left side of the Oval</td></tr><tr><td>FLOAT</td><td>top</td><td>The top of the Oval</td></tr><tr><td>FLOAT</td><td>right</td><td>The right side of the Oval</td></tr><tr><td>FLOAT</td><td>bottom</td><td>The bottom of the Oval</td></tr><tr><td>FLOAT</td><td>startAngle</td><td>Starting angle (in degrees) where the arc begins</td></tr><tr><td>FLOAT</td><td>sweepAngle</td><td>Sweep angle (in degrees) measured clockwise</td></tr></table> |
| |
| ### DrawSector Illustration |
| |
| The `DrawSector` operation renders a "pie" shape within a specified oval bounding box, starting from `startAngle` and sweeping through `sweepAngle`. |
| |
| <div id="drawSectorContainer"> |
| <canvas id="drawSectorCanvas_v1" width="600" height="500" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('drawSectorCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 600, 500); |
| |
| var cx = 300, cy = 250, rx = 175, ry = 125; |
| var startAngle = 45; |
| var sweepAngle = 110; |
| |
| // Bounding Oval |
| ctx.beginPath(); |
| ctx.ellipse(cx, cy, rx, ry, 0, 0, 2 * Math.PI); |
| ctx.strokeStyle = '#eee'; |
| ctx.setLineDash([10, 10]); |
| ctx.lineWidth = 2; |
| ctx.stroke(); |
| ctx.setLineDash([]); |
| |
| // The Sector |
| ctx.beginPath(); |
| ctx.moveTo(cx, cy); |
| ctx.ellipse(cx, cy, rx, ry, 0, (startAngle * Math.PI) / 180, ((startAngle + sweepAngle) * Math.PI) / 180, false); |
| ctx.closePath(); |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fill(); |
| ctx.lineWidth = 4; |
| ctx.strokeStyle = BLUE; |
| ctx.stroke(); |
| |
| // Start Angle Indicator |
| var sx = cx + rx * Math.cos(startAngle * Math.PI / 180); |
| var sy = cy + ry * Math.sin(startAngle * Math.PI / 180); |
| ctx.beginPath(); |
| ctx.moveTo(cx, cy); |
| ctx.lineTo(sx, sy); |
| ctx.strokeStyle = BLUE; |
| ctx.lineWidth = 2; |
| ctx.stroke(); |
| |
| // End Angle Indicator |
| var ex = cx + rx * Math.cos((startAngle + sweepAngle) * Math.PI / 180); |
| var ey = cy + ry * Math.sin((startAngle + sweepAngle) * Math.PI / 180); |
| ctx.beginPath(); |
| ctx.moveTo(cx, cy); |
| ctx.lineTo(ex, ey); |
| ctx.strokeStyle = GRAY; |
| ctx.stroke(); |
| |
| // Center point |
| ctx.beginPath(); |
| ctx.arc(cx, cy, 5, 0, 2 * Math.PI); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fill(); |
| |
| // Labels |
| ctx.font = 'bold 18px Arial'; |
| ctx.textAlign = 'left'; |
| ctx.fillStyle = BLUE; |
| ctx.fillText('startAngle: ' + startAngle + '°', sx + 10, sy + 15); |
| |
| ctx.textAlign = 'left'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText('sweepAngle: ' + sweepAngle + '°', ex - 120, ey + 65); |
| |
| ctx.textAlign = 'center'; |
| ctx.font = '14px Arial'; |
| ctx.fillStyle = GRAY; |
| ctx.fillText('Center (cx, cy)', cx, cy - 15); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| ## DrawOval |
| Draw the specified oval |
| |
| 4 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawOval</td><td>Value: 56</td></tr> |
| <tr><td>FLOAT</td><td>left</td><td>The left side of the oval</td></tr><tr><td>FLOAT</td><td>top</td><td>The top of the oval</td></tr><tr><td>FLOAT</td><td>right</td><td>The right side of the oval</td></tr><tr><td>FLOAT</td><td>bottom</td><td>The bottom of the oval</td></tr></table> |
| |
| ### DrawOval Illustration |
| |
| The `DrawOval` operation renders an oval that fits within the specified bounding box. |
| |
| <div id="drawOvalContainer"> |
| <canvas id="drawOvalCanvas_v1" width="500" height="500" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('drawOvalCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 500); |
| |
| var cx = 250, cy = 250, rx = 175, ry = 100; |
| |
| // Main Oval |
| ctx.beginPath(); |
| ctx.ellipse(cx, cy, rx, ry, 0, 0, 2 * Math.PI); |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fill(); |
| ctx.lineWidth = 5; |
| ctx.strokeStyle = BLUE; |
| ctx.stroke(); |
| |
| // Bounding Box |
| ctx.setLineDash([12, 12]); |
| ctx.strokeStyle = GRAY; |
| ctx.lineWidth = 2; |
| ctx.strokeRect(cx - rx, cy - ry, rx * 2, ry * 2); |
| ctx.setLineDash([]); |
| |
| // Labels |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText('left, top', cx - rx, cy - ry - 15); |
| ctx.fillText('right, bottom', cx + rx - 140, cy + ry + 40); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| ## DrawBitmapInt |
| Draw a bitmap using integer coordinates |
| |
| 10 Fields, total size 41 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawBitmapInt</td><td>Value: 66</td></tr> |
| <tr><td>INT</td><td>imageId</td><td>The ID of the bitmap</td></tr><tr><td>INT</td><td>srcLeft</td><td>The left side of the source image</td></tr><tr><td>INT</td><td>srcTop</td><td>The top of the source image</td></tr><tr><td>INT</td><td>srcRight</td><td>The right side of the source image</td></tr><tr><td>INT</td><td>srcBottom</td><td>The bottom of the source image</td></tr><tr><td>INT</td><td>dstLeft</td><td>The left side of the destination</td></tr><tr><td>INT</td><td>dstTop</td><td>The top of the destination</td></tr><tr><td>INT</td><td>dstRight</td><td>The right side of the destination</td></tr><tr><td>INT</td><td>dstBottom</td><td>The bottom of the destination</td></tr><tr><td>INT</td><td>cdId</td><td>The ID of the content description string</td></tr></table> |
| |
| |
| ## DrawPath |
| Draw a path |
| |
| 1 Fields, total size 5 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawPath</td><td>Value: 124</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the path to draw</td></tr></table> |
| |
| |
| ## DrawTweenPath |
| Draw an interpolated path between two paths |
| |
| 5 Fields, total size 21 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawTweenPath</td><td>Value: 125</td></tr> |
| <tr><td>INT</td><td>path1Id</td><td>The ID of the first path</td></tr><tr><td>INT</td><td>path2Id</td><td>The ID of the second path</td></tr><tr><td>FLOAT</td><td>tween</td><td>Interpolation value [0..1]</td></tr><tr><td>FLOAT</td><td>start</td><td>Trim the start of the path [0..1]</td></tr><tr><td>FLOAT</td><td>stop</td><td>Trim the end of the path [0..1]</td></tr></table> |
| |
| |
| ## DrawContent |
| Draw the component content |
| |
| 0 Fields, total size 1 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawContent</td><td>Value: 139</td></tr> |
| </table> |
| |
| |
| ## DrawBitmapScaled |
| Draw a bitmap with scaling and alignment options |
| |
| 12 Fields, total size 49 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawBitmapScaled</td><td>Value: 149</td></tr> |
| <tr><td>INT</td><td>imageId</td><td>The ID of the bitmap</td></tr><tr><td>FLOAT</td><td>srcLeft</td><td>The left side of the source image</td></tr><tr><td>FLOAT</td><td>srcTop</td><td>The top of the source image</td></tr><tr><td>FLOAT</td><td>srcRight</td><td>The right side of the source image</td></tr><tr><td>FLOAT</td><td>srcBottom</td><td>The bottom of the source image</td></tr><tr><td>FLOAT</td><td>dstLeft</td><td>The left side of the destination</td></tr><tr><td>FLOAT</td><td>dstTop</td><td>The top of the destination</td></tr><tr><td>FLOAT</td><td>dstRight</td><td>The right side of the destination</td></tr><tr><td>FLOAT</td><td>dstBottom</td><td>The bottom of the destination</td></tr><tr><td>INT</td><td>scaleType</td><td>Type of scaling to apply</td></tr><tr><td>FLOAT</td><td>scaleFactor</td><td>Factor for fixed scale</td></tr><tr><td>INT</td><td>cdId</td><td>The ID of the content description string</td></tr></table> |
| |
| ### scaleType |
| | Name | Value | |
| | ---- | ---- | |
| | SCALE_NONE | 0 |
| | SCALE_INSIDE | 1 |
| | SCALE_FILL_WIDTH | 2 |
| | SCALE_FILL_HEIGHT | 3 |
| | SCALE_FIT | 4 |
| | SCALE_CROP | 5 |
| | SCALE_FILL_BOUNDS | 6 |
| | SCALE_FIXED_SCALE | 7 |
| ### DrawBitmapScaled Illustration |
| |
| The `DrawBitmapScaled` operation handles rendering images within a destination area using different scaling algorithms. To illustrate this, a "House" image with a 1:2 aspect ratio (tall) is used. |
| |
| <div id="drawBitmapScaledContainer"> |
| <canvas id="drawBitmapScaledCanvas_v1" width="500" height="300" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('drawBitmapScaledCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#BBBBBB', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 300); |
| |
| function drawHouse(ctx, w, h) { |
| ctx.beginPath(); |
| ctx.rect(0, h * 0.4, w, h * 0.6); |
| ctx.moveTo(0, h * 0.4); |
| ctx.lineTo(w / 2, 0); |
| ctx.lineTo(w, h * 0.4); |
| ctx.rect(w * 0.35, h * 0.7, w * 0.3, h * 0.3); |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fill(); |
| ctx.lineWidth = Math.max(2, w/20); |
| ctx.strokeStyle = BLUE; |
| ctx.stroke(); |
| } |
| |
| function drawScaleExample(x, label, mode) { |
| var destW = 120, destH = 180; |
| var imgW = 100, imgH = 200; |
| |
| ctx.save(); |
| ctx.translate(x, 40); |
| |
| ctx.strokeStyle = '#eee'; |
| ctx.setLineDash([5, 5]); |
| ctx.lineWidth = 2; |
| ctx.strokeRect(0, 0, destW, destH); |
| ctx.setLineDash([]); |
| |
| ctx.save(); |
| ctx.beginPath(); ctx.rect(0, 0, destW, destH); ctx.clip(); |
| |
| if (mode === 'FIT') { |
| var s = Math.min(destW / imgW, destH / imgH); |
| ctx.translate((destW - imgW * s) / 2, (destH - imgH * s) / 2); |
| drawHouse(ctx, imgW * s, imgH * s); |
| } else if (mode === 'CROP') { |
| var s = Math.max(destW / imgW, destH / imgH); |
| ctx.translate((destW - imgW * s) / 2, (destH - imgH * s) / 2); |
| drawHouse(ctx, imgW * s, imgH * s); |
| } else if (mode === 'FILL') { |
| drawHouse(ctx, destW, destH); |
| } |
| ctx.restore(); |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 18px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText(label, destW / 2, destH + 35); |
| ctx.restore(); |
| } |
| |
| drawScaleExample(40, 'SCALE_FIT', 'FIT'); |
| drawScaleExample(190, 'SCALE_CROP', 'CROP'); |
| drawScaleExample(340, 'SCALE_FILL', 'FILL'); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## DrawArc |
| Draw the specified arcwhich will be scaled to fit inside the specified oval |
| |
| 6 Fields, total size 25 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawArc</td><td>Value: 152</td></tr> |
| <tr><td>FLOAT</td><td>left</td><td>The left side of the Oval</td></tr><tr><td>FLOAT</td><td>top</td><td>The top of the Oval</td></tr><tr><td>FLOAT</td><td>right</td><td>The right side of the Oval</td></tr><tr><td>FLOAT</td><td>bottom</td><td>The bottom of the Oval</td></tr><tr><td>FLOAT</td><td>startAngle</td><td>Starting angle (in degrees) where the arc begins</td></tr><tr><td>FLOAT</td><td>sweepAngle</td><td>Sweep angle (in degrees) measured clockwise</td></tr></table> |
| |
| ### DrawArc Illustration |
| |
| The `DrawArc` operation renders an arc within a specified oval bounding box, starting from `startAngle` and sweeping through `sweepAngle` (degrees). |
| |
| <div id="drawArcContainer"> |
| <canvas id="drawArcCanvas_v1" width="600" height="500" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('drawArcCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 600, 500); |
| |
| var cx = 300, cy = 250, rx = 175, ry = 125; |
| var startAngle = 30; |
| var sweepAngle = 240; |
| |
| // Bounding Oval |
| ctx.beginPath(); |
| ctx.ellipse(cx, cy, rx, ry, 0, 0, 2 * Math.PI); |
| ctx.strokeStyle = '#eee'; |
| ctx.setLineDash([10, 10]); |
| ctx.lineWidth = 2; |
| ctx.stroke(); |
| ctx.setLineDash([]); |
| |
| // Axes |
| ctx.beginPath(); |
| ctx.moveTo(cx - rx - 20, cy); |
| ctx.lineTo(cx + rx + 20, cy); |
| ctx.moveTo(cx, cy - ry - 20); |
| ctx.lineTo(cx, cy + ry + 20); |
| ctx.strokeStyle = '#f8f8f8'; |
| ctx.stroke(); |
| |
| // The Arc |
| ctx.beginPath(); |
| ctx.ellipse(cx, cy, rx, ry, 0, (startAngle * Math.PI) / 180, ((startAngle + sweepAngle) * Math.PI) / 180, false); |
| ctx.lineWidth = 8; |
| ctx.strokeStyle = BLUE; |
| ctx.stroke(); |
| |
| // Start Angle Line |
| ctx.beginPath(); |
| ctx.moveTo(cx, cy); |
| var sx = cx + rx * Math.cos(startAngle * Math.PI / 180); |
| var sy = cy + ry * Math.sin(startAngle * Math.PI / 180); |
| ctx.lineTo(sx, sy); |
| ctx.strokeStyle = BLUE; |
| ctx.lineWidth = 2; |
| ctx.stroke(); |
| |
| // End Angle Line |
| ctx.beginPath(); |
| ctx.moveTo(cx, cy); |
| var ex = cx + rx * Math.cos((startAngle + sweepAngle) * Math.PI / 180); |
| var ey = cy + ry * Math.sin((startAngle + sweepAngle) * Math.PI / 180); |
| ctx.lineTo(ex, ey); |
| ctx.strokeStyle = GRAY; |
| ctx.stroke(); |
| |
| // Labels |
| ctx.font = 'bold 18px Arial'; |
| ctx.fillStyle = BLUE; |
| ctx.textAlign = 'left'; |
| ctx.fillText('startAngle: ' + startAngle + '°', sx + (startAngle > 90 ? -150 : 10), sy + (sy > cy ? 20 : -10)); |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText('sweepAngle: ' + sweepAngle + '°', ex + (ex < cx ? -160 : 10), ey + (ey > cy ? 25 : -10)); |
| |
| ctx.font = '14px Arial'; |
| ctx.fillStyle = GRAY; |
| ctx.textAlign = 'right'; |
| ctx.fillText('0° (3 o\'clock)', cx + rx - 5, cy - 10); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## PathTween |
| Interpolate between two paths and store the result in a new path ID |
| |
| 4 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>PathTween</td><td>Value: 158</td></tr> |
| <tr><td>INT</td><td>outId</td><td>The ID of the resulting interpolated path</td></tr><tr><td>INT</td><td>pathId1</td><td>The ID of the first source path</td></tr><tr><td>INT</td><td>pathId2</td><td>The ID of the second source path</td></tr><tr><td>FLOAT</td><td>tween</td><td>The interpolation factor [0..1]</td></tr></table> |
| |
| ### PathTween Illustration |
| |
| The `PathTween` operation interpolates between two source paths (Path 1 and Path 2) based on a `tween` factor (0.0 to 1.0). |
| |
| <div id="pathTweenContainer"> |
| <canvas id="pathTweenCanvas_v1" width="500" height="300" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('pathTweenCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#BBBBBB', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.4)'; |
| ctx.clearRect(0, 0, 500, 300); |
| |
| var tween = 0.5; |
| |
| function getPoints(t) { |
| // Circle-ish |
| var p1 = [ {x:100,y:150}, {x:150,y:50}, {x:250,y:50}, {x:300,y:150} ]; |
| // Line-ish |
| var p2 = [ {x:100,y:250}, {x:150,y:250}, {x:250,y:250}, {x:300,y:250} ]; |
| |
| return p1.map((p, i) => ({ |
| x: p.x + (p2[i].x - p.x) * t, |
| y: p.y + (p2[i].y - p.y) * t |
| })); |
| } |
| |
| function drawPath(pts, color, width, dash) { |
| ctx.beginPath(); |
| ctx.moveTo(pts[0].x, pts[0].y); |
| ctx.bezierCurveTo(pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y); |
| ctx.strokeStyle = color; |
| ctx.lineWidth = width; |
| if (dash) ctx.setLineDash(dash); |
| ctx.stroke(); |
| ctx.setLineDash([]); |
| } |
| |
| // Source Paths |
| drawPath(getPoints(0), GRAY, 2, [5, 5]); |
| drawPath(getPoints(1), GRAY, 2, [5, 5]); |
| |
| // Tweened Path |
| drawPath(getPoints(tween), BLUE, 6); |
| |
| ctx.font = 'bold 20px Arial'; |
| ctx.fillStyle = GRAY; |
| ctx.fillText('Path 1 (tween: 0.0)', 100, 40); |
| ctx.fillText('Path 2 (tween: 1.0)', 100, 280); |
| ctx.fillStyle = BLUE; |
| ctx.fillText('Interpolated (tween: 0.5)', 120, 140); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## PathCreate |
| Start the creation of a dynamic path |
| |
| 3 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>PathCreate</td><td>Value: 159</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the path to create</td></tr><tr><td>FLOAT</td><td>startX</td><td>The X coordinate of the starting point</td></tr><tr><td>FLOAT</td><td>startY</td><td>The Y coordinate of the starting point</td></tr></table> |
| |
| ### PathCreate Illustration |
| |
| The `PathCreate` operation begins the definition of a path, followed by `PathAppend` operations to add segments like lines or curves. |
| |
| <div id="pathCreateContainer"> |
| <canvas id="pathCreateCanvas_v1" width="500" height="300" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('pathCreateCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 500, 300); |
| |
| var startX = 100, startY = 200; |
| var p1x = 200, p1y = 50; |
| var p2x = 300, p2y = 250; |
| var endX = 400, endY = 100; |
| |
| // The Path |
| ctx.beginPath(); |
| ctx.moveTo(startX, startY); |
| ctx.lineTo(p1x, p1y); |
| ctx.bezierCurveTo(p2x, p2y, p2x, p2y, endX, endY); |
| ctx.lineWidth = 5; |
| ctx.strokeStyle = BLUE; |
| ctx.stroke(); |
| |
| // Start point (PathCreate) |
| ctx.beginPath(); |
| ctx.arc(startX, startY, 8, 0, 2 * Math.PI); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fill(); |
| |
| ctx.font = 'bold 20px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText('PathCreate (startX, startY)', startX - 20, startY + 35); |
| ctx.font = '18px Arial'; |
| ctx.fillStyle = GRAY; |
| ctx.fillText('followed by PathAppend segments...', 150, 280); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## PathAppend |
| Append segments to an existing dynamic path |
| |
| 3 Fields, total size 9 + null x 4 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>PathAppend</td><td>Value: 160</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the path to append to</td></tr><tr><td>INT</td><td>length</td><td>The number of elements in the path data</td></tr><tr><td>FLOAT[]</td><td>pathData</td><td>The sequence of commands and coordinates</td></tr></table> |
| |
| ### PathAppend Illustration |
| |
| The `PathAppend` operation adds segments to an existing path. |
| |
| #### LineTo |
| Simple straight line segment. |
| <div id="pathAppendLineContainer"> |
| <canvas id="pathAppendLineCanvas" width="500" height="120" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| #### QuadraticTo |
| A curve with a single control point. The dashed lines show the tangents from the endpoints. |
| <div id="pathAppendQuadContainer"> |
| <canvas id="pathAppendQuadCanvas" width="500" height="200" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| #### CubicTo |
| A curve with two control points, allowing for more complex shapes like "S" curves. |
| <div id="pathAppendCubicContainer"> |
| <canvas id="pathAppendCubicCanvas" width="500" height="250" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444'; |
| |
| function drawPoint(ctx, x, y, label, align) { |
| ctx.beginPath(); |
| ctx.arc(x, y, 5, 0, Math.PI * 2); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fill(); |
| ctx.font = '14px Arial'; |
| if (align === 'right') { |
| ctx.textAlign = 'right'; |
| ctx.fillText(label, x - 10, y + 5); |
| } else { |
| ctx.textAlign = 'left'; |
| ctx.fillText(label, x + 10, y + 5); |
| } |
| } |
| |
| function setupLine() { |
| var canvas = document.getElementById('pathAppendLineCanvas'); |
| if (!canvas) { setTimeout(setupLine, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| var x1 = 100, y1 = 60, x2 = 400, y2 = 60; |
| ctx.clearRect(0, 0, 500, 120); |
| ctx.beginPath(); |
| ctx.moveTo(x1, y1); |
| ctx.lineTo(x2, y2); |
| ctx.lineWidth = 5; |
| ctx.strokeStyle = BLUE; |
| ctx.stroke(); |
| drawPoint(ctx, x1, y1, 'Start', 'right'); |
| drawPoint(ctx, x2, y2, 'End'); |
| } |
| |
| function setupQuad() { |
| var canvas = document.getElementById('pathAppendQuadCanvas'); |
| if (!canvas) { setTimeout(setupQuad, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| var x1 = 100, y1 = 150, cx = 250, cy = 30, x2 = 400, y2 = 150; |
| ctx.clearRect(0, 0, 500, 200); |
| |
| // Tangents |
| ctx.setLineDash([5, 5]); |
| ctx.strokeStyle = GRAY; |
| ctx.lineWidth = 1; |
| ctx.beginPath(); |
| ctx.moveTo(x1, y1); ctx.lineTo(cx, cy); ctx.lineTo(x2, y2); |
| ctx.stroke(); |
| ctx.setLineDash([]); |
| |
| // Curve |
| ctx.beginPath(); |
| ctx.moveTo(x1, y1); |
| ctx.quadraticCurveTo(cx, cy, x2, y2); |
| ctx.lineWidth = 5; |
| ctx.strokeStyle = BLUE; |
| ctx.stroke(); |
| |
| drawPoint(ctx, x1, y1, 'Start', 'right'); |
| drawPoint(ctx, x2, y2, 'End'); |
| drawPoint(ctx, cx, cy, 'Control Point'); |
| } |
| |
| function setupCubic() { |
| var canvas = document.getElementById('pathAppendCubicCanvas'); |
| if (!canvas) { setTimeout(setupCubic, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| var x1 = 80, y1 = 180, c1x = 120, c1y = 20, c2x = 380, c2y = 230, x2 = 420, y2 = 70; |
| ctx.clearRect(0, 0, 500, 250); |
| |
| // Tangents |
| ctx.setLineDash([5, 5]); |
| ctx.strokeStyle = GRAY; |
| ctx.lineWidth = 1; |
| ctx.beginPath(); |
| ctx.moveTo(x1, y1); ctx.lineTo(c1x, c1y); |
| ctx.moveTo(x2, y2); ctx.lineTo(c2x, c2y); |
| ctx.stroke(); |
| ctx.setLineDash([]); |
| |
| // Curve |
| ctx.beginPath(); |
| ctx.moveTo(x1, y1); |
| ctx.bezierCurveTo(c1x, c1y, c2x, c2y, x2, y2); |
| ctx.lineWidth = 5; |
| ctx.strokeStyle = BLUE; |
| ctx.stroke(); |
| |
| drawPoint(ctx, x1, y1, 'Start', 'right'); |
| drawPoint(ctx, x2, y2, 'End'); |
| drawPoint(ctx, c1x, c1y, 'CP1'); |
| drawPoint(ctx, c2x, c2y, 'CP2'); |
| } |
| |
| function run() { setupLine(); setupQuad(); setupCubic(); } |
| if (document.readyState === 'complete') { run(); } |
| else { window.addEventListener('load', run); setTimeout(run, 500); } |
| })(); |
| </script> |
| |
| |
| ## CanvasOperations |
| A collection of canvas operations |
| |
| 0 Fields, total size 1 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>CanvasOperations</td><td>Value: 173</td></tr> |
| </table> |
| |
| |
| ## PathCombine |
| Combine two paths using a boolean operation (Union, Intersect, etc.) |
| |
| 4 Fields, total size 14 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>PathCombine</td><td>Value: 175</td></tr> |
| <tr><td>INT</td><td>outId</td><td>The ID of the resulting path</td></tr><tr><td>INT</td><td>pathId1</td><td>The ID of the first source path</td></tr><tr><td>INT</td><td>pathId2</td><td>The ID of the second source path</td></tr><tr><td>BYTE</td><td>operation</td><td>The boolean operation to perform</td></tr></table> |
| |
| ### operation |
| | Name | Value | |
| | ---- | ---- | |
| | OP_DIFFERENCE | 0 |
| | OP_INTERSECT | 1 |
| | OP_REVERSE_DIFFERENCE | 2 |
| | OP_UNION | 3 |
| | OP_XOR | 4 |
| ### PathCombine Illustration |
| |
| The `PathCombine` operation performs boolean logic between two paths to create a new complex shape. |
| |
| <div id="pathCombineContainer"> |
| <canvas id="pathCombineCanvas_v1" width="500" height="220" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var phase = 0; |
| function draw() { |
| var canvas = document.getElementById('pathCombineCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#BBBBBB', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 500, 220); |
| |
| // Offset between circles oscillates |
| var circleOffset = 20 + (Math.sin(phase) + 1) / 2 * 40; |
| phase += 0.03; |
| |
| function drawCircles(x, label, mode) { |
| ctx.save(); |
| ctx.translate(x, 60); |
| |
| var r = 40; |
| var c1x = 40 - circleOffset/2; |
| var c2x = 40 + circleOffset/2; |
| |
| // Combined Result (Logic) |
| ctx.save(); |
| if (mode === 'INTERSECT') { |
| ctx.beginPath(); ctx.arc(c1x, 40, r, 0, Math.PI*2); ctx.clip(); |
| ctx.beginPath(); ctx.arc(c2x, 40, r, 0, Math.PI*2); |
| ctx.fillStyle = BLUE; ctx.fill(); |
| } else if (mode === 'UNION') { |
| ctx.beginPath(); ctx.arc(c1x, 40, r, 0, Math.PI*2); |
| ctx.arc(c2x, 40, r, 0, Math.PI*2); |
| ctx.fillStyle = BLUE; ctx.fill(); |
| } else if (mode === 'DIFFERENCE') { |
| ctx.beginPath(); ctx.arc(c1x, 40, r, 0, Math.PI*2); |
| ctx.fillStyle = BLUE; ctx.fill(); |
| ctx.globalCompositeOperation = 'destination-out'; |
| ctx.beginPath(); ctx.arc(c2x, 40, r, 0, Math.PI*2); ctx.fill(); |
| } |
| ctx.restore(); |
| |
| // Overlays (Original Outlines) |
| ctx.strokeStyle = '#eee'; |
| ctx.lineWidth = 1; |
| ctx.setLineDash([2, 2]); |
| ctx.beginPath(); ctx.arc(c1x, 40, r, 0, Math.PI*2); ctx.stroke(); |
| ctx.beginPath(); ctx.arc(c2x, 40, r, 0, Math.PI*2); ctx.stroke(); |
| ctx.setLineDash([]); |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 18px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText(label, 40, 130); |
| ctx.restore(); |
| } |
| |
| drawCircles(40, 'UNION', 'UNION'); |
| drawCircles(190, 'INTERSECT', 'INTERSECT'); |
| drawCircles(340, 'DIFFERENCE', 'DIFFERENCE'); |
| |
| requestAnimationFrame(draw); |
| } |
| draw(); |
| })(); |
| </script> |
| |
| |
| ## DrawToBitmap (added in v7) |
| Draw to a bitmap |
| |
| 3 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawToBitmap</td><td>Value: 190</td></tr> |
| <tr><td>INT</td><td>bitmapId</td><td>The bitmap to draw to or 0 to draw to the canvas</td></tr><tr><td>INT</td><td>mode</td><td>Flags to support configuration of bitmap</td></tr><tr><td>INT</td><td>color</td><td>set the initial of the bitmap</td></tr></table> |
| |
| # Text Operations |
| Operations for defining, manipulating, measuring, and rendering text and font data. This includes support for static strings, dynamic formatting, text-based expressions, spatial measurement of text |
| bounds, and high-fidelity rendering using both standard and bitmap fonts across various canvas geometries. |
| |
| Operations in this category: |
| | ID | Name | Version | Size (bytes) |
| | ---- | ---- | ---- | ---- | |
| | 43 | DrawText | v6 | 29 |
| | 48 | DrawBitmapFontText | v6 | 25 |
| | 49 | DrawBitmapFontTextOnPath | v7 | 25 |
| | 53 | DrawTextOnPath | v6 | 17 |
| | 57 | DrawTextOnCircle | v6 | 33 |
| | 102 | TextData | v6 | 5 |
| | 133 | DrawTextAnchored | v6 | 25 |
| | 135 | TextFromFloat | v6 | 17 |
| | 136 | TextMerge | v6 | 13 |
| | 151 | TextFromFloat | v6 | 13 |
| | 153 | TextLookupInt | v6 | 13 |
| | 155 | TextMeasure | v6 | 13 |
| | 156 | TextLength | v6 | 9 |
| | 167 | BitmapFontData | v6 | 29 |
| | 170 | TextMeasure | v6 | 13 |
| | 182 | TextSubtext | v7 | 17 |
| | 183 | BitmapTextMeasure | v7 | 21 |
| | 184 | DrawBitmapTextAnchored | v7 | 33 |
| | 199 | TextTransform | v7 | 21 |
| | 208 | TextLayout | v6 | 45 |
| |
| ## DrawText |
| Draw a run of text, all in a single direction |
| |
| 8 Fields, total size 29 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawText</td><td>Value: 43</td></tr> |
| <tr><td>INT</td><td>textId</td><td>The ID of the text to render</td></tr><tr><td>INT</td><td>start</td><td>The start index of the text to render</td></tr><tr><td>INT</td><td>end</td><td>The end index of the text to render</td></tr><tr><td>INT</td><td>contextStart</td><td>The index of the start of the shaping context</td></tr><tr><td>INT</td><td>contextEnd</td><td>The index of the end of the shaping context</td></tr><tr><td>FLOAT</td><td>x</td><td>The x position at which to draw the text</td></tr><tr><td>FLOAT</td><td>y</td><td>The y position at which to draw the text</td></tr><tr><td>BOOLEAN</td><td>rtl</td><td>Whether the run is in RTL direction</td></tr></table> |
| |
| ### DrawText Illustration |
| |
| The `DrawText` operation renders a run of text at a specific `(x, y)` coordinate. |
| |
| <div id="drawTextContainer"> |
| <canvas id="drawTextCanvas_v1" width="500" height="250" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('drawTextCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 500, 250); |
| |
| var x = 50, y = 150; |
| ctx.font = '60px Arial'; |
| ctx.fillStyle = BLUE; |
| ctx.fillText('Hello World', x, y); |
| |
| // Anchor |
| ctx.beginPath(); |
| ctx.arc(x, y, 7, 0, 2 * Math.PI); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fill(); |
| |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText('Origin (x, y)', x, y + 50); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| ## DrawBitmapFontText |
| Draw text using a bitmap font |
| |
| 6 Fields, total size 25 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawBitmapFontText</td><td>Value: 48</td></tr> |
| <tr><td>INT</td><td>textId</td><td>The ID of the text to render</td></tr><tr><td>INT</td><td>bitmapFontId</td><td>The ID of the bitmap font</td></tr><tr><td>INT</td><td>start</td><td>The start index of the text to render</td></tr><tr><td>INT</td><td>end</td><td>The end index of the text to render</td></tr><tr><td>FLOAT</td><td>x</td><td>The x position at which to draw the text</td></tr><tr><td>FLOAT</td><td>y</td><td>The y position at which to draw the text</td></tr></table> |
| |
| |
| ## DrawBitmapFontTextOnPath (added in v7) |
| Draw text using a bitmap font along a path |
| |
| 6 Fields, total size 25 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawBitmapFontTextOnPath</td><td>Value: 49</td></tr> |
| <tr><td>INT</td><td>textId</td><td>The ID of the text to render</td></tr><tr><td>INT</td><td>bitmapFontId</td><td>The ID of the bitmap font</td></tr><tr><td>INT</td><td>pathId</td><td>The ID of the path to follow</td></tr><tr><td>INT</td><td>start</td><td>The start index of the text to render</td></tr><tr><td>INT</td><td>end</td><td>The end index of the text to render</td></tr><tr><td>FLOAT</td><td>yAdj</td><td>Vertical adjustment relative to the path</td></tr></table> |
| |
| |
| ## DrawTextOnPath |
| Draw text along a path |
| |
| 4 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawTextOnPath</td><td>Value: 53</td></tr> |
| <tr><td>INT</td><td>textId</td><td>The ID of the text</td></tr><tr><td>INT</td><td>pathId</td><td>The ID of the path</td></tr><tr><td>FLOAT</td><td>hOffset</td><td>Horizontal offset along the path</td></tr><tr><td>FLOAT</td><td>vOffset</td><td>Vertical offset relative to the path</td></tr></table> |
| |
| ### DrawTextOnPath Illustration |
| |
| The `DrawTextOnPath` operation renders text along a specified path. The text follows the curve, maintaining its orientation relative to the path's tangent. |
| |
| <div id="drawTextOnPathContainer"> |
| <canvas id="drawTextOnPathCanvas_v1" width="500" height="250" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('drawTextOnPathCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 500, 250); |
| |
| var p0 = {x: 60, y: 180}; |
| var p1 = {x: 120, y: 60}; |
| var p2 = {x: 380, y: 300}; |
| var p3 = {x: 440, y: 180}; |
| |
| // Reference Path |
| ctx.beginPath(); |
| ctx.moveTo(p0.x, p0.y); |
| ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); |
| ctx.strokeStyle = '#eee'; |
| ctx.lineWidth = 2; |
| ctx.setLineDash([12, 12]); |
| ctx.stroke(); |
| ctx.setLineDash([]); |
| |
| var text = "RemoteCompose Text Following Curve"; |
| ctx.font = '24px Arial'; |
| ctx.fillStyle = BLUE; |
| ctx.textAlign = 'center'; |
| |
| function getBezierPoint(t) { |
| var cx = 3 * (p1.x - p0.x); |
| var bx = 3 * (p2.x - p1.x) - cx; |
| var ax = p3.x - p0.x - cx - bx; |
| var cy = 3 * (p1.y - p0.y); |
| var by = 3 * (p2.y - p1.y) - cy; |
| var ay = p3.y - p0.y - cy - by; |
| var x = (ax * Math.pow(t, 3)) + (bx * Math.pow(t, 2)) + (cx * t) + p0.x; |
| var y = (ay * Math.pow(t, 3)) + (by * Math.pow(t, 2)) + (cy * t) + p0.y; |
| return {x: x, y: y}; |
| } |
| |
| function getBezierTangent(t) { |
| var cx = 3 * (p1.x - p0.x); |
| var bx = 3 * (p2.x - p1.x) - cx; |
| var ax = p3.x - p0.x - cx - bx; |
| var cy = 3 * (p1.y - p0.y); |
| var by = 3 * (p2.y - p1.y) - cy; |
| var ay = p3.y - p0.y - cy - by; |
| var dx = (3 * ax * Math.pow(t, 2)) + (2 * bx * t) + cx; |
| var dy = (3 * ay * Math.pow(t, 2)) + (2 * by * t) + cy; |
| return Math.atan2(dy, dx); |
| } |
| |
| var chars = text.split(""); |
| var step = 0.8 / chars.length; |
| for (var i = 0; i < chars.length; i++) { |
| var t = 0.1 + (i * step); |
| var pt = getBezierPoint(t); |
| var angle = getBezierTangent(t); |
| |
| ctx.save(); |
| ctx.translate(pt.x, pt.y); |
| ctx.rotate(angle); |
| ctx.fillText(chars[i], 0, -8); |
| ctx.restore(); |
| } |
| |
| ctx.font = 'bold 20px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText('Path Tangent Alignment', 120, 30); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## DrawTextOnCircle |
| Draw text along a circle |
| |
| 8 Fields, total size 33 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawTextOnCircle</td><td>Value: 57</td></tr> |
| <tr><td>INT</td><td>textId</td><td>The ID of the text</td></tr><tr><td>FLOAT</td><td>centerX</td><td>The x coordinate of the center</td></tr><tr><td>FLOAT</td><td>centerY</td><td>The y coordinate of the center</td></tr><tr><td>FLOAT</td><td>radius</td><td>The radius of the circle</td></tr><tr><td>FLOAT</td><td>startAngle</td><td>The start angle in degrees</td></tr><tr><td>FLOAT</td><td>warpRadiusOffset</td><td>The warp radius offset</td></tr><tr><td>INT</td><td>alignment</td><td>The alignment of the text</td></tr><tr><td>INT</td><td>placement</td><td>The placement of the text</td></tr></table> |
| |
| ### alignment |
| | Name | Value | |
| | ---- | ---- | |
| | START | 0 |
| | CENTER | 1 |
| | END | 2 |
| ### placement |
| | Name | Value | |
| | ---- | ---- | |
| | OUTSIDE | 0 |
| | INSIDE | 1 |
| ### DrawTextOnCircle Illustration |
| |
| The `DrawTextOnCircle` operation renders curved text along the circumference of a circle. |
| |
| <div id="drawTextOnCircleContainer"> |
| <canvas id="drawTextOnCircleCanvas_v1" width="500" height="500" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('drawTextOnCircleCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 500, 500); |
| |
| var cx = 250, cy = 250, r = 160; |
| var text = "RemoteCompose Curved Text Illustration"; |
| |
| // Circle Guide |
| ctx.strokeStyle = '#eee'; |
| ctx.setLineDash([12, 12]); |
| ctx.lineWidth = 2; |
| ctx.beginPath(); |
| ctx.arc(cx, cy, r, 0, 2 * Math.PI); |
| ctx.stroke(); |
| ctx.setLineDash([]); |
| |
| ctx.font = '32px Arial'; |
| ctx.fillStyle = BLUE; |
| ctx.textAlign = 'center'; |
| |
| var characters = text.split(""); |
| var anglePerChar = (Math.PI * 1.5) / characters.length; |
| var startAngle = -Math.PI * 0.75; |
| |
| for (var i = 0; i < characters.length; i++) { |
| ctx.save(); |
| ctx.translate(cx, cy); |
| var currentAngle = startAngle + i * anglePerChar; |
| ctx.rotate(currentAngle); |
| ctx.fillText(characters[i], 0, -r); |
| ctx.restore(); |
| } |
| |
| // Center Anchor |
| ctx.beginPath(); |
| ctx.arc(cx, cy, 6, 0, 2 * Math.PI); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fill(); |
| |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText('Center (cx, cy)', cx + 15, cy - 15); |
| |
| // Radius indicator |
| ctx.strokeStyle = BLUE; |
| ctx.setLineDash([5, 5]); |
| ctx.beginPath(); |
| ctx.moveTo(cx, cy); |
| ctx.lineTo(cx + r, cy); |
| ctx.stroke(); |
| ctx.fillStyle = BLUE; |
| ctx.fillText('Radius', cx + r/2, cy + 25); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| ## TextData |
| Define a static string and associate it with an ID |
| |
| 2 Fields, total size 5 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>TextData</td><td>Value: 102</td></tr> |
| <tr><td>INT</td><td>textId</td><td>The ID of the text</td></tr><tr><td>UTF8</td><td>text</td><td>The string value</td></tr></table> |
| |
| |
| ## DrawTextAnchored |
| Draw text centered about an anchor point |
| |
| 6 Fields, total size 25 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawTextAnchored</td><td>Value: 133</td></tr> |
| <tr><td>INT</td><td>textId</td><td>The ID of the text to render</td></tr><tr><td>FLOAT</td><td>x</td><td>The x-position of the anchor point</td></tr><tr><td>FLOAT</td><td>y</td><td>The y-position of the anchor point</td></tr><tr><td>FLOAT</td><td>panX</td><td>The horizontal pan from left(-1) to right(1), 0 being centered</td></tr><tr><td>FLOAT</td><td>panY</td><td>The vertical pan from top(-1) to bottom(1), 0 being centered</td></tr><tr><td>INT</td><td>flags</td><td>Behavior flags</td></tr></table> |
| |
| ### flags |
| | Name | Value | |
| | ---- | ---- | |
| | ANCHOR_TEXT_RTL | 1 |
| | ANCHOR_MONOSPACE_MEASURE | 2 |
| | MEASURE_EVERY_TIME | 4 |
| | BASELINE_RELATIVE | 8 |
| ### DrawTextAnchored Illustration |
| |
| The `DrawTextAnchored` operation renders text relative to an anchor point `(x, y)` using horizontal (`panX`) and vertical (`panY`) values ranging from -1.0 to 1.0. |
| |
| #### Center Aligned (`panX: 0, panY: 0`) |
| <div id="drawTextAnchoredContainer_center"> |
| <canvas id="drawTextAnchoredCanvas_center" width="500" height="160" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| #### Top-Left Aligned (`panX: -1, panY: -1`) |
| <div id="drawTextAnchoredContainer_tl"> |
| <canvas id="drawTextAnchoredCanvas_tl" width="500" height="160" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| #### Bottom-Right Aligned (`panX: 1, panY: 1`) |
| <div id="drawTextAnchoredContainer_br"> |
| <canvas id="drawTextAnchoredCanvas_br" width="500" height="160" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function setupCanvas(canvasId, panX, panY, anchorLabelYOffset) { |
| function draw() { |
| var canvas = document.getElementById(canvasId); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#BBBBBB', DARK_GRAY = '#444444'; |
| var ax = 250, ay = 80; |
| var text = "Anchored Text"; |
| ctx.font = '32px Arial'; |
| ctx.textBaseline = 'top'; |
| var metrics = ctx.measureText(text); |
| var tw = metrics.width; |
| var th = 32; |
| |
| ctx.clearRect(0, 0, 500, 160); |
| |
| var dx = (-(panX + 1) / 2) * tw; |
| var dy = (-(panY + 1) / 2) * th; |
| |
| ctx.strokeStyle = '#ddd'; |
| ctx.lineWidth = 1; |
| ctx.strokeRect(ax + dx, ay + dy, tw, th); |
| |
| ctx.fillStyle = BLUE; |
| ctx.fillText(text, ax + dx, ay + dy); |
| |
| ctx.beginPath(); |
| ctx.arc(ax, ay, 6, 0, 2 * Math.PI); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fill(); |
| |
| ctx.font = 'bold 18px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.textAlign = 'left'; |
| var ayOff = anchorLabelYOffset || -15; |
| ctx.fillText('Anchor (x, y)', ax + 15, ay + ayOff); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| } |
| |
| setupCanvas('drawTextAnchoredCanvas_center', 0, 0, -35); |
| setupCanvas('drawTextAnchoredCanvas_tl', -1, -1, -25); |
| setupCanvas('drawTextAnchoredCanvas_br', 1, 1, -15); |
| })(); |
| </script> |
| |
| |
| |
| ## TextFromFloat |
| Convert a float value into a formatted string |
| |
| 5 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>TextFromFloat</td><td>Value: 135</td></tr> |
| <tr><td>INT</td><td>textId</td><td>The ID of the resulting text</td></tr><tr><td>FLOAT</td><td>value</td><td>The float value to convert</td></tr><tr><td>SHORT</td><td>digitsBefore</td><td>Number of digits before the decimal point</td></tr><tr><td>SHORT</td><td>digitsAfter</td><td>Number of digits after the decimal point</td></tr><tr><td>INT</td><td>flags</td><td>Formatting and padding flags</td></tr></table> |
| |
| |
| ## TextMerge |
| Merge two strings into one |
| |
| 3 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>TextMerge</td><td>Value: 136</td></tr> |
| <tr><td>INT</td><td>textId</td><td>The ID of the resulting text</td></tr><tr><td>INT</td><td>srcId1</td><td>The ID of the first source string</td></tr><tr><td>INT</td><td>srcId2</td><td>The ID of the second source string</td></tr></table> |
| |
| |
| ## TextFromFloat |
| Look up a string from a collection via index |
| |
| 3 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>TextFromFloat</td><td>Value: 151</td></tr> |
| <tr><td>INT</td><td>textId</td><td>The ID of the resulting text</td></tr><tr><td>INT</td><td>dataSetId</td><td>The ID of the string collection</td></tr><tr><td>FLOAT</td><td>index</td><td>The index of the string to retrieve</td></tr></table> |
| |
| |
| ## TextLookupInt |
| Look up a string from a collection via an integer index variable |
| |
| 3 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>TextLookupInt</td><td>Value: 153</td></tr> |
| <tr><td>INT</td><td>textId</td><td>The ID of the resulting text</td></tr><tr><td>INT</td><td>dataSetId</td><td>The ID of the string collection</td></tr><tr><td>INT</td><td>indexId</td><td>The ID of the integer index variable</td></tr></table> |
| |
| |
| ## TextMeasure |
| Measure text dimensions and store the result in a float variable |
| |
| 3 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>TextMeasure</td><td>Value: 155</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the float variable to store the result</td></tr><tr><td>INT</td><td>textId</td><td>The ID of the text to measure</td></tr><tr><td>INT</td><td>type</td><td>The type of measurement (WIDTH, HEIGHT, etc.)</td></tr></table> |
| |
| ### type |
| | Name | Value | |
| | ---- | ---- | |
| | MEASURE_WIDTH | 0 |
| | MEASURE_HEIGHT | 1 |
| | MEASURE_LEFT | 2 |
| | MEASURE_RIGHT | 3 |
| | MEASURE_TOP | 4 |
| | MEASURE_BOTTOM | 5 |
| ### TextMeasure Illustration |
| |
| The `TextMeasure` operation calculates dimensions of a text string, such as its width, height, or relative positions like the baseline. |
| |
| <div id="textMeasureContainer"> |
| <canvas id="textMeasureCanvas_v1" width="600" height="220" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('textMeasureCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#BBBBBB', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 600, 220); |
| |
| var x=60, y=110; |
| var text = "Typography"; |
| ctx.font = '80px serif'; |
| ctx.textAlign = 'left'; |
| ctx.textBaseline = 'alphabetic'; |
| |
| // Draw Text |
| ctx.fillStyle = BLUE; |
| ctx.fillText(text, x, y); |
| |
| // Metrics lines |
| var metrics = ctx.measureText(text); |
| var w = metrics.width; |
| var ascent = 60; // approx |
| var descent = 20; // approx |
| |
| // Baseline |
| ctx.strokeStyle = DARK_GRAY; |
| ctx.lineWidth = 2; |
| ctx.beginPath(); ctx.moveTo(x - 20, y); ctx.lineTo(x + w + 20, y); ctx.stroke(); |
| |
| // Ascent |
| ctx.strokeStyle = GRAY; |
| ctx.setLineDash([5, 5]); |
| ctx.beginPath(); ctx.moveTo(x - 20, y - ascent); ctx.lineTo(x + w + 20, y - ascent); ctx.stroke(); |
| |
| // Descent |
| ctx.beginPath(); ctx.moveTo(x - 20, y + descent); ctx.lineTo(x + w + 20, y + descent); ctx.stroke(); |
| ctx.setLineDash([]); |
| |
| // Labels |
| ctx.font = 'bold 18px Arial'; |
| ctx.textAlign = 'left'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText('Baseline', x + w + 25, y + 5); |
| |
| ctx.fillStyle = GRAY; |
| ctx.fillText('Ascent', x + w + 25, y - ascent + 5); |
| ctx.fillText('Descent', x + w + 25, y + descent + 5); |
| |
| // Width dimension |
| var dimY = y + 50; |
| ctx.strokeStyle = DARK_GRAY; |
| ctx.lineWidth = 1; |
| ctx.beginPath(); |
| ctx.moveTo(x, dimY); ctx.lineTo(x + w, dimY); |
| ctx.moveTo(x, dimY - 5); ctx.lineTo(x, dimY + 5); |
| ctx.moveTo(x + w, dimY - 5); ctx.lineTo(x + w, dimY + 5); |
| ctx.stroke(); |
| |
| ctx.textAlign = 'center'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText('Width: ' + Math.round(w) + 'px', x + w/2, dimY + 25); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## TextLength |
| Get the length of a string and store it in a float variable |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>TextLength</td><td>Value: 156</td></tr> |
| <tr><td>INT</td><td>lengthId</td><td>The ID of the float variable to store the length</td></tr><tr><td>INT</td><td>textId</td><td>The ID of the text to measure</td></tr></table> |
| |
| |
| ## BitmapFontData |
| Define a bitmap font with glyph metadata and optional kerning |
| |
| 13 Fields, total size 29 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>BitmapFontData</td><td>Value: 167</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the bitmap font</td></tr><tr><td>INT</td><td>versionAndNumGlyphs</td><td>Encoded version and number of glyphs</td></tr><tr><td>UTF8</td><td>chars[0..n]</td><td>The characters for each glyph</td></tr><tr><td>INT</td><td>bitmapId[0..n]</td><td>The bitmap ID for each glyph</td></tr><tr><td>SHORT</td><td>marginLeft[0..n]</td><td>Left margin for each glyph</td></tr><tr><td>SHORT</td><td>marginTop[0..n]</td><td>Top margin for each glyph</td></tr><tr><td>SHORT</td><td>marginRight[0..n]</td><td>Right margin for each glyph</td></tr><tr><td>SHORT</td><td>marginBottom[0..n]</td><td>Bottom margin for each glyph</td></tr><tr><td>SHORT</td><td>width[0..n]</td><td>Width for each glyph</td></tr><tr><td>SHORT</td><td>height[0..n]</td><td>Height for each glyph</td></tr><tr><td>SHORT</td><td>kerningSize</td><td>Number of entries in the kerning table</td></tr><tr><td>UTF8</td><td>glyphPair[0..n]</td><td>Glyph pair for kerning</td></tr><tr><td>SHORT</td><td>adjustment[0..n]</td><td>Horizontal adjustment for kerning pair</td></tr></table> |
| |
| |
| ## TextMeasure |
| Extract text-related properties (width, length, etc.) |
| |
| 4 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>TextMeasure</td><td>Value: 170</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the float variable to store the result</td></tr><tr><td>INT</td><td>textId</td><td>The ID of the text variable to measure</td></tr><tr><td>SHORT</td><td>type</td><td>The type of property to extract</td></tr><tr><td>SHORT</td><td>unused</td><td>unused field</td></tr></table> |
| |
| |
| ## TextSubtext (added in v7) |
| Extract a substring from a source string |
| |
| 4 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>TextSubtext</td><td>Value: 182</td></tr> |
| <tr><td>INT</td><td>textId</td><td>The ID of the resulting substring</td></tr><tr><td>INT</td><td>srcId1</td><td>The ID of the source string</td></tr><tr><td>FLOAT</td><td>start</td><td>The start index of the substring</td></tr><tr><td>FLOAT</td><td>len</td><td>The length of the substring (or -1 for remainder)</td></tr></table> |
| |
| |
| ## BitmapTextMeasure (added in v7) |
| Measure text dimensions specifically for bitmap fonts |
| |
| 5 Fields, total size 21 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>BitmapTextMeasure</td><td>Value: 183</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the float variable to store the result</td></tr><tr><td>INT</td><td>textId</td><td>The ID of the text to measure</td></tr><tr><td>INT</td><td>bitmapFontId</td><td>The ID of the bitmap font</td></tr><tr><td>INT</td><td>type</td><td>The type of measurement (WIDTH, HEIGHT, etc.)</td></tr><tr><td>FLOAT</td><td>glyphSpacing</td><td>Horizontal spacing adjustment between glyphs in pixels</td></tr></table> |
| |
| |
| ## DrawBitmapTextAnchored (added in v7) |
| Draw bitmap font text anchored to a point with alignment (pan) |
| |
| 8 Fields, total size 33 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawBitmapTextAnchored</td><td>Value: 184</td></tr> |
| <tr><td>INT</td><td>textId</td><td>The ID of the text to render</td></tr><tr><td>INT</td><td>bitmapFontId</td><td>The ID of the bitmap font</td></tr><tr><td>FLOAT</td><td>start</td><td>The start index of the text to render</td></tr><tr><td>FLOAT</td><td>end</td><td>The end index of the text to render</td></tr><tr><td>FLOAT</td><td>x</td><td>The x-position of the anchor point</td></tr><tr><td>FLOAT</td><td>y</td><td>The y-position of the anchor point</td></tr><tr><td>FLOAT</td><td>panX</td><td>The horizontal pan from left(-1) to right(1), 0 being centered</td></tr><tr><td>FLOAT</td><td>panY</td><td>The vertical pan from top(-1) to bottom(1), 0 being centered</td></tr></table> |
| |
| |
| ## TextTransform [EXPERIMENTAL] (added in v7) |
| !!! WARNING |
| Experimental operation |
| |
| Transform a string (case conversion, trimming, etc.) |
| |
| 5 Fields, total size 21 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>TextTransform</td><td>Value: 199</td></tr> |
| <tr><td>INT</td><td>textId</td><td>The ID of the resulting transformed text</td></tr><tr><td>INT</td><td>srcId1</td><td>The ID of the source string</td></tr><tr><td>FLOAT</td><td>start</td><td>The start index of the transformation range</td></tr><tr><td>FLOAT</td><td>len</td><td>The length of the transformation range</td></tr><tr><td>INT</td><td>operation</td><td>The type of transformation to apply</td></tr></table> |
| |
| ### operation |
| | Name | Value | |
| | ---- | ---- | |
| | TEXT_TO_LOWERCASE | 1 |
| | TEXT_TO_UPPERCASE | 2 |
| | TEXT_TRIM | 3 |
| | TEXT_CAPITALIZE | 4 |
| | TEXT_UPPERCASE_FIRST_CHAR | 5 |
| |
| ## TextLayout |
| Text layout implementation |
| |
| 11 Fields, total size 45 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>TextLayout</td><td>Value: 208</td></tr> |
| <tr><td>INT</td><td>componentId</td><td>Unique ID for this component</td></tr><tr><td>INT</td><td>animationId</td><td>ID used to match components for animation purposes</td></tr><tr><td>INT</td><td>textId</td><td>The ID of the text to display</td></tr><tr><td>INT</td><td>color</td><td>The text color (ARGB)</td></tr><tr><td>FLOAT</td><td>fontSize</td><td>The font size in pixels</td></tr><tr><td>INT</td><td>fontStyle</td><td>The font style (0=normal, 1=italic)</td></tr><tr><td>FLOAT</td><td>fontWeight</td><td>The font weight [1..1000]</td></tr><tr><td>INT</td><td>fontFamilyId</td><td>The ID of the font family name string</td></tr><tr><td>INT</td><td>textAlign</td><td>The text alignment and flags</td></tr><tr><td>INT</td><td>overflow</td><td>The overflow strategy</td></tr><tr><td>INT</td><td>maxLines</td><td>The maximum number of lines</td></tr></table> |
| |
| # Layout Operations |
| High-level structural components (Box, Row, Column, etc.) used to build the UI hierarchy. |
| |
| Operations in this category: |
| | ID | Name | Version | Size (bytes) |
| | ---- | ---- | ---- | ---- | |
| | 2 | ComponentStart | v6 | 17 |
| | 200 | RootLayout | v6 | 5 |
| | 201 | LayoutContent | v6 | 5 |
| | 209 | HostAction | v6 | 5 |
| | 210 | HostNamedAction | v6 | 9 |
| | 216 | HostActionMetadata | v6 | 9 |
| | 217 | StateLayout | v6 | 21 |
| |
| ## ComponentStart |
| Basic component encapsulating draw commands. This is not resizable. |
| |
| 4 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ComponentStart</td><td>Value: 2</td></tr> |
| <tr><td>INT</td><td>type</td><td>Type of component</td></tr><tr><td>INT</td><td>componentId</td><td>Unique ID for this component</td></tr><tr><td>FLOAT</td><td>width</td><td>Width of the component</td></tr><tr><td>FLOAT</td><td>height</td><td>Height of the component</td></tr></table> |
| |
| |
| ## RootLayout |
| Root element for a document. Other components / layout managers are children in the component tree starting from this Root component. |
| |
| 1 Fields, total size 5 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>RootLayout</td><td>Value: 200</td></tr> |
| <tr><td>INT</td><td>componentId</td><td>Unique ID for this component</td></tr></table> |
| |
| |
| ## LayoutContent |
| Container for child components. BoxLayout, RowLayout and ColumnLayout expect a LayoutComponentContent as a child, encapsulating the components that need to be laid out. |
| |
| 1 Fields, total size 5 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>LayoutContent</td><td>Value: 201</td></tr> |
| <tr><td>INT</td><td>componentId</td><td>Unique ID for this component</td></tr></table> |
| |
| |
| ## HostAction |
| Host action. This operation represents a host action |
| |
| 1 Fields, total size 5 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>HostAction</td><td>Value: 209</td></tr> |
| <tr><td>INT</td><td>ACTION_ID</td><td>Host Action ID</td></tr></table> |
| |
| |
| ## HostNamedAction |
| Host Named action. This operation represents a host action |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>HostNamedAction</td><td>Value: 210</td></tr> |
| <tr><td>INT</td><td>TEXT_ID</td><td>Named Host Action Text ID</td></tr><tr><td>INT</td><td>VALUE_ID</td><td>Named Host Action Value ID</td></tr></table> |
| |
| |
| ## HostActionMetadata |
| Host action + metadata. This operation represents a host action that can also provides some metadata |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>HostActionMetadata</td><td>Value: 216</td></tr> |
| <tr><td>INT</td><td>ACTION_ID</td><td>Host Action ID</td></tr><tr><td>INT</td><td>METADATA</td><td>Host Action Text Metadata ID</td></tr></table> |
| |
| |
| ## StateLayout |
| A layout that switches between child layouts based on an index |
| |
| 5 Fields, total size 21 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>StateLayout</td><td>Value: 217</td></tr> |
| <tr><td>INT</td><td>componentId</td><td>Unique ID for this component</td></tr><tr><td>INT</td><td>animationId</td><td>ID for animation purposes</td></tr><tr><td>INT</td><td>horizontalPositioning</td><td>Horizontal positioning value</td></tr><tr><td>INT</td><td>verticalPositioning</td><td>Vertical positioning value</td></tr><tr><td>INT</td><td>indexId</td><td>The ID of the variable providing the current state index</td></tr></table> |
| |
| # Layout Managers |
| Higher-level components that manage the positioning and sizing of their children according to specific algorithms (Row, Column, Box, etc.). |
| |
| Operations in this category: |
| | ID | Name | Version | Size (bytes) |
| | ---- | ---- | ---- | ---- | |
| | 176 | FitBoxLayout | v6 | 17 |
| | 202 | BoxLayout | v6 | 17 |
| | 203 | RowLayout | v6 | 21 |
| | 204 | ColumnLayout | v6 | 21 |
| | 205 | CanvasLayout | v6 | 9 |
| | 230 | CollapsibleRow | v6 | 21 |
| | 233 | CollapsibleColumn | v6 | 21 |
| | 234 | ImageLayout | v6 | 21 |
| | 239 | CoreText | v7 | 85 + null x 4 + null x 4 |
| | 240 | FlowLayout | v7 | 21 |
| |
| ## FitBoxLayout |
| FitBox layout implementation. |
| |
| Only displays the first child component that fits in the available space. |
| |
| 4 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>FitBoxLayout</td><td>Value: 176</td></tr> |
| <tr><td>INT</td><td>componentId</td><td>Unique ID for this component</td></tr><tr><td>INT</td><td>animationId</td><td>ID used to match components for animation purposes</td></tr><tr><td>INT</td><td>horizontalPositioning</td><td>Horizontal positioning value</td></tr><tr><td>INT</td><td>verticalPositioning</td><td>Vertical positioning value</td></tr></table> |
| |
| ### horizontalPositioning |
| | Name | Value | |
| | ---- | ---- | |
| | START | 1 |
| | CENTER | 2 |
| | END | 3 |
| ### verticalPositioning |
| | Name | Value | |
| | ---- | ---- | |
| | TOP | 4 |
| | CENTER | 2 |
| | BOTTOM | 5 |
| ### FitBox Layout Algorithm |
| |
| FitBox is a specialized container that only renders the **first** child component that completely fits within the available space. |
| |
| #### Visual Illustration (Animated Fitting Logic) |
| <div id="fitboxLayoutContainer"> |
| <canvas id="fitboxLayoutCanvas_v1" width="500" height="300" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var phase = 0; |
| function draw() { |
| var canvas = document.getElementById('fitboxLayoutCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#BBBBBB', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 300); |
| |
| // Container size oscillates |
| var baseW = 350, baseH = 180; |
| var minW = 80, minH = 40; |
| var containerW = minW + (Math.sin(phase) + 1) / 2 * (baseW - minW); |
| var containerH = minH + (Math.sin(phase) + 1) / 2 * (baseH - minH); |
| phase += 0.015; |
| |
| var startX = 25, startY = 80; |
| |
| // Available Space |
| ctx.strokeStyle = DARK_GRAY; |
| ctx.setLineDash([5, 5]); |
| ctx.strokeRect(startX, startY, containerW, containerH); |
| ctx.setLineDash([]); |
| ctx.font = 'bold 16px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText('FitBox Size: ' + Math.round(containerW) + 'x' + Math.round(containerH), startX, startY - 15); |
| |
| // Define 3 potential children with different sizes |
| var children = [ |
| { id: 1, w: 250, h: 120, label: 'Child 1 (Large)' }, |
| { id: 2, w: 150, h: 80, label: 'Child 2 (Medium)' }, |
| { id: 3, w: 60, h: 30, label: 'Child 3 (Small)' } |
| ]; |
| |
| var selectedIdx = -1; |
| for (var i = 0; i < children.length; i++) { |
| if (children[i].w <= containerW && children[i].h <= containerH) { |
| selectedIdx = i; |
| break; |
| } |
| } |
| |
| // Draw the selection indicator |
| ctx.fillStyle = BLUE; |
| ctx.font = 'bold 18px Arial'; |
| ctx.textAlign = 'left'; |
| if (selectedIdx !== -1) { |
| ctx.fillText('Selected: ' + children[selectedIdx].label, startX + 250, 40); |
| |
| // Draw the selected child inside the container |
| ctx.fillStyle = 'rgba(0, 71, 171, 0.5)'; |
| ctx.fillRect(startX, startY, children[selectedIdx].w, children[selectedIdx].h); |
| ctx.strokeStyle = BLUE; |
| ctx.lineWidth = 3; |
| ctx.strokeRect(startX, startY, children[selectedIdx].w, children[selectedIdx].h); |
| |
| // Draw big white number in center |
| ctx.fillStyle = 'white'; |
| ctx.font = 'bold 50px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.textBaseline = 'middle'; |
| ctx.fillText(children[selectedIdx].id, startX + children[selectedIdx].w / 2, startY + children[selectedIdx].h / 2); |
| } else { |
| ctx.fillStyle = 'red'; |
| ctx.fillText('Selected: NONE (Too small)', startX + 250, 40); |
| } |
| |
| // Draw the list of candidates |
| ctx.textAlign = 'left'; |
| ctx.font = '14px Arial'; |
| for (var i = 0; i < children.length; i++) { |
| var active = (i === selectedIdx); |
| var tooBig = (children[i].w > containerW || children[i].h > containerH); |
| |
| ctx.fillStyle = active ? BLUE : (tooBig ? '#ccc' : DARK_GRAY); |
| var prefix = active ? 'â–¶ ' : ' '; |
| ctx.fillText(prefix + children[i].label + ' [' + children[i].w + 'x' + children[i].h + ']', 30, 230 + i * 20); |
| } |
| |
| requestAnimationFrame(draw); |
| } |
| draw(); |
| })(); |
| </script> |
| |
| #### Measurement and Selection |
| |
| 1. **Fitting Logic**: |
| - The layout iterates through its children in order. |
| - For each child, it checks its minimum required dimensions (defined by `WidthIn` or `HeightIn` modifiers). |
| - The first child whose minimum width and height are less than or equal to the FitBox's available constraints is selected. |
| |
| 2. **Visibility Control**: |
| - The selected child is marked as `VISIBLE`. |
| - All other children are marked as `GONE`. |
| - If no child fits, the FitBox itself may become `GONE`. |
| |
| 3. **Sizing**: |
| - The FitBox takes the measured size of the single selected child. |
| |
| |
| |
| ## BoxLayout |
| Box layout implementation. |
| |
| Child components are laid out independently from one another, |
| and painted in their hierarchy order (first children drawnbefore the latter). Horizontal and Vertical positioningare supported. |
| |
| 4 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>BoxLayout</td><td>Value: 202</td></tr> |
| <tr><td>INT</td><td>COMPONENT_ID</td><td>unique id for this component</td></tr><tr><td>INT</td><td>ANIMATION_ID</td><td>id used to match components, for animation purposes</td></tr><tr><td>INT</td><td>HORIZONTAL_POSITIONING</td><td>horizontal positioning value</td></tr><tr><td>INT</td><td>VERTICAL_POSITIONING</td><td>vertical positioning value</td></tr></table> |
| |
| ### HORIZONTAL_POSITIONING |
| | Name | Value | |
| | ---- | ---- | |
| | START | 1 |
| | CENTER | 2 |
| | END | 3 |
| ### VERTICAL_POSITIONING |
| | Name | Value | |
| | ---- | ---- | |
| | TOP | 4 |
| | CENTER | 2 |
| | BOTTOM | 5 |
| ### Box Layout Algorithm |
| |
| The Box Layout manager positions child components independently within its own bounds. It is the most basic container, allowing components to be stacked or positioned relative to the Box's edges. |
| |
| #### Visual Illustration (Alignment) |
| <div id="boxLayoutContainer"> |
| <canvas id="boxLayoutCanvas_v1" width="500" height="300" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('boxLayoutCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 300); |
| |
| var W=400, H=200, startX=50, startY=50; |
| |
| // Box Bounds |
| ctx.strokeStyle = GRAY; |
| ctx.setLineDash([5, 5]); |
| ctx.strokeRect(startX, startY, W, H); |
| ctx.setLineDash([]); |
| |
| function drawItem(offsetX, offsetY, label, color) { |
| var iw=100, ih=60; |
| ctx.fillStyle = color; |
| ctx.fillRect(startX + offsetX, startY + offsetY, iw, ih); |
| ctx.strokeStyle = BLUE; |
| ctx.lineWidth = 2; |
| ctx.strokeRect(startX + offsetX, startY + offsetY, iw, ih); |
| ctx.fillStyle = 'white'; |
| ctx.font = 'bold 14px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText(label, startX + offsetX + iw/2, startY + offsetY + ih/2 + 5); |
| } |
| |
| // Stacked/Aligned Items |
| drawItem(0, 0, 'Top-Start', 'rgba(0, 71, 171, 0.4)'); |
| drawItem(W-100, H-60, 'Bottom-End', 'rgba(0, 71, 171, 0.7)'); |
| drawItem((W-100)/2, (H-60)/2, 'Center', BLUE); |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 18px Arial'; |
| ctx.textAlign = 'left'; |
| ctx.fillText('Independent Child Alignment & Stacking', startX, startY - 20); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| #### Measurement Phase |
| |
| The measurement process is handled in `computeWrapSize` and `computeSize`. |
| |
| 1. **Independent Measurement**: |
| - Each child component is measured independently with the available width and height of the Box. |
| - If a child has a dynamic `LayoutCompute` modifier, its size is calculated based on the provided expressions before final measurement. |
| |
| 2. **Intrinsic Size (Wrap Content)**: |
| - If the Box is set to wrap its content, its `width` will be the maximum width among all its visible children. |
| - Similarly, its `height` will be the maximum height among all its visible children. |
| |
| #### Layout Phase |
| |
| In the `internalLayoutMeasure` method, children are positioned based on the Box's alignment rules. |
| |
| 1. **Alignment**: |
| - **Horizontal Positioning**: Children can be aligned to the `START`, `CENTER`, or `END` of the Box. |
| - **Vertical Positioning**: Children can be aligned to the `TOP`, `CENTER`, or `BOTTOM` of the Box. |
| - Each child is positioned using these global rules relative to the Box's internal area (after padding). |
| |
| 2. **Stacking**: |
| - Components are painted in their hierarchy order. The first child in the list is drawn first, and subsequent children are drawn on top if they overlap. |
| |
| ### Examples |
| |
| <table class="interpolation"> |
| <tr> |
| |
| <tr> |
| <td width="100">Top</td> |
| <td> |
| <img src='images/layout-BoxLayout-start-top.png' width="150"> |
| </td> |
| </tr> |
| |
| <tr> |
| <td width="100">Center</td> |
| <td> |
| <img src='images/layout-BoxLayout-center-center.png' width="150"> |
| </td> |
| </tr> |
| |
| <tr> |
| <td width="100">Bottom</td> |
| <td> |
| <img src='images/layout-BoxLayout-end-bottom.png' width="150"> |
| </td> |
| </tr> |
| |
| </tr> |
| </table> |
| |
| ## RowLayout |
| Row layout implementation, positioning components one after the other horizontally. |
| |
| It supports weight and horizontal/vertical positioning. |
| |
| 5 Fields, total size 21 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>RowLayout</td><td>Value: 203</td></tr> |
| <tr><td>INT</td><td>componentId</td><td>Unique ID for this component</td></tr><tr><td>INT</td><td>animationId</td><td>ID used to match components for animation purposes</td></tr><tr><td>INT</td><td>horizontalPositioning</td><td>Horizontal positioning value</td></tr><tr><td>INT</td><td>verticalPositioning</td><td>Vertical positioning value</td></tr><tr><td>FLOAT</td><td>spacedBy</td><td>Horizontal spacing between components</td></tr></table> |
| |
| ### horizontalPositioning |
| | Name | Value | |
| | ---- | ---- | |
| | START | 1 |
| | CENTER | 2 |
| | END | 3 |
| | SPACE_BETWEEN | 6 |
| | SPACE_EVENLY | 7 |
| | SPACE_AROUND | 8 |
| ### verticalPositioning |
| | Name | Value | |
| | ---- | ---- | |
| | TOP | 4 |
| | CENTER | 2 |
| | BOTTOM | 5 |
| ### Row Layout Algorithm |
| |
| The Row Layout manager positions child components horizontally one after another. It supports intrinsic sizing, weight-based distribution, and various horizontal and vertical alignment strategies. |
| |
| #### Visual Illustration (Weights) |
| <div id="rowLayoutContainer"> |
| <canvas id="rowLayoutCanvas_v1" width="500" height="200" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var phase = 0; |
| function draw() { |
| var canvas = document.getElementById('rowLayoutCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 200); |
| |
| var startX = 25, startY = 60, rowH = 80; |
| var minW = 150, maxW = 450; |
| var totalW = minW + (Math.sin(phase) + 1) / 2 * (maxW - minW); |
| phase += 0.02; |
| |
| // Row Bounds |
| ctx.strokeStyle = GRAY; |
| ctx.setLineDash([5, 5]); |
| ctx.strokeRect(startX, startY, totalW, rowH); |
| ctx.setLineDash([]); |
| |
| // Items |
| // Item 1: Fixed |
| var w1 = 80; |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fillRect(startX, startY, w1, rowH); |
| ctx.strokeStyle = BLUE; |
| ctx.lineWidth = 2; |
| ctx.strokeRect(startX, startY, w1, rowH); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 14px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText('Fixed (80px)', startX + w1/2, startY + rowH/2 + 5); |
| |
| // Remaining width distribution |
| var remainingW = Math.max(0, totalW - w1); |
| |
| // Item 2: Weight 1 |
| var w2 = remainingW * (1/3); |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fillRect(startX + w1, startY, w2, rowH); |
| ctx.strokeRect(startX + w1, startY, w2, rowH); |
| ctx.fillStyle = BLUE; |
| if (w2 > 50) { |
| var p2 = Math.round(w2 / totalW * 100); |
| ctx.fillText('Weight 1 (' + p2 + '%)', startX + w1 + w2/2, startY + rowH/2 + 5); |
| } |
| |
| // Item 3: Weight 2 |
| var w3 = remainingW * (2/3); |
| ctx.fillStyle = BLUE; |
| ctx.fillRect(startX + w1 + w2, startY, w3, rowH); |
| ctx.strokeRect(startX + w1 + w2, startY, w3, rowH); |
| ctx.fillStyle = 'white'; |
| if (w3 > 50) { |
| var p3 = Math.round(w3 / totalW * 100); |
| ctx.fillText('Weight 2 (' + p3 + '%)', startX + w1 + w2 + w3/2, startY + rowH/2 + 5); |
| } |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 18px Arial'; |
| ctx.textAlign = 'left'; |
| ctx.fillText('Horizontal Stacking with Weights', startX, startY - 20); |
| |
| requestAnimationFrame(draw); |
| } |
| draw(); |
| })(); |
| </script> |
| |
| #### Measurement Phase |
| |
| The measurement process is handled in `computeWrapSize` and `computeSize`. |
| |
| 1. **Weight Distribution**: |
| - The layout first identifies children with horizontal weights (`WidthModifier.weight`). |
| - Children **without** weights are measured first with the available width. The accumulated width of these children is subtracted from the total available space. |
| - The remaining space is then distributed among the weighted children proportional to their weight value. |
| - Each weighted child is measured with its calculated fixed width. |
| |
| 2. **Intrinsic Size**: |
| - The `width` of the Row is the sum of all children's measured widths plus the `spacedBy` gaps between them. |
| - The `height` of the Row is the maximum height among all measured children. |
| |
| #### Layout Phase |
| |
| Once children are measured, the `internalLayoutMeasure` method determines their final `(x, y)` coordinates. |
| |
| 1. **Horizontal Positioning**: |
| - **START**: Children are packed at the beginning of the row. |
| - **CENTER**: The entire block of children is centered horizontally. |
| - **END**: Children are packed at the end of the row. |
| - **SPACE_BETWEEN**: The first child is at the start, the last at the end, and the remaining space is distributed evenly between children. |
| - **SPACE_EVENLY**: Space is distributed such that the gap between any two items and the edges is equal. |
| - **SPACE_AROUND**: Space is distributed such that the gap between items is equal, and the gap at the ends is half of the internal gap. |
| |
| 2. **Vertical Positioning**: |
| - Applied individually to each child within the calculated Row height. |
| - **TOP**: Child is at the top. |
| - **CENTER**: Child is vertically centered. |
| - **BOTTOM**: Child is at the bottom. |
| - **Baseline Alignment**: If children have `AlignBy` modifiers, they are aligned based on their specified baseline or anchor. |
| |
| 3. **Spacing**: |
| - The `spacedBy` value is added between each child in all positioning modes. |
| |
| ### Examples |
| |
| <table class="interpolation"> |
| <tr> |
| |
| <tr> |
| <td width="100">Start</td> |
| <td> |
| <img src='images/layout-RowLayout-start-top.png' width="400"> |
| </td> |
| </tr> |
| |
| <tr> |
| <td width="100">Center</td> |
| <td> |
| <img src='images/layout-RowLayout-center-top.png' width="400"> |
| </td> |
| </tr> |
| |
| <tr> |
| <td width="100">End</td> |
| <td> |
| <img src='images/layout-RowLayout-end-top.png' width="400"> |
| </td> |
| </tr> |
| |
| <tr> |
| <td width="100">SpaceEvenly</td> |
| <td> |
| <img src='images/layout-RowLayout-space-evenly-top.png' width="400"> |
| </td> |
| </tr> |
| |
| <tr> |
| <td width="100">SpaceAround</td> |
| <td> |
| <img src='images/layout-RowLayout-space-around-top.png' width="400"> |
| </td> |
| </tr> |
| |
| <tr> |
| <td width="100">SpaceBetween</td> |
| <td> |
| <img src='images/layout-RowLayout-space-between-top.png' width="400"> |
| </td> |
| </tr> |
| |
| </tr> |
| </table> |
| |
| ## ColumnLayout |
| Column layout implementation, positioning components one after the other vertically. |
| |
| It supports weight and horizontal/vertical positioning. |
| |
| 5 Fields, total size 21 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ColumnLayout</td><td>Value: 204</td></tr> |
| <tr><td>INT</td><td>componentId</td><td>Unique ID for this component</td></tr><tr><td>INT</td><td>animationId</td><td>ID used to match components for animation purposes</td></tr><tr><td>INT</td><td>horizontalPositioning</td><td>Horizontal positioning value</td></tr><tr><td>INT</td><td>verticalPositioning</td><td>Vertical positioning value</td></tr><tr><td>FLOAT</td><td>spacedBy</td><td>Horizontal spacing between components</td></tr></table> |
| |
| ### horizontalPositioning |
| | Name | Value | |
| | ---- | ---- | |
| | START | 1 |
| | CENTER | 2 |
| | END | 3 |
| ### verticalPositioning |
| | Name | Value | |
| | ---- | ---- | |
| | TOP | 4 |
| | CENTER | 2 |
| | BOTTOM | 5 |
| | SPACE_BETWEEN | 6 |
| | SPACE_EVENLY | 7 |
| | SPACE_AROUND | 8 |
| ### Column Layout Algorithm |
| |
| The Column Layout manager positions child components vertically one after another. It is the vertical counterpart to the Row Layout. |
| |
| #### Visual Illustration (Weights) |
| <div id="columnLayoutContainer"> |
| <canvas id="columnLayoutCanvas_v1" width="500" height="400" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var phase = 0; |
| function draw() { |
| var canvas = document.getElementById('columnLayoutCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 400); |
| |
| var startX = 150, startY = 50, colW = 200; |
| var minH = 100, maxH = 320; |
| var totalH = minH + (Math.sin(phase) + 1) / 2 * (maxH - minH); |
| phase += 0.02; |
| |
| // Column Bounds |
| ctx.strokeStyle = GRAY; |
| ctx.setLineDash([5, 5]); |
| ctx.strokeRect(startX, startY, colW, totalH); |
| ctx.setLineDash([]); |
| |
| // Items |
| // Item 1: Fixed |
| var h1 = 60; |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fillRect(startX, startY, colW, h1); |
| ctx.strokeStyle = BLUE; |
| ctx.lineWidth = 2; |
| ctx.strokeRect(startX, startY, colW, h1); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 14px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText('Fixed (60px)', startX + colW/2, startY + h1/2 + 5); |
| |
| // Remaining height distribution |
| var remainingH = Math.max(0, totalH - h1); |
| |
| // Item 2: Weight 1 |
| var h2 = remainingH * (1/3); |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fillRect(startX, startY + h1, colW, h2); |
| ctx.strokeRect(startX, startY + h1, colW, h2); |
| ctx.fillStyle = BLUE; |
| if (h2 > 25) { |
| var p2 = Math.round(h2 / totalH * 100); |
| ctx.fillText('Weight 1 (' + p2 + '%)', startX + colW/2, startY + h1 + h2/2 + 5); |
| } |
| |
| // Item 3: Weight 2 |
| var h3 = remainingH * (2/3); |
| ctx.fillStyle = BLUE; |
| ctx.fillRect(startX, startY + h1 + h2, colW, h3); |
| ctx.strokeRect(startX, startY + h1 + h2, colW, h3); |
| ctx.fillStyle = 'white'; |
| if (h3 > 25) { |
| var p3 = Math.round(h3 / totalH * 100); |
| ctx.fillText('Weight 2 (' + p3 + '%)', startX + colW/2, startY + h1 + h2 + h3/2 + 5); |
| } |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 18px Arial'; |
| ctx.textAlign = 'left'; |
| ctx.fillText('Vertical Stacking with Weights', 140, 30); |
| |
| requestAnimationFrame(draw); |
| } |
| draw(); |
| })(); |
| </script> |
| |
| #### Measurement Phase |
| |
| 1. **Weight Distribution**: |
| - The layout identifies children with vertical weights (`HeightModifier.weight`). |
| - Children **without** weights are measured first. Their combined height is subtracted from the total available height. |
| - The remaining vertical space is distributed among weighted children proportional to their weights. |
| |
| 2. **Intrinsic Size**: |
| - The `height` of the Column is the sum of all children's measured heights plus the `spacedBy` vertical gaps. |
| - The `width` of the Column is the maximum width among all measured children. |
| |
| #### Layout Phase |
| |
| 1. **Vertical Positioning**: |
| - **TOP**: Children are packed at the top. |
| - **CENTER**: The block of children is centered vertically. |
| - **BOTTOM**: Children are packed at the bottom. |
| - **SPACE_BETWEEN**, **SPACE_EVENLY**, **SPACE_AROUND**: Similar to Row Layout, but applied vertically to distribute children across the available height. |
| |
| 2. **Horizontal Positioning**: |
| - Applied individually to each child within the Column's width. |
| - **START**, **CENTER**, **END**: Aligns children horizontally within the column. |
| |
| 3. **Spacing**: |
| - The `spacedBy` value is added between each child vertically. |
| |
| ### Examples |
| |
| <table class="interpolation"> |
| <tr> |
| |
| <td> |
| <table class="interpolation" width="100" height="400"> |
| <tr><td><img src='images/layout-ColumnLayout-start-top.png' height="400"></td></tr> |
| <tr><td width="100">Top</td></tr> |
| </table> |
| </td> |
| |
| <td> |
| <table class="interpolation" width="100" height="400"> |
| <tr><td><img src='images/layout-ColumnLayout-start-center.png' height="400"></td></tr> |
| <tr><td width="100">Center</td></tr> |
| </table> |
| </td> |
| |
| <td> |
| <table class="interpolation" width="100" height="400"> |
| <tr><td><img src='images/layout-ColumnLayout-start-bottom.png' height="400"></td></tr> |
| <tr><td width="100">Bottom</td></tr> |
| </table> |
| </td> |
| |
| <td> |
| <table class="interpolation" width="100" height="400"> |
| <tr><td><img src='images/layout-ColumnLayout-start-space-evenly.png' height="400"></td></tr> |
| <tr><td width="100">SpaceEvenly</td></tr> |
| </table> |
| </td> |
| |
| <td> |
| <table class="interpolation" width="100" height="400"> |
| <tr><td><img src='images/layout-ColumnLayout-start-space-around.png' height="400"></td></tr> |
| <tr><td width="100">SpaceAround</td></tr> |
| </table> |
| </td> |
| |
| <td> |
| <table class="interpolation" width="100" height="400"> |
| <tr><td><img src='images/layout-ColumnLayout-start-space-between.png' height="400"></td></tr> |
| <tr><td width="100">SpaceBetween</td></tr> |
| </table> |
| </td> |
| |
| </tr> |
| </table> |
| |
| ## CanvasLayout |
| Canvas implementation. Encapsulates drawing operations. |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>CanvasLayout</td><td>Value: 205</td></tr> |
| <tr><td>INT</td><td>componentId</td><td>Unique ID for this component</td></tr><tr><td>INT</td><td>animationId</td><td>ID used to match components for animation purposes</td></tr></table> |
| |
| ### Canvas Layout Algorithm |
| |
| Canvas Layout is a container optimized for custom drawing operations while still supporting child components. |
| |
| #### Visual Illustration |
| <div id="canvasLayoutContainer"> |
| <canvas id="canvasLayoutCanvas_v1" width="500" height="300" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('canvasLayoutCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', DARK_BLUE = '#002366', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 300); |
| |
| var cx = 50, cy = 50, cw = 400, ch = 200; |
| |
| // Canvas Bounds |
| ctx.strokeStyle = DARK_GRAY; |
| ctx.lineWidth = 2; |
| ctx.strokeRect(cx, cy, cw, ch); |
| ctx.fillStyle = '#fdfdfd'; |
| ctx.fillRect(cx, cy, cw, ch); |
| |
| // Custom Drawing (relative to canvas top-left) |
| ctx.save(); |
| ctx.translate(cx, cy); |
| |
| // Drawing 1: Grid |
| ctx.strokeStyle = '#eee'; |
| ctx.lineWidth = 1; |
| for (var i = 0; i < cw; i += 40) { ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, ch); ctx.stroke(); } |
| for (var j = 0; j < ch; j += 40) { ctx.beginPath(); ctx.moveTo(0, j); ctx.lineTo(cw, j); ctx.stroke(); } |
| |
| // Drawing 2: Shapes |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.beginPath(); |
| ctx.arc(100, 100, 60, 0, Math.PI*2); |
| ctx.fill(); |
| ctx.strokeStyle = BLUE; |
| ctx.stroke(); |
| |
| // Drawing 3: Child Component (e.g., a button or text block) |
| var childX = 220, childY = 80, childW = 120, childH = 40; |
| ctx.fillStyle = DARK_BLUE; |
| ctx.fillRect(childX, childY, childW, childH); |
| ctx.fillStyle = 'white'; |
| ctx.font = 'bold 14px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText('Child Component', childX + childW/2, childY + 25); |
| |
| // Annotation for child positioning |
| ctx.strokeStyle = DARK_GRAY; |
| ctx.setLineDash([2, 2]); |
| ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(childX, childY); ctx.stroke(); |
| ctx.setLineDash([]); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = '12px Arial'; |
| ctx.fillText('LayoutCompute(x, y)', childX/2, childY/2); |
| |
| ctx.restore(); |
| |
| ctx.font = 'bold 20px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.textAlign = 'left'; |
| ctx.fillText('Canvas Layout', cx, cy - 15); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| #### Layout Logic |
| |
| 1. **Drawing Encapsulation**: |
| - Unlike other layouts, CanvasLayout primarily serves as a scope for `Draw` operations (DrawRect, DrawCircle, etc.). |
| - All drawing instructions contained within the Canvas are executed relative to the Canvas's top-left corner. |
| |
| 2. **Child Positioning**: |
| - Children of a CanvasLayout are by default positioned at `(0, 0)` and sized to fill the entire Canvas area if standard `LayoutComponentContent` is used. |
| - However, if `LayoutCompute` modifiers are used, children can be positioned anywhere on top of the custom drawing. |
| |
| |
| |
| ## CollapsibleRow |
| A row layout that can hide children if space is insufficient |
| |
| 5 Fields, total size 21 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>CollapsibleRow</td><td>Value: 230</td></tr> |
| <tr><td>INT</td><td>componentId</td><td>Unique ID for this component</td></tr><tr><td>INT</td><td>animationId</td><td>ID for animation purposes</td></tr><tr><td>INT</td><td>horizontalPositioning</td><td>Horizontal positioning value</td></tr><tr><td>INT</td><td>verticalPositioning</td><td>Vertical positioning value</td></tr><tr><td>FLOAT</td><td>spacedBy</td><td>Horizontal spacing between components</td></tr></table> |
| |
| ### Collapsible Row Layout Algorithm |
| |
| Collapsible Row extends Row Layout with the ability to automatically hide children that do not fit within the available horizontal space. |
| |
| #### Basic Overflow Animation |
| <div id="collapsibleRowContainer"> |
| <canvas id="collapsibleRowCanvas_v1" width="500" height="180" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| #### Priority-Based Hiding Animation |
| In this example, **Item 2** has a higher priority value (lower importance), so it is hidden **first**. When it disappears, the layout collapses and moves Item 3 into its place. |
| |
| <div id="collapsibleRowPriorityContainer"> |
| <canvas id="collapsibleRowPriorityCanvas_v1" width="500" height="180" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| var itemW = 120, itemH = 60, spacing = 10, startX = 25, startY = 70; |
| |
| function drawItem(ctx, idx, x, y, fits, label) { |
| if (!fits) return; // In a real layout, GONE items aren't drawn at all |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fillRect(x, y, itemW, itemH); |
| ctx.strokeStyle = BLUE; |
| ctx.lineWidth = 2; |
| ctx.strokeRect(x, y, itemW, itemH); |
| ctx.fillStyle = BLUE; |
| ctx.textAlign = 'center'; |
| ctx.font = 'bold 14px Arial'; |
| ctx.fillText(label, x + itemW/2, y + itemH/2 + 5); |
| } |
| |
| var phase1 = 0, phase2 = 0; |
| function animate() { |
| var c1 = document.getElementById('collapsibleRowCanvas_v1'); |
| var c2 = document.getElementById('collapsibleRowPriorityCanvas_v1'); |
| |
| if (c1) { |
| var ctx = c1.getContext('2d'); |
| ctx.clearRect(0, 0, 500, 180); |
| var w = 100 + (Math.sin(phase1) + 1) / 2 * 350; |
| phase1 += 0.015; |
| ctx.strokeStyle = GRAY; ctx.setLineDash([5, 5]); ctx.strokeRect(startX, startY, w, itemH); ctx.setLineDash([]); |
| ctx.fillStyle = GRAY; ctx.font = 'bold 14px Arial'; ctx.textAlign='left'; ctx.fillText('Width: ' + Math.round(w) + 'px', startX, startY - 10); |
| |
| var currentX = startX; |
| for (var i=0; i<3; i++) { |
| var fits = ( (currentX + itemW - startX) <= w ); |
| if (fits) { |
| drawItem(ctx, i+1, currentX, startY, true, 'ITEM ' + (i+1)); |
| currentX += itemW + spacing; |
| } |
| } |
| } |
| |
| if (c2) { |
| var ctx = c2.getContext('2d'); |
| ctx.clearRect(0, 0, 500, 180); |
| var w = 100 + (Math.sin(phase2) + 1) / 2 * 350; |
| phase2 += 0.015; |
| ctx.strokeStyle = GRAY; ctx.setLineDash([5, 5]); ctx.strokeRect(startX, startY, w, itemH); ctx.setLineDash([]); |
| ctx.fillStyle = GRAY; ctx.font = 'bold 14px Arial'; ctx.textAlign='left'; ctx.fillText('Width: ' + Math.round(w) + 'px', startX, startY - 10); |
| |
| var items = [ |
| {id: 1, p: 10}, |
| {id: 2, p: 100}, |
| {id: 3, p: 20} |
| ]; |
| |
| var visibility = {}; |
| var accumulated = 0; |
| // Process in priority order (lowest P first) |
| var priorityProcess = [...items].sort((a,b) => a.p - b.p); |
| for (var itm of priorityProcess) { |
| if (accumulated + itemW <= w) { |
| visibility[itm.id] = true; |
| accumulated += itemW + spacing; |
| } else { |
| visibility[itm.id] = false; |
| } |
| } |
| |
| var currentX = startX; |
| for (var itm of items) { |
| if (visibility[itm.id]) { |
| drawItem(ctx, itm.id, currentX, startY, true, 'ITEM ' + itm.id + ' (P:' + itm.p + ')'); |
| currentX += itemW + spacing; |
| } |
| } |
| } |
| requestAnimationFrame(animate); |
| } |
| animate(); |
| })(); |
| </script> |
| |
| #### Measurement Phase |
| |
| 1. **Priority Sorting**: |
| - If children have `CollapsiblePriority` modifiers, they are sorted by priority (lowest values are kept first). |
| - If priorities are equal, the original hierarchy order is used. |
| |
| 2. **Overflow Detection**: |
| - Children are measured one by one. |
| - The layout keeps track of the accumulated width. |
| - As soon as a child's width would cause the total to exceed the available width, that child and all subsequent children (in priority order) are marked as `GONE`. |
| |
| #### Layout Phase |
| |
| - The remaining `VISIBLE` children are laid out exactly like a standard Row Layout, using the specified horizontal and vertical positioning. |
| |
| |
| |
| ## CollapsibleColumn |
| A column layout that can hide children if space is insufficient |
| |
| 5 Fields, total size 21 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>CollapsibleColumn</td><td>Value: 233</td></tr> |
| <tr><td>INT</td><td>componentId</td><td>Unique ID for this component</td></tr><tr><td>INT</td><td>animationId</td><td>ID for animation purposes</td></tr><tr><td>INT</td><td>horizontalPositioning</td><td>Horizontal positioning value</td></tr><tr><td>INT</td><td>verticalPositioning</td><td>Vertical positioning value</td></tr><tr><td>FLOAT</td><td>spacedBy</td><td>Vertical spacing between components</td></tr></table> |
| |
| ### Collapsible Column Layout Algorithm |
| |
| Collapsible Column extends Column Layout by hiding children that exceed the available vertical space. |
| |
| #### Basic Overflow Animation |
| <div id="collapsibleColumnContainer"> |
| <canvas id="collapsibleColumnCanvas_v1" width="500" height="400" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| #### Priority-Based Hiding Animation |
| In this example, **Item 2** has a higher priority value (lower importance), so it is hidden **first**. Notice how Item 3 shifts up to fill the gap when Item 2 is removed. |
| |
| <div id="collapsibleColumnPriorityContainer"> |
| <canvas id="collapsibleColumnPriorityCanvas_v1" width="500" height="400" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| var itemW = 200, itemH = 80, spacing = 10, startX = 150, startY = 50; |
| |
| function drawItem(ctx, idx, x, y, fits, label) { |
| if (!fits) return; |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fillRect(x, y, itemW, itemH); |
| ctx.strokeStyle = BLUE; |
| ctx.lineWidth = 2; |
| ctx.strokeRect(x, y, itemW, itemH); |
| ctx.fillStyle = BLUE; |
| ctx.textAlign = 'center'; |
| ctx.font = 'bold 14px Arial'; |
| ctx.fillText(label, x + itemW/2, y + itemH/2 + 5); |
| } |
| |
| var phase1 = 0, phase2 = 0; |
| function animate() { |
| var c1 = document.getElementById('collapsibleColumnCanvas_v1'); |
| var c2 = document.getElementById('collapsibleColumnPriorityCanvas_v1'); |
| |
| if (c1) { |
| var ctx = c1.getContext('2d'); |
| ctx.clearRect(0, 0, 500, 400); |
| var h = 50 + (Math.sin(phase1) + 1) / 2 * 300; |
| phase1 += 0.015; |
| ctx.strokeStyle = GRAY; ctx.setLineDash([5, 5]); ctx.strokeRect(startX, startY, itemW, h); ctx.setLineDash([]); |
| ctx.fillStyle = GRAY; ctx.font = 'bold 14px Arial'; ctx.textAlign='left'; ctx.fillText('Height: ' + Math.round(h) + 'px', startX, startY - 10); |
| |
| var currentY = startY; |
| for (var i=0; i<3; i++) { |
| var fits = ( (currentY + itemH - startY) <= h ); |
| if (fits) { |
| drawItem(ctx, i+1, startX, currentY, true, 'ITEM ' + (i+1)); |
| currentY += itemH + spacing; |
| } |
| } |
| } |
| |
| if (c2) { |
| var ctx = c2.getContext('2d'); |
| ctx.clearRect(0, 0, 500, 400); |
| var h = 50 + (Math.sin(phase2) + 1) / 2 * 300; |
| phase2 += 0.015; |
| ctx.strokeStyle = GRAY; ctx.setLineDash([5, 5]); ctx.strokeRect(startX, startY, itemW, h); ctx.setLineDash([]); |
| ctx.fillStyle = GRAY; ctx.font = 'bold 14px Arial'; ctx.textAlign='left'; ctx.fillText('Height: ' + Math.round(h) + 'px', startX, startY - 10); |
| |
| var items = [ |
| {id: 1, p: 10}, |
| {id: 2, p: 100}, |
| {id: 3, p: 20} |
| ]; |
| |
| var visibility = {}; |
| var accumulated = 0; |
| var priorityProcess = [...items].sort((a,b) => a.p - b.p); |
| for (var itm of priorityProcess) { |
| if (accumulated + itemH <= h) { |
| visibility[itm.id] = true; |
| accumulated += itemH + spacing; |
| } else { |
| visibility[itm.id] = false; |
| } |
| } |
| |
| var currentY = startY; |
| for (var itm of items) { |
| if (visibility[itm.id]) { |
| drawItem(ctx, itm.id, startX, currentY, true, 'ITEM ' + itm.id + ' (P:' + itm.p + ')'); |
| currentY += itemH + spacing; |
| } |
| } |
| } |
| requestAnimationFrame(animate); |
| } |
| animate(); |
| })(); |
| </script> |
| |
| #### Measurement Phase |
| |
| 1. **Priority Sorting**: |
| - Children are sorted based on their `CollapsiblePriority` modifier. |
| |
| 2. **Overflow Detection**: |
| - The layout measures children and accumulates their heights. |
| - Any child that would cause the total height to exceed the Column's maximum height is marked as `GONE`. |
| |
| #### Layout Phase |
| |
| - Visible children are positioned vertically according to standard Column Layout rules (Top, Center, Bottom, etc.). |
| |
| |
| |
| ## ImageLayout |
| Image layout implementation |
| |
| 5 Fields, total size 21 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ImageLayout</td><td>Value: 234</td></tr> |
| <tr><td>INT</td><td>componentId</td><td>Unique ID for this component</td></tr><tr><td>INT</td><td>animationId</td><td>ID used to match components for animation purposes</td></tr><tr><td>INT</td><td>bitmapId</td><td>The ID of the bitmap to display</td></tr><tr><td>INT</td><td>scaleType</td><td>The scale type to apply</td></tr><tr><td>FLOAT</td><td>alpha</td><td>The alpha transparency [0..1]</td></tr></table> |
| |
| ### Image Layout Algorithm |
| |
| Image Layout handles the display and scaling of bitmap resources. |
| |
| #### Visual Illustration (Scale Modes) |
| <div id="imageLayoutContainer"> |
| <canvas id="imageLayoutCanvas_v1" width="500" height="280" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('imageLayoutCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 500, 280); |
| |
| function drawScaleDemo(x, y, label, mode) { |
| var vw = 120, vh = 120; // Viewport |
| var iw = 160, ih = 80; // Image (wider than high) |
| |
| ctx.save(); |
| ctx.translate(x, y); |
| |
| // Label |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 16px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText(label, vw/2, -15); |
| |
| // Viewport bounds |
| ctx.strokeStyle = '#eee'; |
| ctx.setLineDash([4, 4]); |
| ctx.strokeRect(0, 0, vw, vh); |
| ctx.setLineDash([]); |
| |
| ctx.save(); |
| ctx.beginPath(); |
| ctx.rect(0, 0, vw, vh); |
| ctx.clip(); |
| |
| var dx, dy, dw, dh; |
| |
| if (mode === 'FIT') { |
| var scale = Math.min(vw / iw, vh / ih); |
| dw = iw * scale; |
| dh = ih * scale; |
| dx = (vw - dw) / 2; |
| dy = (vh - dh) / 2; |
| } else if (mode === 'FILL') { |
| dx = 0; dy = 0; dw = vw; dh = vh; |
| } else if (mode === 'CENTER_CROP') { |
| var scale = Math.max(vw / iw, vh / ih); |
| dw = iw * scale; |
| dh = ih * scale; |
| dx = (vw - dw) / 2; |
| dy = (vh - dh) / 2; |
| } |
| |
| // Draw Placeholder Image |
| ctx.fillStyle = BLUE; |
| ctx.fillRect(dx, dy, dw, dh); |
| ctx.strokeStyle = 'white'; |
| ctx.lineWidth = 2; |
| ctx.strokeRect(dx + 5, dy + 5, dw - 10, dh - 10); |
| ctx.beginPath(); |
| ctx.moveTo(dx, dy); ctx.lineTo(dx + dw, dy + dh); |
| ctx.moveTo(dx + dw, dy); ctx.lineTo(dx, dy + dh); |
| ctx.stroke(); |
| |
| ctx.restore(); |
| |
| // Border |
| ctx.strokeStyle = DARK_GRAY; |
| ctx.lineWidth = 2; |
| ctx.strokeRect(0, 0, vw, vh); |
| |
| ctx.restore(); |
| } |
| |
| drawScaleDemo(40, 60, 'FIT', 'FIT'); |
| drawScaleDemo(190, 60, 'CENTER_CROP', 'CENTER_CROP'); |
| drawScaleDemo(340, 60, 'FILL', 'FILL'); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| #### Sizing |
| |
| 1. **Wrap Content**: |
| - If sizing is not constrained, the component takes the intrinsic width and height of the source bitmap. |
| |
| 2. **Scaling**: |
| - Calculates a destination rectangle within the component's bounds based on the `scaleType`. |
| - Supports typical scaling modes like `Fit`, `CenterCrop`, and `Fill`. |
| |
| #### Rendering |
| |
| - Applies alpha transparency. |
| - Clips the bitmap to the component's bounds if the scaled image exceeds them. |
| |
| |
| |
| ## CoreText [EXPERIMENTAL] (added in v7) |
| !!! WARNING |
| Experimental operation |
| |
| Core text layout implementation with advanced styling |
| |
| 26 Fields, total size 85 + null x 4 + null x 4 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>CoreText</td><td>Value: 239</td></tr> |
| <tr><td>INT</td><td>textId</td><td>The ID of the text to display</td></tr><tr><td>INT</td><td>componentId</td><td>Unique ID for this component</td></tr><tr><td>INT</td><td>animationId</td><td>ID for animation purposes</td></tr><tr><td>INT</td><td>color</td><td>The text color (ARGB)</td></tr><tr><td>INT</td><td>colorId</td><td>The ID of the color variable</td></tr><tr><td>FLOAT</td><td>fontSize</td><td>The font size</td></tr><tr><td>FLOAT</td><td>minFontSize</td><td>Minimum font size for autosize</td></tr><tr><td>FLOAT</td><td>maxFontSize</td><td>Maximum font size for autosize</td></tr><tr><td>INT</td><td>fontStyle</td><td>The font style</td></tr><tr><td>FLOAT</td><td>fontWeight</td><td>The font weight</td></tr><tr><td>INT</td><td>fontFamily</td><td>The ID of the font family</td></tr><tr><td>INT</td><td>textAlign</td><td>Text alignment</td></tr><tr><td>INT</td><td>overflow</td><td>Overflow behavior</td></tr><tr><td>INT</td><td>maxLines</td><td>Maximum number of lines</td></tr><tr><td>FLOAT</td><td>letterSpacing</td><td>Letter spacing</td></tr><tr><td>FLOAT</td><td>lineHeightAdd</td><td>Line height addition</td></tr><tr><td>FLOAT</td><td>lineHeightMultiplier</td><td>Line height multiplier</td></tr><tr><td>INT</td><td>lineBreakStrategy</td><td>Line break strategy</td></tr><tr><td>INT</td><td>hyphenationFrequency</td><td>Hyphenation frequency</td></tr><tr><td>INT</td><td>justificationMode</td><td>Justification mode</td></tr><tr><td>BOOLEAN</td><td>underline</td><td>Whether to underline</td></tr><tr><td>BOOLEAN</td><td>strikethrough</td><td>Whether to strikethrough</td></tr><tr><td>INT[]</td><td>fontAxis</td><td>Font axis tags</td></tr><tr><td>FLOAT[]</td><td>fontAxisValues</td><td>Font axis values</td></tr><tr><td>BOOLEAN</td><td>autosize</td><td>Whether to enable autosize</td></tr><tr><td>INT</td><td>flags</td><td>Behavior flags</td></tr></table> |
| |
| ### Core Text Layout Algorithm |
| |
| Core Text is a leaf layout component responsible for high-fidelity text rendering and measurement. |
| |
| #### Visual Illustration (Autosize and Alignment) |
| <div id="coreTextContainer"> |
| <canvas id="coreTextCanvas_v1" width="500" height="280" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var phase = 0; |
| function draw() { |
| var canvas = document.getElementById('coreTextCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 500, 280); |
| |
| // Box size oscillates for Autosize demo |
| var boxW = 150 + Math.sin(phase) * 50; |
| var boxH = 100 + Math.cos(phase) * 30; |
| phase += 0.02; |
| |
| var text = "DYNAMIC TEXT"; |
| |
| // 1. Autosize Demo |
| ctx.save(); |
| ctx.translate(50, 60); |
| ctx.strokeStyle = GRAY; |
| ctx.setLineDash([4, 4]); |
| ctx.strokeRect(0, 0, boxW, boxH); |
| ctx.setLineDash([]); |
| |
| // Simple heuristic for autosize illustration |
| var fontSize = Math.min(boxW / 7, boxH / 1.5); |
| ctx.font = 'bold ' + fontSize + 'px Arial'; |
| ctx.fillStyle = BLUE; |
| ctx.textAlign = 'center'; |
| ctx.textBaseline = 'middle'; |
| ctx.fillText(text, boxW/2, boxH/2); |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 16px Arial'; |
| ctx.textAlign = 'left'; |
| ctx.fillText('Autosize: ' + Math.round(fontSize) + 'px', 0, -15); |
| ctx.restore(); |
| |
| // 2. Alignment Demo |
| ctx.save(); |
| ctx.translate(300, 60); |
| var alignBoxW = 160, alignBoxH = 150; |
| ctx.strokeStyle = '#eee'; |
| ctx.strokeRect(0, 0, alignBoxW, alignBoxH); |
| |
| ctx.font = '18px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| |
| // Left |
| ctx.textAlign = 'left'; |
| ctx.fillText('Align: Left', 10, 30); |
| ctx.fillStyle = BLUE; |
| ctx.fillRect(10, 35, 100, 4); |
| |
| // Center |
| ctx.fillStyle = DARK_GRAY; |
| ctx.textAlign = 'center'; |
| ctx.fillText('Align: Center', alignBoxW/2, 80); |
| ctx.fillStyle = BLUE; |
| ctx.fillRect(alignBoxW/2 - 50, 85, 100, 4); |
| |
| // Right |
| ctx.fillStyle = DARK_GRAY; |
| ctx.textAlign = 'right'; |
| ctx.fillText('Align: Right', alignBoxW - 10, 130); |
| ctx.fillStyle = BLUE; |
| ctx.fillRect(alignBoxW - 110, 135, 100, 4); |
| |
| ctx.restore(); |
| |
| requestAnimationFrame(draw); |
| } |
| draw(); |
| })(); |
| </script> |
| |
| #### Measurement Phase |
| |
| 1. **Complex Text Analysis**: |
| - The algorithm checks for features requiring complex layout: line breaks (\n), tabs (\t), letter spacing, varied line heights, or overflow ellipsis. |
| - If these are present, it uses a complex layout engine to calculate line breaks and word wrapping. |
| |
| 2. **Autosize**: |
| - If `autosize` is enabled, the layout performs a binary search between `minFontSize` and `maxFontSize`. |
| - It finds the largest font size that allows the text to fit within the provided `maxWidth` and `maxHeight` without causing unexpected hyphenation or overflow. |
| |
| 3. **Intrinsic Size**: |
| - The measured size is the bounding box of the rendered text glyphs. |
| |
| #### Styling and Alignment |
| |
| - Supports ARGB colors (static or dynamic via ID). |
| - Supports font weights, styles (italic), and variable font axes. |
| - Horizontal alignment (`Left`, `Center`, `Right`, `Justify`) is applied during the text layout process. |
| |
| |
| |
| ## FlowLayout [EXPERIMENTAL] (added in v7) |
| !!! WARNING |
| Experimental operation |
| |
| Flow layout implementation. Positions components one after the other horizontally and wraps to the next line if space is exhausted. |
| |
| 5 Fields, total size 21 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>FlowLayout</td><td>Value: 240</td></tr> |
| <tr><td>INT</td><td>componentId</td><td>Unique ID for this component</td></tr><tr><td>INT</td><td>animationId</td><td>ID for animation purposes</td></tr><tr><td>INT</td><td>horizontalPositioning</td><td>Horizontal positioning value</td></tr><tr><td>INT</td><td>verticalPositioning</td><td>Vertical positioning value</td></tr><tr><td>FLOAT</td><td>spacedBy</td><td>Horizontal spacing between components</td></tr></table> |
| |
| ### horizontalPositioning |
| | Name | Value | |
| | ---- | ---- | |
| | START | 1 |
| | CENTER | 2 |
| | END | 3 |
| | SPACE_BETWEEN | 6 |
| | SPACE_EVENLY | 7 |
| | SPACE_AROUND | 8 |
| ### verticalPositioning |
| | Name | Value | |
| | ---- | ---- | |
| | TOP | 4 |
| | CENTER | 2 |
| | BOTTOM | 5 |
| ### Flow Layout Algorithm |
| |
| The Flow Layout manager positions child components horizontally and automatically wraps them to a new "row" when the available width is exhausted. |
| |
| #### Visual Illustration |
| <div id="flowLayoutContainer"> |
| <canvas id="flowLayoutCanvas_v1" width="500" height="300" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var widthPhase = 0; |
| function draw() { |
| var canvas = document.getElementById('flowLayoutCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 300); |
| |
| // Dynamic width for animation |
| var baseWidth = 460; |
| var minWidth = 150; |
| var currentFlowWidth = minWidth + (Math.sin(widthPhase) + 1) / 2 * (baseWidth - minWidth); |
| widthPhase += 0.02; |
| |
| var startX = 20, startY = 60; |
| var itemW = 60, itemH = 40, spacing = 10; |
| var items = 8; |
| |
| // Draw Container Boundary |
| ctx.strokeStyle = GRAY; |
| ctx.setLineDash([5, 5]); |
| ctx.strokeRect(startX, startY, currentFlowWidth, 200); |
| ctx.setLineDash([]); |
| ctx.font = 'bold 18px Arial'; |
| ctx.fillStyle = GRAY; |
| ctx.fillText('Available Width: ' + Math.round(currentFlowWidth) + 'px', startX, startY - 15); |
| |
| // Flow Logic |
| var tx = 0, ty = 0; |
| for (var i = 0; i < items; i++) { |
| if (tx + itemW > currentFlowWidth) { |
| tx = 0; |
| ty += itemH + spacing; |
| } |
| |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fillRect(startX + tx, startY + ty, itemW, itemH); |
| ctx.strokeStyle = BLUE; |
| ctx.lineWidth = 2; |
| ctx.strokeRect(startX + tx, startY + ty, itemW, itemH); |
| |
| ctx.fillStyle = BLUE; |
| ctx.font = 'bold 14px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText(i + 1, startX + tx + itemW/2, startY + ty + itemH/2 + 5); |
| |
| tx += itemW + spacing; |
| } |
| |
| requestAnimationFrame(draw); |
| } |
| draw(); |
| })(); |
| </script> |
| |
| #### Measurement Phase |
| |
| 1. **Segmentation**: |
| - The algorithm first iterates through children to determine which ones fit on the current line. |
| - If a child (including its minimum width if weighted) exceeds the remaining width in the current line, a new line is started. |
| - This results in a list of "rows," where each row is a collection of components. |
| |
| 2. **Wrap Sizing**: |
| - The total `width` is the maximum width of any individual row. |
| - The total `height` is the sum of the heights of all rows. |
| |
| #### Layout Phase |
| |
| 1. **Row Processing**: |
| - Each segment (row) identified in the measurement phase is treated like an individual Row Layout. |
| - Horizontal alignment (`START`, `CENTER`, `END`, etc.) is applied to components within each row. |
| - Vertical alignment (`TOP`, `CENTER`, `BOTTOM`) determines how the entire block of rows is positioned within the Flow Layout's total height. |
| |
| 2. **Spacing**: |
| - Horizontal spacing between components in a row is controlled by `spacedBy`. |
| |
| # Modifier Operations |
| Augmentations applied to components to change their dimensions, appearance, or behavior. |
| |
| Operations in this category: |
| | ID | Name | Version | Size (bytes) |
| | ---- | ---- | ---- | ---- | |
| | 16 | WidthModifierOperation | v6 | 9 |
| | 54 | RoundedClipRectModifierOperation | v6 | 17 |
| | 55 | BackgroundModifierOperation | v6 | 37 |
| | 58 | PaddingModifierOperation | v6 | 17 |
| | 59 | ClickModifier | v6 | 1 |
| | 67 | HeightModifierOperation | v6 | 9 |
| | 107 | BorderModifierOperation | v6 | 45 |
| | 108 | ClipRectModifierOperation | v6 | 1 |
| | 174 | DrawContentOperation | v6 | 1 |
| | 211 | ComponentVisibilityOperation | v6 | 5 |
| | 219 | TouchModifier | v6 | 1 |
| | 220 | TouchUpModifier | v6 | 1 |
| | 221 | OffsetModifierOperation | v6 | 9 |
| | 223 | ZIndexModifierOperation | v6 | 5 |
| | 224 | GraphicsLayerModifierOperation | v6 | 5 |
| | 225 | TouchCancelModifier | v6 | 1 |
| | 226 | ScrollModifierOperation | v6 | 17 |
| | 228 | MarqueeModifierOperation | v6 | 25 |
| | 229 | RippleModifier | v6 | 1 |
| | 231 | WidthInModifierOperation | v6 | 9 |
| | 232 | HeightInModifierOperation | v6 | 9 |
| | 235 | CollapsiblePriorityModifierOperation | v6 | 9 |
| | 237 | AlignByModifierOperation | v7 | 9 |
| | 238 | LayoutCompute | v7 | 9 |
| |
| ## WidthModifierOperation |
| Set the width dimension on a component |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>WidthModifierOperation</td><td>Value: 16</td></tr> |
| <tr><td>INT</td><td>type</td><td>The type of dimension rule (0=FIXED, 1=WRAP, etc.)</td></tr><tr><td>FLOAT</td><td>value</td><td>The width value</td></tr></table> |
| |
| |
| ## RoundedClipRectModifierOperation |
| Clip the component's content to its rounded rectangular bounds |
| |
| 4 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>RoundedClipRectModifierOperation</td><td>Value: 54</td></tr> |
| <tr><td>FLOAT</td><td>topStart</td><td>The topStart radius of the rectangle</td></tr><tr><td>FLOAT</td><td>topEnd</td><td>The topEnd radius of the rectangle</td></tr><tr><td>FLOAT</td><td>bottomStart</td><td>The bottomStart radius of the rectangle</td></tr><tr><td>FLOAT</td><td>bottomEnd</td><td>The bottomEnd radius of the rectangle</td></tr></table> |
| |
| ### RoundedClipRect Illustration |
| |
| The `RoundedClipRect` modifier restricts the drawing area of a component to its own bounds with rounded corners. |
| |
| <div id="roundedClipContainer"> |
| <canvas id="roundedClipCanvas_v1" width="500" height="300" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('roundedClipCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 500, 300); |
| |
| var x=100, y=50, w=300, h=200, r=40; |
| |
| // Background content (dimmed) |
| ctx.globalAlpha = 0.1; |
| ctx.fillStyle = BLUE; |
| ctx.fillRect(50, 20, 400, 260); |
| ctx.globalAlpha = 1.0; |
| |
| // Guide |
| ctx.strokeStyle = GRAY; |
| ctx.lineWidth = 2; |
| ctx.setLineDash([8, 8]); |
| ctx.strokeRect(x, y, w, h); |
| ctx.setLineDash([]); |
| |
| // Clip |
| ctx.save(); |
| ctx.beginPath(); |
| ctx.moveTo(x + r, y); |
| ctx.lineTo(x + w - r, y); |
| ctx.quadraticCurveTo(x + w, y, x + w, y + r); |
| ctx.lineTo(x + w, y + h - r); |
| ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); |
| ctx.lineTo(x + r, y + h); |
| ctx.quadraticCurveTo(x, y + h, x, y + h - r); |
| ctx.lineTo(x, y + r); |
| ctx.quadraticCurveTo(x, y, x + r, y); |
| ctx.clip(); |
| |
| ctx.fillStyle = BLUE; |
| ctx.fillRect(50, 20, 400, 260); |
| |
| ctx.fillStyle = 'white'; |
| ctx.font = 'bold 24px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText('Rounded Clip Area', x + w/2, y + h/2 + 10); |
| ctx.restore(); |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 18px Arial'; |
| ctx.fillText('Clip Bounds (left, top, right, bottom)', x, y + h + 30); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| ## BackgroundModifierOperation |
| Define a background color or shape for a component |
| |
| 9 Fields, total size 37 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>BackgroundModifierOperation</td><td>Value: 55</td></tr> |
| <tr><td>INT</td><td>flags</td><td>Behavior flags</td></tr><tr><td>INT</td><td>colorId</td><td>The ID of the color if flags include COLOR_REF</td></tr><tr><td>INT</td><td>reserve1</td><td>Reserved for future use</td></tr><tr><td>INT</td><td>reserve2</td><td>Reserved for future use</td></tr><tr><td>FLOAT</td><td>r</td><td>Red component [0..1]</td></tr><tr><td>FLOAT</td><td>g</td><td>Green component [0..1]</td></tr><tr><td>FLOAT</td><td>b</td><td>Blue component [0..1]</td></tr><tr><td>FLOAT</td><td>a</td><td>Alpha component [0..1]</td></tr><tr><td>INT</td><td>shapeType</td><td>The shape type (0=RECTANGLE, 1=CIRCLE)</td></tr></table> |
| |
| ### Background Modifier Illustration |
| |
| The `Background` modifier applies a color and shape behind a component. |
| |
| #### Rectangle Shape |
| <div id="bgRectContainer"> |
| <canvas id="bgRectCanvas_v1" width="240" height="240" style="border:1px solid #ccc; background: #fff; display: inline-block; margin: 10px;"></canvas> |
| </div> |
| |
| #### Circle Shape |
| <div id="bgCircleContainer"> |
| <canvas id="bgCircleCanvas_v1" width="240" height="240" style="border:1px solid #ccc; background: #fff; display: inline-block; margin: 10px;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function setup(id, type) { |
| function draw() { |
| var canvas = document.getElementById(id); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 240, 240); |
| ctx.fillStyle = BLUE; |
| |
| if (type === 'rect') { |
| ctx.fillRect(30, 30, 180, 180); |
| } else { |
| ctx.beginPath(); |
| ctx.arc(120, 120, 90, 0, 2 * Math.PI); |
| ctx.fill(); |
| } |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 22px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText(type.toUpperCase(), 120, 230); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| } |
| setup('bgRectCanvas_v1', 'rect'); |
| setup('bgCircleCanvas_v1', 'circle'); |
| })(); |
| </script> |
| |
| |
| |
| ## PaddingModifierOperation |
| Define padding around a component |
| |
| 4 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>PaddingModifierOperation</td><td>Value: 58</td></tr> |
| <tr><td>FLOAT</td><td>left</td><td>Left padding</td></tr><tr><td>FLOAT</td><td>top</td><td>Top padding</td></tr><tr><td>FLOAT</td><td>right</td><td>Right padding</td></tr><tr><td>FLOAT</td><td>bottom</td><td>Bottom padding</td></tr></table> |
| |
| ### Padding Modifier Illustration |
| |
| The `Padding` modifier adds space around a component's content. |
| |
| <div id="paddingModifierContainer"> |
| <canvas id="paddingModifierCanvas_v1" width="500" height="330" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('paddingModifierCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.1)'; |
| var L=50, T=65, R=80, B=35; |
| var W=500, H=330; |
| |
| ctx.clearRect(0, 0, W, H); |
| |
| // Component Bounds |
| ctx.strokeStyle = '#eee'; |
| ctx.lineWidth = 2; |
| ctx.setLineDash([4, 4]); |
| ctx.strokeRect(15, 15, W-30, H-30); |
| ctx.setLineDash([]); |
| |
| // Padding Area |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fillRect(15, 15, W-30, H-30); |
| |
| // Content Area |
| ctx.fillStyle = BLUE; |
| ctx.fillRect(15+L, 15+T, W-30-L-R, H-30-T-B); |
| |
| // Labels |
| ctx.font = 'bold 20px Arial'; |
| ctx.fillStyle = GRAY; |
| ctx.textAlign = 'center'; |
| ctx.fillText('Top: ' + T, W/2, 15+T/2 + 8); |
| ctx.fillText('Bottom: ' + B, W/2, H-15-B/2 + 8); |
| ctx.textAlign = 'left'; |
| ctx.fillText('Left: ' + L, 15+8, H/2); |
| ctx.textAlign = 'right'; |
| ctx.fillText('Right: ' + R, W-15-8, H/2); |
| |
| ctx.fillStyle = 'white'; |
| ctx.textAlign = 'center'; |
| ctx.fillText('Content', W/2, H/2); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## ClickModifier |
| Click modifier. This operation contains a list of action operations executed on click |
| |
| 0 Fields, total size 1 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ClickModifier</td><td>Value: 59</td></tr> |
| </table> |
| |
| |
| ## HeightModifierOperation |
| Set the height dimension on a component |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>HeightModifierOperation</td><td>Value: 67</td></tr> |
| <tr><td>INT</td><td>type</td><td>The type of dimension rule (0=FIXED, 1=WRAP, etc.)</td></tr><tr><td>FLOAT</td><td>value</td><td>The height value</td></tr></table> |
| |
| |
| ## BorderModifierOperation |
| Define a border for a component |
| |
| 11 Fields, total size 45 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>BorderModifierOperation</td><td>Value: 107</td></tr> |
| <tr><td>INT</td><td>flags</td><td>Behavior flags</td></tr><tr><td>INT</td><td>colorId</td><td>The ID of the color if flags include COLOR_REF</td></tr><tr><td>INT</td><td>reserve1</td><td>Reserved for future use</td></tr><tr><td>INT</td><td>reserve2</td><td>Reserved for future use</td></tr><tr><td>FLOAT</td><td>borderWidth</td><td>Width of the border</td></tr><tr><td>FLOAT</td><td>roundedCorner</td><td>Radius for rounded corners</td></tr><tr><td>FLOAT</td><td>r</td><td>Red component [0..1]</td></tr><tr><td>FLOAT</td><td>g</td><td>Green component [0..1]</td></tr><tr><td>FLOAT</td><td>b</td><td>Blue component [0..1]</td></tr><tr><td>FLOAT</td><td>a</td><td>Alpha component [0..1]</td></tr><tr><td>INT</td><td>shapeType</td><td>The shape type (0=RECTANGLE, 1=CIRCLE)</td></tr></table> |
| |
| ### Border Modifier Illustration |
| |
| The `Border` modifier draws a stroke around the component's edge with a specific width and corner radius. |
| |
| <div id="borderModifierContainer"> |
| <canvas id="borderModifierCanvas_v1" width="500" height="250" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('borderModifierCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444'; |
| var x=80, y=50, w=340, h=150, r=35, bw=8; |
| |
| ctx.clearRect(0, 0, 500, 250); |
| |
| ctx.beginPath(); |
| ctx.moveTo(x + r, y); |
| ctx.lineTo(x + w - r, y); |
| ctx.quadraticCurveTo(x + w, y, x + w, y + r); |
| ctx.lineTo(x + w, y + h - r); |
| ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); |
| ctx.lineTo(x + r, y + h); |
| ctx.quadraticCurveTo(x, y + h, x, y + h - r); |
| ctx.lineTo(x, y + r); |
| ctx.quadraticCurveTo(x, y, x + r, y); |
| ctx.closePath(); |
| |
| ctx.lineWidth = bw; |
| ctx.strokeStyle = BLUE; |
| ctx.stroke(); |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillText('borderWidth: ' + bw, x + w/2 - 75, y + h/2); |
| ctx.fillText('roundedCorner: ' + r, x + w - 50, y + 15); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## ClipRectModifierOperation |
| Clip the component's content to its rectangular bounds |
| |
| 0 Fields, total size 1 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ClipRectModifierOperation</td><td>Value: 108</td></tr> |
| </table> |
| |
| ### ClipRect Illustration |
| |
| The `ClipRect` modifier restricts the drawing area of a component to its own rectangular bounds. |
| |
| <div id="clipRectContainer"> |
| <canvas id="clipRectCanvas_v1" width="500" height="330" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('clipRectCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 330); |
| |
| var clipX=80, clipY=80, clipW=340, clipH=170; |
| |
| // Draw what's outside the clip (dimmed Blue) |
| ctx.globalAlpha = 0.2; |
| ctx.fillStyle = BLUE; |
| ctx.beginPath(); |
| ctx.arc(115, 115, 100, 0, 2 * Math.PI); |
| ctx.fill(); |
| ctx.globalAlpha = 1.0; |
| |
| // Clip Boundary |
| ctx.strokeStyle = GRAY; |
| ctx.lineWidth = 2; |
| ctx.setLineDash([8, 8]); |
| ctx.strokeRect(clipX, clipY, clipW, clipH); |
| ctx.setLineDash([]); |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText('Clip Bounds', clipX, clipY - 15); |
| |
| // Apply clip and draw content |
| ctx.save(); |
| ctx.beginPath(); |
| ctx.rect(clipX, clipY, clipW, clipH); |
| ctx.clip(); |
| |
| ctx.fillStyle = BLUE; |
| ctx.beginPath(); |
| ctx.arc(115, 115, 100, 0, 2 * Math.PI); |
| ctx.fill(); |
| |
| ctx.fillStyle = BLUE; |
| ctx.globalAlpha = 0.6; |
| ctx.fillRect(300, 130, 170, 170); |
| |
| ctx.restore(); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## DrawContentOperation |
| A modifier that triggers drawing of the component's content |
| |
| 0 Fields, total size 1 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>DrawContentOperation</td><td>Value: 174</td></tr> |
| </table> |
| |
| |
| ## ComponentVisibilityOperation |
| Set component visibility from a provided integer variable |
| |
| 1 Fields, total size 5 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ComponentVisibilityOperation</td><td>Value: 211</td></tr> |
| <tr><td>INT</td><td>visibilityId</td><td>The ID of the integer variable representing visibility</td></tr></table> |
| |
| ### ComponentVisibility Illustration |
| |
| The `ComponentVisibility` modifier controls whether a component is visible, hidden but still taking up space, or completely removed from the layout. |
| |
| <div id="visibilityModifierContainer"> |
| <canvas id="visibilityModifierCanvas_v1" width="500" height="250" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('visibilityModifierCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#BBBBBB', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 250); |
| |
| function drawState(x, y, label, sublabel, vis) { |
| ctx.strokeStyle = GRAY; |
| ctx.setLineDash([5, 5]); |
| ctx.strokeRect(x, y, 120, 100); |
| ctx.setLineDash([]); |
| |
| if (vis === 'VISIBLE') { |
| ctx.fillStyle = BLUE; |
| ctx.fillRect(x + 10, y + 10, 100, 80); |
| } else if (vis === 'INVISIBLE') { |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fillRect(x + 10, y + 10, 100, 80); |
| ctx.setLineDash([2, 2]); |
| ctx.strokeStyle = BLUE; |
| ctx.strokeRect(x + 10, y + 10, 100, 80); |
| ctx.setLineDash([]); |
| } |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 18px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText(label, x + 60, y + 130); |
| ctx.font = '14px Arial'; |
| ctx.fillStyle = GRAY; |
| ctx.fillText(sublabel, x + 60, y + 150); |
| } |
| |
| drawState(50, 40, 'VISIBLE', 'Drawn & Sized', 'VISIBLE'); |
| drawState(190, 40, 'INVISIBLE', 'Sized only', 'INVISIBLE'); |
| drawState(330, 40, 'GONE', 'Not Sized', 'GONE'); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## TouchModifier |
| Touch down modifier. This operation contains a list of action operations executed on touch down |
| |
| 0 Fields, total size 1 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>TouchModifier</td><td>Value: 219</td></tr> |
| </table> |
| |
| |
| ## TouchUpModifier |
| Touch up modifier. This operation contains a list of action operations executed on touch up |
| |
| 0 Fields, total size 1 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>TouchUpModifier</td><td>Value: 220</td></tr> |
| </table> |
| |
| |
| ## OffsetModifierOperation |
| Shift the component's position |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>OffsetModifierOperation</td><td>Value: 221</td></tr> |
| <tr><td>FLOAT</td><td>x</td><td>X offset</td></tr><tr><td>FLOAT</td><td>y</td><td>Y offset</td></tr></table> |
| |
| ### Offset Modifier Illustration |
| |
| The `Offset` modifier shifts the position of a component by a specific amount in X and Y without affecting its layout in the parent. |
| |
| <div id="offsetModifierContainer"> |
| <canvas id="offsetModifierCanvas_v1" width="500" height="250" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('offsetModifierCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| var ox=100, oy=50; |
| |
| ctx.clearRect(0, 0, 500, 250); |
| |
| // Original (dashed) |
| ctx.setLineDash([8, 8]); |
| ctx.strokeStyle = '#bbb'; |
| ctx.lineWidth = 2; |
| ctx.strokeRect(65, 65, 135, 100); |
| ctx.setLineDash([]); |
| ctx.font = 'bold 18px Arial'; |
| ctx.fillStyle = '#888'; |
| ctx.fillText('Original', 65, 55); |
| |
| // Offset |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fillRect(65 + ox, 65 + oy, 135, 100); |
| ctx.strokeStyle = BLUE; |
| ctx.lineWidth = 3; |
| ctx.strokeRect(65 + ox, 65 + oy, 135, 100); |
| |
| // Arrow |
| ctx.beginPath(); |
| ctx.moveTo(130, 115); |
| ctx.lineTo(130 + ox, 115 + oy); |
| ctx.strokeStyle = GRAY; |
| ctx.lineWidth = 2; |
| ctx.stroke(); |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillText('x: ' + ox + ', y: ' + oy, 130 + ox/2 - 40, 115 + oy/2 - 10); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## ZIndexModifierOperation |
| Define the Z-Index of a component |
| |
| 1 Fields, total size 5 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ZIndexModifierOperation</td><td>Value: 223</td></tr> |
| <tr><td>FLOAT</td><td>value</td><td>The Z-Index value</td></tr></table> |
| |
| ### ZIndex Illustration |
| |
| The `ZIndex` modifier determines the stacking order of overlapping components. Components with a higher Z-Index are drawn on top of those with a lower value. |
| |
| <div id="zIndexContainer"> |
| <canvas id="zIndexCanvas_v1" width="500" height="300" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('zIndexCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#BBBBBB', DARK_GRAY = '#444444', BLUE_LIGHT = 'rgba(0, 71, 171, 0.6)'; |
| ctx.clearRect(0, 0, 500, 300); |
| |
| function drawBox(x, y, label, color, z) { |
| ctx.fillStyle = color; |
| ctx.fillRect(x, y, 150, 100); |
| ctx.strokeStyle = '#fff'; |
| ctx.lineWidth = 2; |
| ctx.strokeRect(x, y, 150, 100); |
| |
| ctx.fillStyle = 'white'; |
| ctx.font = 'bold 20px Arial'; |
| ctx.fillText(label, x + 20, y + 40); |
| ctx.font = '16px Arial'; |
| ctx.fillText('Z-Index: ' + z, x + 20, y + 70); |
| } |
| |
| // Stacked boxes illustration |
| drawBox(100, 100, 'Box A', '#888', 1); |
| drawBox(150, 130, 'Box B', BLUE_LIGHT, 2); |
| drawBox(200, 160, 'Box C', BLUE, 3); |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillText('Higher Z drawn last (on top)', 120, 50); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## GraphicsLayerModifierOperation |
| Define transformations (scale, rotation, alpha) for a component |
| |
| 3 Fields, total size 5 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>GraphicsLayerModifierOperation</td><td>Value: 224</td></tr> |
| <tr><td>INT</td><td>length</td><td>Number of attributes</td></tr><tr><td>REPEATED DATA</td><td colspan="2"><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>INT</td><td>attributeId</td><td>The ID and type of the attribute</td></tr><tr><td>FLOAT</td><td>attributeValue</td><td>The value of the attribute</td></tr></table></td></tr><tr><td>REPEATED DATA</td><td colspan="2"><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>INT</td><td>attributeId</td><td>The ID and type of the attribute</td></tr><tr><td>INT</td><td>attributeValue</td><td>The value of the attribute</td></tr></table></td></tr></table> |
| |
| ### GraphicsLayer Illustration |
| |
| The `GraphicsLayer` modifier applies advanced transformations and effects like rotation, scaling, and transparency to a component. |
| |
| <div id="graphicsLayerContainer"> |
| <canvas id="graphicsLayerCanvas_v1" width="500" height="330" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('graphicsLayerCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 500, 330); |
| |
| var rotation = 15; |
| var scale = 1.2; |
| var alpha = 0.7; |
| |
| ctx.save(); |
| ctx.translate(250, 165); |
| ctx.rotate(rotation * Math.PI / 180); |
| ctx.scale(scale, scale); |
| ctx.globalAlpha = alpha; |
| |
| ctx.fillStyle = BLUE; |
| ctx.fillRect(-85, -85, 170, 170); |
| ctx.strokeStyle = BLUE; |
| ctx.lineWidth = 3; |
| ctx.strokeRect(-85, -85, 170, 170); |
| ctx.restore(); |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillText('rotationZ: ' + rotation + '°', 15, 35); |
| ctx.fillText('scaleX/Y: ' + scale, 15, 70); |
| ctx.fillText('alpha: ' + alpha, 15, 105); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## TouchCancelModifier |
| Touch cancel modifier. This operation contains a list of action operations executed on touch cancel |
| |
| 0 Fields, total size 1 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>TouchCancelModifier</td><td>Value: 225</td></tr> |
| </table> |
| |
| |
| ## ScrollModifierOperation |
| Define a scrolling behavior for a component |
| |
| 4 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ScrollModifierOperation</td><td>Value: 226</td></tr> |
| <tr><td>INT</td><td>direction</td><td>Direction of the scroll (0=VERTICAL, 1=HORIZONTAL)</td></tr><tr><td>FLOAT</td><td>position</td><td>The current scroll position (expression)</td></tr><tr><td>FLOAT</td><td>max</td><td>The maximum scroll position</td></tr><tr><td>FLOAT</td><td>notchMax</td><td>The maximum notch position</td></tr></table> |
| |
| ### Scroll Modifier Illustration |
| |
| The `Scroll` modifier allows a component to have a larger internal content area than its physical viewport bounds. |
| |
| <div id="scrollModifierContainer"> |
| <canvas id="scrollModifierCanvas_v1" width="500" height="300" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var phase = 0; |
| function draw() { |
| var canvas = document.getElementById('scrollModifierCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.1)'; |
| ctx.clearRect(0, 0, 500, 300); |
| |
| var viewportX=150, viewportY=80, viewportW=200, viewportH=180; |
| var contentW=200, contentH=500; |
| |
| // Animate scroll position |
| var maxScroll = contentH - viewportH; |
| var scrollY = (Math.sin(phase) + 1) / 2 * maxScroll; |
| phase += 0.02; |
| |
| // Content Area (Full) - Drawn behind/around |
| ctx.strokeStyle = GRAY; |
| ctx.setLineDash([5, 5]); |
| ctx.strokeRect(viewportX, viewportY - scrollY, contentW, contentH); |
| ctx.setLineDash([]); |
| ctx.font = '16px Arial'; |
| ctx.fillStyle = GRAY; |
| ctx.textAlign = 'left'; |
| ctx.fillText('Content Area (500px)', viewportX + 10, viewportY - scrollY + 25); |
| |
| // Some content items |
| for(var i=0; i<7; i++) { |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fillRect(viewportX + 20, viewportY - scrollY + 50 + i*70, contentW - 40, 50); |
| } |
| |
| // Viewport (Clip bounds) |
| ctx.lineWidth = 4; |
| ctx.strokeStyle = DARK_GRAY; |
| ctx.strokeRect(viewportX, viewportY, viewportW, viewportH); |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillText('Viewport (180px)', viewportX, viewportY - 15); |
| |
| // Scroll Indicator |
| ctx.fillStyle = BLUE; |
| var thumbH = (viewportH / contentH) * viewportH; |
| var thumbY = viewportY + (scrollY / contentH) * viewportH; |
| ctx.fillRect(viewportX + viewportW + 5, thumbY, 6, thumbH); |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 16px Arial'; |
| ctx.fillText('scroll position', viewportX + viewportW + 15, thumbY + thumbH/2 + 5); |
| |
| requestAnimationFrame(draw); |
| } |
| draw(); |
| })(); |
| </script> |
| |
| |
| |
| ## MarqueeModifierOperation |
| Define a scrolling marquee effect for a component |
| |
| 6 Fields, total size 25 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>MarqueeModifierOperation</td><td>Value: 228</td></tr> |
| <tr><td>INT</td><td>iterations</td><td>Number of iterations</td></tr><tr><td>INT</td><td>animationMode</td><td>Animation mode</td></tr><tr><td>FLOAT</td><td>repeatDelayMillis</td><td>Repeat delay in ms</td></tr><tr><td>FLOAT</td><td>initialDelayMillis</td><td>Initial delay in ms</td></tr><tr><td>FLOAT</td><td>spacing</td><td>Spacing between marquee iterations</td></tr><tr><td>FLOAT</td><td>velocity</td><td>Velocity of the marquee animation</td></tr></table> |
| |
| ### Marquee Illustration |
| |
| The `Marquee` modifier creates a scrolling animation for text that is too long to fit within its container. |
| |
| <div id="marqueeModifierContainer"> |
| <canvas id="marqueeModifierCanvas_v1" width="500" height="150" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var offset = 0; |
| function draw() { |
| var canvas = document.getElementById('marqueeModifierCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.1)'; |
| ctx.clearRect(0, 0, 500, 150); |
| |
| var vx=100, vy=50, vw=300, vh=50; |
| var text = "This is a long marquee text that scrolls continuously..."; |
| |
| ctx.font = '24px Arial'; |
| var tw = ctx.measureText(text).width; |
| |
| // Viewport |
| ctx.strokeStyle = DARK_GRAY; |
| ctx.lineWidth = 2; |
| ctx.strokeRect(vx, vy, vw, vh); |
| |
| // Scrolling Content |
| ctx.save(); |
| ctx.beginPath(); |
| ctx.rect(vx, vy, vw, vh); |
| ctx.clip(); |
| |
| ctx.fillStyle = BLUE; |
| ctx.fillText(text, vx + 20 - offset, vy + 35); |
| // Draw repeat if needed |
| if (offset > 20) { |
| ctx.fillText(text, vx + 20 - offset + tw + 50, vy + 35); |
| } |
| ctx.restore(); |
| |
| // Labels |
| ctx.fillStyle = GRAY; |
| ctx.font = 'bold 18px Arial'; |
| ctx.fillText('Viewport (Static)', vx, vy - 15); |
| ctx.fillText('Velocity Direction', 180, vy + vh + 35); |
| |
| // Arrow |
| ctx.beginPath(); |
| ctx.moveTo(150, 130); |
| ctx.lineTo(100, 130); |
| ctx.strokeStyle = BLUE; |
| ctx.stroke(); |
| |
| offset = (offset + 1) % (tw + 50); |
| requestAnimationFrame(draw); |
| } |
| draw(); |
| })(); |
| </script> |
| |
| |
| |
| ## RippleModifier |
| Ripple modifier. This modifier will do a ripple animation on touch down |
| |
| 0 Fields, total size 1 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>RippleModifier</td><td>Value: 229</td></tr> |
| </table> |
| |
| |
| ## WidthInModifierOperation |
| Add additional constraints to the width |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>WidthInModifierOperation</td><td>Value: 231</td></tr> |
| <tr><td>FLOAT</td><td>min</td><td>The minimum width, -1 if not applied</td></tr><tr><td>FLOAT</td><td>max</td><td>The maximum width, -1 if not applied</td></tr></table> |
| |
| |
| ## HeightInModifierOperation |
| Add additional constraints to the height |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>HeightInModifierOperation</td><td>Value: 232</td></tr> |
| <tr><td>FLOAT</td><td>min</td><td>The minimum height, -1 if not applied</td></tr><tr><td>FLOAT</td><td>max</td><td>The maximum height, -1 if not applied</td></tr></table> |
| |
| |
| ## CollapsiblePriorityModifierOperation |
| Add additional priority to children of collapsible layouts |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>CollapsiblePriorityModifierOperation</td><td>Value: 235</td></tr> |
| <tr><td>INT</td><td>orientation</td><td>Horizontal(0) or Vertical (1)</td></tr><tr><td>FLOAT</td><td>priority</td><td>The associated priority</td></tr></table> |
| |
| |
| ## AlignByModifierOperation [EXPERIMENTAL] (added in v7) |
| !!! WARNING |
| Experimental operation |
| |
| Align a component based on a specific baseline or anchor |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>AlignByModifierOperation</td><td>Value: 237</td></tr> |
| <tr><td>FLOAT</td><td>line</td><td>The ID of the float variable or baseline ID to align by</td></tr><tr><td>INT</td><td>flags</td><td>Alignment flags</td></tr></table> |
| |
| |
| ## LayoutCompute [EXPERIMENTAL] (added in v7) |
| !!! WARNING |
| Experimental operation |
| |
| Compute component position and measure via dynamic expressions |
| |
| 3 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>LayoutCompute</td><td>Value: 238</td></tr> |
| <tr><td>INT</td><td>type</td><td>Type of computation (0=MEASURE, 1=POSITION)</td></tr><tr><td>INT</td><td>boundsId</td><td>The ID of the float list variable to store the bounds</td></tr><tr><td>BOOLEAN</td><td>animateChanges</td><td>Whether to animate layout changes</td></tr></table> |
| |
| # Actions & Events Operations |
| Interactive side effects, including host-side actions and dynamic state updates. |
| |
| Operations in this category: |
| | ID | Name | Version | Size (bytes) |
| | ---- | ---- | ---- | ---- | |
| | 212 | ValueIntegerChangeActionOperation | v6 | 9 |
| | 213 | ValueStringChangeActionOperation | v6 | 9 |
| | 218 | ValueIntegerExpressionChangeActionOperation | v6 | 17 |
| | 222 | ValueFloatChangeActionOperation | v6 | 9 |
| | 227 | ValueFloatExpressionChangeActionOperation | v6 | 9 |
| |
| ## ValueIntegerChangeActionOperation |
| Action that sets a new value for an integer variable |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ValueIntegerChangeActionOperation</td><td>Value: 212</td></tr> |
| <tr><td>INT</td><td>targetValueId</td><td>The ID of the integer variable to update</td></tr><tr><td>INT</td><td>value</td><td>The new integer value to assign</td></tr></table> |
| |
| |
| ## ValueStringChangeActionOperation |
| Action that sets a new value for a string variable |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ValueStringChangeActionOperation</td><td>Value: 213</td></tr> |
| <tr><td>INT</td><td>targetValueId</td><td>The ID of the string variable to update</td></tr><tr><td>INT</td><td>valueId</td><td>The ID of the new string value to assign</td></tr></table> |
| |
| |
| ## ValueIntegerExpressionChangeActionOperation |
| Action that updates an integer variable via a dynamic expression |
| |
| 2 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ValueIntegerExpressionChangeActionOperation</td><td>Value: 218</td></tr> |
| <tr><td>LONG</td><td>targetValueId</td><td>The ID of the integer variable to update</td></tr><tr><td>LONG</td><td>valueExpressionId</td><td>The ID of the expression to evaluate</td></tr></table> |
| |
| |
| ## ValueFloatChangeActionOperation |
| Action that sets a new value for a float variable |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ValueFloatChangeActionOperation</td><td>Value: 222</td></tr> |
| <tr><td>INT</td><td>targetValueId</td><td>The ID of the float variable to update</td></tr><tr><td>FLOAT</td><td>value</td><td>The new float value to assign</td></tr></table> |
| |
| |
| ## ValueFloatExpressionChangeActionOperation |
| Action that updates a float variable via a dynamic expression |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ValueFloatExpressionChangeActionOperation</td><td>Value: 227</td></tr> |
| <tr><td>INT</td><td>targetValueId</td><td>The ID of the float variable to update</td></tr><tr><td>INT</td><td>valueExpressionId</td><td>The ID of the expression to evaluate</td></tr></table> |
| |
| # Animation & Particles Operations |
| Specialized systems for time-based transitions, impulses, and complex particle effects. |
| |
| Operations in this category: |
| | ID | Name | Version | Size (bytes) |
| | ---- | ---- | ---- | ---- | |
| | 14 | AnimationSpec | v6 | 29 |
| | 161 | ParticlesCreate | v6 | 21 + null x 4 |
| | 163 | ParticlesLoop | v6 | 17 + null x 4 + null x 4 |
| | 164 | ImpulseOperation | v6 | 9 |
| | 165 | ImpulseProcess | v6 | 1 |
| | 194 | ParticlesCompare | v7 | 27 + null x 4 + null x 4 + null x 4 |
| |
| ## AnimationSpec |
| Define the animation specifications for a component |
| |
| 7 Fields, total size 29 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>AnimationSpec</td><td>Value: 14</td></tr> |
| <tr><td>INT</td><td>animationId</td><td>The ID of the animation</td></tr><tr><td>FLOAT</td><td>motionDuration</td><td>Duration of the motion animation in ms</td></tr><tr><td>INT</td><td>motionEasingType</td><td>The type of easing for motion</td></tr><tr><td>FLOAT</td><td>visibilityDuration</td><td>Duration of visibility animation in ms</td></tr><tr><td>INT</td><td>visibilityEasingType</td><td>The type of easing for visibility</td></tr><tr><td>INT</td><td>enterAnimation</td><td>The entry animation type</td></tr><tr><td>INT</td><td>exitAnimation</td><td>The exit animation type</td></tr></table> |
| |
| ### AnimationSpec Illustration |
| |
| The `AnimationSpec` defines easing curves and durations for motion and visibility transitions. |
| |
| #### Easing Curves (Cubic Bezier) |
| <div id="animationSpecContainer"> |
| <canvas id="animationSpecCanvas_v1" width="500" height="250" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('animationSpecCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 500, 250); |
| |
| function drawCurve(x, y, w, h, cp1x, cp1y, cp2x, cp2y, label) { |
| ctx.strokeStyle = '#eee'; |
| ctx.lineWidth = 2; |
| ctx.strokeRect(x, y, w, h); |
| |
| ctx.beginPath(); |
| ctx.moveTo(x, y + h); |
| ctx.bezierCurveTo(x + cp1x*w, y + h - cp1y*h, x + cp2x*w, y + h - cp2y*h, x + w, y); |
| ctx.lineWidth = 5; |
| ctx.strokeStyle = BLUE; |
| ctx.stroke(); |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 18px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText(label, x + w/2, y + h + 30); |
| } |
| |
| drawCurve(35, 35, 100, 100, 0.4, 0.0, 0.2, 1.0, "Standard"); |
| drawCurve(185, 35, 100, 100, 0.4, 0.0, 1.0, 1.0, "Accelerate"); |
| drawCurve(335, 35, 100, 100, 0.0, 0.0, 0.2, 1.0, "Decelerate"); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## ParticlesCreate |
| Create a particle system |
| |
| 6 Fields, total size 21 + null x 4 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ParticlesCreate</td><td>Value: 161</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the particle system</td></tr><tr><td>INT</td><td>particleCount</td><td>Number of particles to create</td></tr><tr><td>INT</td><td>varCount</td><td>Number of variables associated with each particle</td></tr><tr><td>INT</td><td>varId[0..n]</td><td>The ID of each associated variable</td></tr><tr><td>INT</td><td>equLen[0..n]</td><td>The length of the initialization equation for each variable</td></tr><tr><td>FLOAT[]</td><td>equations[0..n]</td><td>The initialization equations (RPN)</td></tr></table> |
| |
| ### Particles Illustration |
| |
| The `ParticlesCreate` operation defines a particle emitter that generates short-lived visual elements with dynamic velocity, spread, and life. |
| |
| <div id="particlesContainer"> |
| <canvas id="particlesCanvas_v1" width="500" height="300" style="border:1px solid #ccc; background: #000; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var particles = []; |
| function draw() { |
| var canvas = document.getElementById('particlesCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| ctx.clearRect(0, 0, 500, 300); |
| |
| var ex = 250, ey = 150; |
| |
| if (particles.length < 100) { |
| particles.push({ |
| x: ex, y: ey, |
| vx: (Math.random() - 0.5) * 4, |
| vy: (Math.random() - 0.5) * 4, |
| life: 1.0, |
| size: 2 + Math.random() * 4 |
| }); |
| } |
| |
| for (var i = particles.length - 1; i >= 0; i--) { |
| var p = particles[i]; |
| p.x += p.vx; |
| p.y += p.vy; |
| p.life -= 0.01; |
| |
| if (p.life <= 0) { |
| particles.splice(i, 1); |
| continue; |
| } |
| |
| ctx.fillStyle = 'rgba(0, 71, 171, ' + p.life + ')'; |
| ctx.beginPath(); |
| ctx.arc(p.x, p.y, p.size, 0, Math.PI*2); |
| ctx.fill(); |
| } |
| |
| ctx.fillStyle = '#888'; |
| ctx.font = 'bold 20px Arial'; |
| ctx.fillText('Dynamic Particle Emitter', 140, 40); |
| |
| requestAnimationFrame(draw); |
| } |
| draw(); |
| })(); |
| </script> |
| |
| |
| ## ParticlesLoop |
| Update and recycle particles in a system |
| |
| 6 Fields, total size 17 + null x 4 + null x 4 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ParticlesLoop</td><td>Value: 163</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the particle system</td></tr><tr><td>INT</td><td>restartLen</td><td>The length of the restart equation (recycles particle if > 0)</td></tr><tr><td>FLOAT[]</td><td>restartEquation</td><td>The restart equation (RPN)</td></tr><tr><td>INT</td><td>varCount</td><td>The number of update equations</td></tr><tr><td>INT</td><td>equLen[0..n]</td><td>The length of each update equation</td></tr><tr><td>FLOAT[]</td><td>equations[0..n]</td><td>The update equations (RPN)</td></tr></table> |
| |
| |
| ## ImpulseOperation |
| Execute a list of actions once, and a process block for a fixed duration |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ImpulseOperation</td><td>Value: 164</td></tr> |
| <tr><td>FLOAT</td><td>duration</td><td>Duration of the impulse</td></tr><tr><td>FLOAT</td><td>startAt</td><td>The start time of the impulse</td></tr></table> |
| |
| |
| ## ImpulseProcess |
| A block of operations executed during an active impulse |
| |
| 0 Fields, total size 1 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ImpulseProcess</td><td>Value: 165</td></tr> |
| </table> |
| |
| |
| ## ParticlesCompare (added in v7) |
| Compare particles and execute conditional logic |
| |
| 10 Fields, total size 27 + null x 4 + null x 4 + null x 4 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ParticlesCompare</td><td>Value: 194</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the particle system</td></tr><tr><td>SHORT</td><td>flags</td><td>Configuration flags</td></tr><tr><td>FLOAT</td><td>min</td><td>The minimum index to process</td></tr><tr><td>FLOAT</td><td>max</td><td>The maximum index to process</td></tr><tr><td>INT</td><td>expLen</td><td>The length of the comparison expression</td></tr><tr><td>FLOAT[]</td><td>expression</td><td>The comparison expression (RPN)</td></tr><tr><td>INT</td><td>res1Count</td><td>The number of equations in the first result block</td></tr><tr><td>FLOAT[]</td><td>res1Equations</td><td>The equations for the first result block</td></tr><tr><td>INT</td><td>res2Count</td><td>The number of equations in the second result block</td></tr><tr><td>FLOAT[]</td><td>res2Equations</td><td>The equations for the second result block</td></tr></table> |
| |
| # Logic & Expressions Operations |
| Dynamic calculations, conditional execution, loops, and measurement utilities. |
| |
| Operations in this category: |
| | ID | Name | Version | Size (bytes) |
| | ---- | ---- | ---- | ---- | |
| | 81 | FloatExpression | v6 | 9 |
| | 144 | IntegerExpression | v6 | 13 + null x 4 |
| | 150 | ComponentValue | v6 | 13 |
| | 157 | TouchExpression | v6 | 37 |
| | 171 | ImageAttribute | v6 | 13 |
| | 172 | TimeAttribute | v6 | 13 |
| | 178 | ConditionalOperations | v6 | 10 |
| | 192 | IdLookup | v7 | 13 |
| | 215 | Loop | v6 | 17 |
| |
| ## FloatExpression |
| Define a float via dynamic expression and optional animation |
| |
| 6 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>FloatExpression</td><td>Value: 81</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the resulting float</td></tr><tr><td>SHORT</td><td>expression_length</td><td>The length of the expression</td></tr><tr><td>SHORT</td><td>animation_length</td><td>The length of the animation spec</td></tr><tr><td>REPEATED FLOAT</td><td>expression</td><td>Sequence of floats representing an expression (RPN)</td></tr><tr><td>REPEATED FLOAT</td><td>animationSpec</td><td>Sequence of floats representing an animation curve</td></tr><tr><td></td><td colspan="2"><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>FLOAT</td><td>duration</td><td>Time in sec</td></tr><tr><td>INT</td><td>bits</td><td>WRAP | INITIAL VALUE | TYPE </td></tr><tr><td>REPEATED FLOAT</td><td>spec</td><td>SPEC PARAMETERS</td></tr><tr><td>FLOAT</td><td>initialValue</td><td>Initial value</td></tr><tr><td>FLOAT</td><td>wrapValue</td><td>Wrap value</td></tr></table></td></tr></table> |
| |
| |
| ## IntegerExpression |
| Define an integer via dynamic expression |
| |
| 4 Fields, total size 13 + null x 4 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>IntegerExpression</td><td>Value: 144</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the resulting integer</td></tr><tr><td>INT</td><td>mask</td><td>Bitmask representing whether each value is a constant or an ID</td></tr><tr><td>INT</td><td>length</td><td>The number of elements in the expression</td></tr><tr><td>INT[]</td><td>values</td><td>The array of constants, IDs, and operators (RPN)</td></tr></table> |
| |
| |
| ## ComponentValue |
| Expose a component's layout property (width, height, etc.) as a variable |
| |
| 3 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ComponentValue</td><td>Value: 150</td></tr> |
| <tr><td>INT</td><td>type</td><td>The type of value to expose</td></tr><tr><td>INT</td><td>componentId</td><td>The ID of the component to reference</td></tr><tr><td>INT</td><td>valueId</td><td>The ID of the variable to store the value in</td></tr></table> |
| |
| ### type |
| | Name | Value | |
| | ---- | ---- | |
| | WIDTH | 0 |
| | HEIGHT | 1 |
| | POS_X | 2 |
| | POS_Y | 3 |
| | POS_ROOT_X | 4 |
| | POS_ROOT_Y | 5 |
| | CONTENT_WIDTH | 6 |
| | CONTENT_HEIGHT | 7 |
| |
| ## TouchExpression |
| Define a float value derived from touch interactions |
| |
| 12 Fields, total size 37 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>TouchExpression</td><td>Value: 157</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the resulting float variable</td></tr><tr><td>FLOAT</td><td>value</td><td>The initial value</td></tr><tr><td>FLOAT</td><td>min</td><td>The minimum allowed value</td></tr><tr><td>FLOAT</td><td>max</td><td>The maximum allowed value</td></tr><tr><td>FLOAT</td><td>velocityId</td><td>Reserved for velocity ID</td></tr><tr><td>INT</td><td>touchEffects</td><td>Haptic feedback and touch behavior flags</td></tr><tr><td>INT</td><td>expression_length</td><td>The length of the touch mapping expression</td></tr><tr><td>REPEATED FLOAT</td><td>expression</td><td>Sequence of floats representing touch mapping (RPN)</td></tr><tr><td>INT</td><td>stopModeAndLen</td><td>Encoded stop mode and length of stop spec</td></tr><tr><td>REPEATED FLOAT</td><td>stopSpec</td><td>Parameters for stop behavior (e.g., notches)</td></tr><tr><td>INT</td><td>easingLen</td><td>The length of the easing spec</td></tr><tr><td>REPEATED FLOAT</td><td>easingSpec</td><td>Parameters for deceleration easing</td></tr></table> |
| |
| |
| ## ImageAttribute |
| Extract image-related properties (width, height) |
| |
| 5 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ImageAttribute</td><td>Value: 171</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the float variable to store the result</td></tr><tr><td>INT</td><td>imageId</td><td>The ID of the image variable to extract from</td></tr><tr><td>SHORT</td><td>type</td><td>The type of property to extract (0=WIDTH, 1=HEIGHT)</td></tr><tr><td>SHORT</td><td>argsLength</td><td>The number of additional arguments</td></tr><tr><td>REPEATED INT</td><td>args</td><td>The additional arguments</td></tr></table> |
| |
| |
| ## TimeAttribute |
| Extract time-related information (seconds, hours, etc.) |
| |
| 5 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>TimeAttribute</td><td>Value: 172</td></tr> |
| <tr><td>INT</td><td>id</td><td>The ID of the float variable to store the result</td></tr><tr><td>INT</td><td>timeId</td><td>The ID of the time variable to extract from</td></tr><tr><td>SHORT</td><td>type</td><td>The type of time information to extract</td></tr><tr><td>SHORT</td><td>argsLength</td><td>The number of additional arguments</td></tr><tr><td>REPEATED INT</td><td>args</td><td>The additional arguments</td></tr></table> |
| |
| |
| ## ConditionalOperations |
| Execute a list of operations if a condition is met |
| |
| 3 Fields, total size 10 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>ConditionalOperations</td><td>Value: 178</td></tr> |
| <tr><td>BYTE</td><td>type</td><td>The type of comparison (EQ, NEQ, LT, etc.)</td></tr><tr><td>FLOAT</td><td>varA</td><td>The first value to compare</td></tr><tr><td>FLOAT</td><td>varB</td><td>The second value to compare</td></tr></table> |
| |
| ### type |
| | Name | Value | |
| | ---- | ---- | |
| | TYPE_EQ | 0 |
| | TYPE_NEQ | 1 |
| | TYPE_LT | 2 |
| | TYPE_LTE | 3 |
| | TYPE_GT | 4 |
| | TYPE_GTE | 5 |
| |
| ## IdLookup (added in v7) |
| Look up an ID from an ID collection via index |
| |
| 3 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>IdLookup</td><td>Value: 192</td></tr> |
| <tr><td>INT</td><td>textId</td><td>The ID of the integer variable to store the result</td></tr><tr><td>FLOAT</td><td>dataSet</td><td>The ID of the collection</td></tr><tr><td>FLOAT</td><td>index</td><td>The index of the ID to retrieve</td></tr></table> |
| |
| |
| ## Loop |
| Execute a list of operations in a loop |
| |
| 4 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>Loop</td><td>Value: 215</td></tr> |
| <tr><td>INT</td><td>indexId</td><td>The ID of the variable to store the loop index</td></tr><tr><td>FLOAT</td><td>from</td><td>Starting value</td></tr><tr><td>FLOAT</td><td>step</td><td>Increment value</td></tr><tr><td>FLOAT</td><td>until</td><td>Stop value (exclusive)</td></tr></table> |
| |
| # Matrix Operations |
| Coordinate system transformations, including translation, scaling, rotation, skewing, and matrix math. |
| |
| Operations in this category: |
| | ID | Name | Version | Size (bytes) |
| | ---- | ---- | ---- | ---- | |
| | 126 | MatrixScale | v6 | 17 |
| | 127 | MatrixTranslate | v6 | 9 |
| | 128 | MatrixSkew | v6 | 9 |
| | 129 | MatrixRotate | v6 | 13 |
| | 130 | MatrixSave | v6 | 1 |
| | 131 | MatrixRestore | v6 | 1 |
| | 181 | MatrixFromPath | v7 | 17 |
| | 186 | MatrixConstant | v7 | 9 + null x 4 |
| | 187 | MatrixExpression | v7 | 9 + null x 4 |
| | 188 | MatrixVectorMath | v7 | 15 + null x 4 + null x 4 |
| |
| ## MatrixScale |
| Scale the following draw commands |
| |
| 4 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>MatrixScale</td><td>Value: 126</td></tr> |
| <tr><td>FLOAT</td><td>scaleX</td><td>The amount to scale in X</td></tr><tr><td>FLOAT</td><td>scaleY</td><td>The amount to scale in Y</td></tr><tr><td>FLOAT</td><td>pivotX</td><td>The x-coordinate for the pivot point</td></tr><tr><td>FLOAT</td><td>pivotY</td><td>The y-coordinate for the pivot point</td></tr></table> |
| |
| ### MatrixScale Illustration |
| |
| The `MatrixScale` operation scales the coordinate system by `scaleX` and `scaleY` relative to a pivot point `(pivotX, pivotY)`. |
| |
| <div id="matrixScaleContainer"> |
| <canvas id="matrixScaleCanvas_v1" width="500" height="330" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var phase = 0; |
| function draw() { |
| var canvas = document.getElementById('matrixScaleCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 330); |
| |
| var px = 100, py = 100; |
| var sx = 1.2 + Math.sin(phase) * 0.8; |
| var sy = 1.2 + Math.cos(phase * 0.7) * 0.4; |
| phase += 0.02; |
| |
| function drawHouse(ctx, x, y, w, h, color, alpha) { |
| ctx.save(); |
| ctx.globalAlpha = alpha || 1.0; |
| ctx.translate(x, y); |
| // Body |
| ctx.fillStyle = color; |
| ctx.fillRect(0, h * 0.4, w, h * 0.6); |
| // Roof |
| ctx.beginPath(); |
| ctx.moveTo(0, h * 0.4); |
| ctx.lineTo(w / 2, 0); |
| ctx.lineTo(w, h * 0.4); |
| ctx.closePath(); |
| ctx.fill(); |
| // Door |
| ctx.fillStyle = 'white'; |
| ctx.fillRect(w * 0.35, h * 0.75, w * 0.3, h * 0.25); |
| ctx.restore(); |
| } |
| |
| // Original |
| ctx.strokeStyle = '#eee'; |
| ctx.lineWidth = 1; |
| ctx.setLineDash([4, 4]); |
| ctx.strokeRect(px, py, 80, 80); |
| ctx.setLineDash([]); |
| drawHouse(ctx, px, py, 80, 80, '#ddd', 0.5); |
| |
| ctx.font = '16px Arial'; |
| ctx.fillStyle = '#bbb'; |
| ctx.fillText('Original (80x80)', px, py - 12); |
| |
| // Scaled |
| ctx.save(); |
| ctx.translate(px, py); |
| ctx.scale(sx, sy); |
| ctx.translate(-px, -py); |
| |
| drawHouse(ctx, px, py, 80, 80, BLUE, 0.6); |
| ctx.lineWidth = 2; |
| ctx.strokeStyle = BLUE; |
| ctx.strokeRect(px, py, 80, 80); |
| ctx.restore(); |
| |
| // Pivot Anchor |
| ctx.beginPath(); |
| ctx.arc(px, py, 7, 0, 2 * Math.PI); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fill(); |
| |
| // Labels |
| ctx.font = 'bold 20px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText('Pivot (px, py)', px + 15, py - 8); |
| |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillText('scaleX: ' + sx.toFixed(2), 280, 160); |
| ctx.fillText('scaleY: ' + sy.toFixed(2), 280, 200); |
| |
| requestAnimationFrame(draw); |
| } |
| draw(); |
| })(); |
| </script> |
| |
| |
| ## MatrixTranslate |
| Preconcat the current matrix with the specified translation |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>MatrixTranslate</td><td>Value: 127</td></tr> |
| <tr><td>FLOAT</td><td>dx</td><td>The distance to translate in X</td></tr><tr><td>FLOAT</td><td>dy</td><td>The distance to translate in Y</td></tr></table> |
| |
| ### MatrixTranslate Illustration |
| |
| The `MatrixTranslate` operation moves the coordinate system by `dx` and `dy`. |
| |
| <div id="matrixTranslateContainer"> |
| <canvas id="matrixTranslateCanvas_v1" width="500" height="250" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var phase = 0; |
| function draw() { |
| var canvas = document.getElementById('matrixTranslateCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 250); |
| |
| // Dynamic DX/DY |
| var dx = 100 + Math.sin(phase) * 100; |
| var dy = 40 + Math.cos(phase * 0.5) * 30; |
| phase += 0.02; |
| |
| var x = 30, y = 30, w = 80, h = 80; |
| |
| function drawHouse(ctx, x, y, w, h, color, alpha) { |
| ctx.save(); |
| ctx.globalAlpha = alpha || 1.0; |
| ctx.translate(x, y); |
| // Body |
| ctx.fillStyle = color; |
| ctx.fillRect(0, 0, w, h); |
| // Roof |
| ctx.beginPath(); |
| ctx.moveTo(0, 0); |
| ctx.lineTo(w / 2, -h * 0.5); |
| ctx.lineTo(w, 0); |
| ctx.closePath(); |
| ctx.fill(); |
| // Door |
| ctx.fillStyle = 'white'; |
| ctx.fillRect(w * 0.35, h * 0.6, w * 0.3, h * 0.4); |
| ctx.restore(); |
| } |
| |
| // Original (Reference) |
| ctx.strokeStyle = '#eee'; |
| ctx.lineWidth = 1; |
| ctx.setLineDash([4, 4]); |
| ctx.strokeRect(x, y, w, h); |
| ctx.setLineDash([]); |
| drawHouse(ctx, x, y, w, h, '#ddd', 0.5); |
| |
| ctx.font = 'bold 18px Arial'; |
| ctx.fillStyle = '#bbb'; |
| ctx.fillText('Original', x, y - 10); |
| |
| // Transformed |
| ctx.save(); |
| ctx.translate(dx, dy); |
| drawHouse(ctx, x, y, w, h, BLUE, 0.6); |
| ctx.lineWidth = 2; |
| ctx.strokeStyle = BLUE; |
| ctx.strokeRect(x, y, w, h); |
| ctx.restore(); |
| |
| // Translation Arrow |
| ctx.beginPath(); |
| ctx.moveTo(x + w/2, y + h/2); |
| ctx.lineTo(x + w/2 + dx, y + h/2 + dy); |
| ctx.strokeStyle = GRAY; |
| ctx.setLineDash([8, 5]); |
| ctx.stroke(); |
| ctx.setLineDash([]); |
| |
| ctx.fillStyle = 'red'; |
| ctx.beginPath(); ctx.arc(x + w/2 + dx, y + h/2 + dy, 5, 0, Math.PI*2); ctx.fill(); |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillText('dx: ' + Math.round(dx) + ', dy: ' + Math.round(dy), 250, 40); |
| |
| requestAnimationFrame(draw); |
| } |
| draw(); |
| })(); |
| </script> |
| |
| |
| ## MatrixSkew |
| Current matrix with the specified skew. |
| |
| 2 Fields, total size 9 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>MatrixSkew</td><td>Value: 128</td></tr> |
| <tr><td>FLOAT</td><td>skewX</td><td>The amount to skew in X</td></tr><tr><td>FLOAT</td><td>skewY</td><td>The amount to skew in Y</td></tr></table> |
| |
| ### MatrixSkew Illustration |
| |
| The `MatrixSkew` operation applies a skew (shear) transformation to the coordinate system. |
| |
| <div id="matrixSkewContainer"> |
| <canvas id="matrixSkewCanvas_v1" width="500" height="330" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var phase = 0; |
| function draw() { |
| var canvas = document.getElementById('matrixSkewCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 330); |
| |
| var skX = Math.sin(phase) * 0.8; |
| var skY = Math.cos(phase * 0.6) * 0.3; |
| phase += 0.02; |
| |
| var x = 120, y = 120, w = 80, h = 80; |
| |
| function drawHouse(ctx, x, y, w, h, color, alpha) { |
| ctx.save(); |
| ctx.globalAlpha = alpha || 1.0; |
| ctx.translate(x, y); |
| // Body |
| ctx.fillStyle = color; |
| ctx.fillRect(0, 0, w, h); |
| // Roof |
| ctx.beginPath(); |
| ctx.moveTo(0, 0); |
| ctx.lineTo(w / 2, -h * 0.5); |
| ctx.lineTo(w, 0); |
| ctx.closePath(); |
| ctx.fill(); |
| // Door |
| ctx.fillStyle = 'white'; |
| ctx.fillRect(w * 0.35, h * 0.6, w * 0.3, h * 0.4); |
| ctx.restore(); |
| } |
| |
| // Original |
| ctx.strokeStyle = '#eee'; |
| ctx.lineWidth = 1; |
| ctx.setLineDash([4, 4]); |
| ctx.strokeRect(x, y, w, h); |
| ctx.setLineDash([]); |
| drawHouse(ctx, x, y, w, h, '#ddd', 0.5); |
| |
| ctx.font = '16px Arial'; |
| ctx.fillStyle = '#bbb'; |
| ctx.fillText('Original', x, y - 10); |
| |
| // Skewed |
| ctx.save(); |
| ctx.translate(x, y); |
| ctx.transform(1, skY, skX, 1, 0, 0); |
| drawHouse(ctx, 0, 0, w, h, BLUE, 0.6); |
| ctx.lineWidth = 2; |
| ctx.strokeStyle = BLUE; |
| ctx.strokeRect(0, 0, w, h); |
| ctx.restore(); |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 22px Arial'; |
| ctx.fillText('skewX: ' + skX.toFixed(2), 35, 50); |
| ctx.fillText('skewY: ' + skY.toFixed(2), 35, 80); |
| |
| requestAnimationFrame(draw); |
| } |
| draw(); |
| })(); |
| </script> |
| |
| |
| ## MatrixRotate |
| Apply rotation to matrix |
| |
| 3 Fields, total size 13 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>MatrixRotate</td><td>Value: 129</td></tr> |
| <tr><td>FLOAT</td><td>rotate</td><td>Angle to rotate</td></tr><tr><td>FLOAT</td><td>pivotX</td><td>X Pivot point</td></tr><tr><td>FLOAT</td><td>pivotY</td><td>Y Pivot point</td></tr></table> |
| |
| ### MatrixRotate Illustration |
| |
| The `MatrixRotate` operation rotates the coordinate system by `rotate` degrees relative to a pivot point `(pivotX, pivotY)`. |
| |
| <div id="matrixRotateContainer"> |
| <canvas id="matrixRotateCanvas_v1" width="500" height="330" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| var angle = 0; |
| function draw() { |
| var canvas = document.getElementById('matrixRotateCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 330); |
| |
| var px = 250, py = 165; |
| angle = (angle + 1) % 360; |
| |
| function drawHouse(ctx, x, y, w, h, color, alpha) { |
| ctx.save(); |
| ctx.globalAlpha = alpha || 1.0; |
| ctx.translate(x, y); |
| // Body |
| ctx.fillStyle = color; |
| ctx.fillRect(0, -h * 0.6, w, h * 0.6); |
| // Roof |
| ctx.beginPath(); |
| ctx.moveTo(0, -h * 0.6); |
| ctx.lineTo(w / 2, -h); |
| ctx.lineTo(w, -h * 0.6); |
| ctx.closePath(); |
| ctx.fill(); |
| // Door |
| ctx.fillStyle = 'white'; |
| ctx.fillRect(w * 0.35, -h * 0.25, w * 0.3, h * 0.25); |
| ctx.restore(); |
| } |
| |
| // Original |
| ctx.strokeStyle = '#eee'; |
| ctx.lineWidth = 1; |
| ctx.setLineDash([4, 4]); |
| ctx.strokeRect(px, py - 80, 80, 80); |
| ctx.setLineDash([]); |
| drawHouse(ctx, px, py, 80, 80, '#ddd', 0.5); |
| |
| ctx.font = '16px Arial'; |
| ctx.fillStyle = '#bbb'; |
| ctx.fillText('Original', px + 8, py - 90); |
| |
| // Rotated |
| ctx.save(); |
| ctx.translate(px, py); |
| ctx.rotate(angle * Math.PI / 180); |
| |
| drawHouse(ctx, 0, 0, 80, 80, BLUE, 0.6); |
| ctx.lineWidth = 2; |
| ctx.strokeStyle = BLUE; |
| ctx.strokeRect(0, -80, 80, 80); |
| ctx.restore(); |
| |
| // Pivot Anchor |
| ctx.beginPath(); |
| ctx.arc(px, py, 7, 0, 2 * Math.PI); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fill(); |
| |
| // Labels |
| ctx.font = 'bold 20px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText('Pivot (px, py)', px + 15, py + 25); |
| ctx.font = 'bold 24px Arial'; |
| ctx.fillText('rotate: ' + angle + '°', 35, 50); |
| |
| requestAnimationFrame(draw); |
| } |
| draw(); |
| })(); |
| </script> |
| |
| |
| ## MatrixSave |
| Save the matrix and clip to a stack |
| |
| 0 Fields, total size 1 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>MatrixSave</td><td>Value: 130</td></tr> |
| </table> |
| |
| ### MatrixSave/Restore Illustration |
| |
| `MatrixSave` pushes the current transformation and clip state onto a stack. `MatrixRestore` pops the state, returning the coordinate system to its previous configuration. |
| |
| <div id="matrixSaveContainer"> |
| <canvas id="matrixSaveCanvas_v1" width="500" height="300" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('matrixSaveCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#BBBBBB', DARK_GRAY = '#444444', BLUE_TRANS = 'rgba(0, 71, 171, 0.2)'; |
| ctx.clearRect(0, 0, 500, 300); |
| |
| // Initial State |
| ctx.strokeStyle = '#eee'; |
| ctx.lineWidth = 2; |
| ctx.strokeRect(50, 50, 100, 100); |
| ctx.fillStyle = GRAY; |
| ctx.font = 'bold 16px Arial'; |
| ctx.fillText('Initial Coordinate System', 50, 45); |
| |
| // SAVE 1 |
| ctx.save(); |
| ctx.translate(150, 0); |
| ctx.rotate(Math.PI/8); |
| |
| ctx.fillStyle = BLUE_TRANS; |
| ctx.fillRect(50, 50, 100, 100); |
| ctx.strokeStyle = BLUE; |
| ctx.lineWidth = 3; |
| ctx.strokeRect(50, 50, 100, 100); |
| ctx.fillStyle = BLUE; |
| ctx.fillText('Transformed (State 1)', 50, 45); |
| |
| // RESTORE 1 |
| ctx.restore(); |
| |
| // Demonstration we are back at initial |
| ctx.beginPath(); |
| ctx.moveTo(50, 200); |
| ctx.lineTo(450, 200); |
| ctx.strokeStyle = DARK_GRAY; |
| ctx.lineWidth = 2; |
| ctx.setLineDash([8, 5]); |
| ctx.stroke(); |
| ctx.setLineDash([]); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 20px Arial'; |
| ctx.fillText('Back to Initial after MatrixRestore', 100, 235); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| ## MatrixRestore |
| Restore the matrix and clip |
| |
| 0 Fields, total size 1 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>MatrixRestore</td><td>Value: 131</td></tr> |
| </table> |
| |
| |
| ## MatrixFromPath (added in v7) |
| Set the matrix relative to a path position and tangent |
| |
| 4 Fields, total size 17 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>MatrixFromPath</td><td>Value: 181</td></tr> |
| <tr><td>INT</td><td>pathId</td><td>The ID of the path</td></tr><tr><td>FLOAT</td><td>percent</td><td>The position on the path [0..1]</td></tr><tr><td>FLOAT</td><td>vOffset</td><td>Vertical offset from the path</td></tr><tr><td>INT</td><td>flags</td><td>Flags for position/tangent</td></tr></table> |
| |
| ### flags |
| | Name | Value | |
| | ---- | ---- | |
| | POSITION_MATRIX_FLAG | 1 |
| | TANGENT_MATRIX_FLAG | 2 |
| ### MatrixFromPath Illustration |
| |
| The `MatrixFromPath` operation calculates a matrix that aligns an object to a specific point and tangent along a path. |
| |
| <div id="matrixFromPathContainer"> |
| <canvas id="matrixFromPathCanvas_v1" width="500" height="300" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('matrixFromPathCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', GRAY = '#888888', DARK_GRAY = '#444444'; |
| ctx.clearRect(0, 0, 500, 300); |
| |
| var p0 = {x: 50, y: 200}; |
| var p1 = {x: 150, y: 50}; |
| var p2 = {x: 350, y: 250}; |
| var p3 = {x: 450, y: 100}; |
| |
| // Reference Path |
| ctx.beginPath(); |
| ctx.moveTo(p0.x, p0.y); |
| ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); |
| ctx.strokeStyle = '#eee'; |
| ctx.lineWidth = 3; |
| ctx.stroke(); |
| |
| function getBezier(t) { |
| var cx = 3 * (p1.x - p0.x), bx = 3 * (p2.x - p1.x) - cx, ax = p3.x - p0.x - cx - bx; |
| var cy = 3 * (p1.y - p0.y), by = 3 * (p2.y - p1.y) - cy, ay = p3.y - p0.y - cy - by; |
| var x = (ax*t*t*t) + (bx*t*t) + (cx*t) + p0.x; |
| var y = (ay*t*t*t) + (by*t*t) + (cy*t) + p0.y; |
| var dx = (3*ax*t*t) + (2*bx*t) + cx; |
| var dy = (3*ay*t*t) + (2*by*t) + cy; |
| return {x: x, y: y, angle: Math.atan2(dy, dx)}; |
| } |
| |
| var t = 0.6; |
| var state = getBezier(t); |
| |
| // Tangent Construction Line |
| ctx.beginPath(); |
| ctx.moveTo(state.x - 50*Math.cos(state.angle), state.y - 50*Math.sin(state.angle)); |
| ctx.lineTo(state.x + 50*Math.cos(state.angle), state.y + 50*Math.sin(state.angle)); |
| ctx.strokeStyle = GRAY; |
| ctx.setLineDash([5, 5]); |
| ctx.stroke(); |
| ctx.setLineDash([]); |
| |
| // Aligned Object (Arrow) |
| ctx.save(); |
| ctx.translate(state.x, state.y); |
| ctx.rotate(state.angle); |
| |
| ctx.beginPath(); |
| ctx.moveTo(-30, -15); |
| ctx.lineTo(30, 0); |
| ctx.lineTo(-30, 15); |
| ctx.fillStyle = BLUE; |
| ctx.fill(); |
| ctx.restore(); |
| |
| // Anchor point |
| ctx.beginPath(); |
| ctx.arc(state.x, state.y, 6, 0, 2 * Math.PI); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fill(); |
| |
| ctx.font = 'bold 20px Arial'; |
| ctx.fillStyle = DARK_GRAY; |
| ctx.textAlign = 'center'; |
| ctx.fillText('Matrix aligned to Path at t=' + t, 250, 40); |
| ctx.font = '16px Arial'; |
| ctx.fillText('Object rotation = path tangent', state.x, state.y + 45); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## MatrixConstant (added in v7) |
| A constant matrix and its associated ID |
| |
| 3 Fields, total size 9 + null x 4 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>MatrixConstant</td><td>Value: 186</td></tr> |
| <tr><td>INT</td><td>matrixId</td><td>The ID of the matrix</td></tr><tr><td>INT</td><td>type</td><td>The type of matrix</td></tr><tr><td>FLOAT[]</td><td>values</td><td>The matrix values</td></tr></table> |
| |
| ### MatrixConstant Illustration |
| |
| The `MatrixConstant` operation defines a static transformation matrix. In RemoteCompose, matrices are typically 3x3 (9 values) for 2D transformations, following the standard row-major indexing: |
| |
| | | | | |
| |:---:|:---:|:---:| |
| | **MSCALE_X** (0) | **MSKEW_X** (1) | **MTRANS_X** (2) | |
| | **MSKEW_Y** (3) | **MSCALE_Y** (4) | **MTRANS_Y** (5) | |
| | **MPERSP_0** (6) | **MPERSP_1** (7) | **MPERSP_2** (8) | |
| |
| <div id="matrixConstantContainer"> |
| <canvas id="matrixConstantCanvas_v1" width="500" height="350" style="border:1px solid #ccc; background: #fff; display: block; margin: 10px 0;"></canvas> |
| </div> |
| |
| <script> |
| (function() { |
| function draw() { |
| var canvas = document.getElementById('matrixConstantCanvas_v1'); |
| if (!canvas) { setTimeout(draw, 100); return; } |
| var ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| var BLUE = '#0047AB', DARK_GRAY = '#444444', GRAY = '#888888'; |
| ctx.clearRect(0, 0, 500, 350); |
| |
| // Matrix Representation |
| var startX = 50, startY = 60, cellW = 100, cellH = 40; |
| var values = ['sX', 'kX', 'tX', 'kY', 'sY', 'tY', 'p0', 'p1', 'p2']; |
| |
| ctx.strokeStyle = DARK_GRAY; |
| ctx.lineWidth = 1; |
| ctx.font = '14px Arial'; |
| ctx.textAlign = 'center'; |
| |
| for (var i = 0; i < 3; i++) { |
| for (var j = 0; j < 3; j++) { |
| var idx = i * 3 + j; |
| var x = startX + j * cellW; |
| var y = startY + i * cellH; |
| |
| ctx.strokeRect(x, y, cellW, cellH); |
| ctx.fillStyle = DARK_GRAY; |
| ctx.fillText(values[idx], x + cellW/2, y + 25); |
| ctx.fillStyle = GRAY; |
| ctx.font = '10px Arial'; |
| ctx.fillText('index ' + idx, x + cellW - 25, y + 12); |
| ctx.font = '14px Arial'; |
| } |
| } |
| |
| ctx.fillStyle = DARK_GRAY; |
| ctx.font = 'bold 20px Arial'; |
| ctx.textAlign = 'left'; |
| ctx.fillText('3x3 Transformation Matrix Layout', startX, 40); |
| |
| // Example explanation |
| var exY = 220; |
| ctx.font = 'bold 16px Arial'; |
| ctx.fillStyle = BLUE; |
| ctx.textAlign = 'left'; |
| ctx.fillText('Example: Translation only', startX, exY); |
| |
| ctx.font = '16px monospace'; |
| ctx.fillText('[ 1, 0, tx,', startX + 20, exY + 30); |
| ctx.fillText(' 0, 1, ty,', startX + 20, exY + 55); |
| ctx.fillText(' 0, 0, 1 ]', startX + 20, exY + 80); |
| } |
| if (document.readyState === 'complete') { draw(); } |
| else { window.addEventListener('load', draw); setTimeout(draw, 500); } |
| })(); |
| </script> |
| |
| |
| |
| ## MatrixExpression (added in v7) |
| A matrix defined by an expression |
| |
| 3 Fields, total size 9 + null x 4 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>MatrixExpression</td><td>Value: 187</td></tr> |
| <tr><td>INT</td><td>matrixId</td><td>The ID of the matrix</td></tr><tr><td>INT</td><td>type</td><td>The type of matrix</td></tr><tr><td>FLOAT[]</td><td>expression</td><td>The matrix expression</td></tr></table> |
| |
| |
| ## MatrixVectorMath (added in v7) |
| Evaluates a matrix * vector and outputs a vector |
| |
| 6 Fields, total size 15 + null x 4 + null x 4 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>MatrixVectorMath</td><td>Value: 188</td></tr> |
| <tr><td>INT</td><td>matrixId</td><td>The ID of the matrix</td></tr><tr><td>SHORT</td><td>opType</td><td>The type of operation (0=multiply)</td></tr><tr><td>INT</td><td>outLength</td><td>The length of the output vector</td></tr><tr><td>INT[]</td><td>outputs</td><td>The IDs to write the output vector</td></tr><tr><td>INT</td><td>inLength</td><td>The length of the input vector</td></tr><tr><td>FLOAT[]</td><td>inputs</td><td>The input vector values</td></tr></table> |
| |
| # Accessibility Operations |
| Semantics and properties used to expose UI information to assistive technologies. |
| |
| Operations in this category: |
| | ID | Name | Version | Size (bytes) |
| | ---- | ---- | ---- | ---- | |
| | 250 | CoreSemantics | v6 | 15 |
| |
| ## CoreSemantics |
| Define accessibility semantics for a component |
| |
| 7 Fields, total size 15 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>CoreSemantics</td><td>Value: 250</td></tr> |
| <tr><td>INT</td><td>contentDescriptionId</td><td>ID of the content description string</td></tr><tr><td>BYTE</td><td>role</td><td>The accessibility role (BUTTON, CHECKBOX, etc.)</td></tr><tr><td>INT</td><td>textId</td><td>ID of the text string</td></tr><tr><td>INT</td><td>stateDescriptionId</td><td>ID of the state description string</td></tr><tr><td>BYTE</td><td>mode</td><td>Semantics merge mode (SET, MERGE)</td></tr><tr><td>BOOLEAN</td><td>enabled</td><td>Whether the component is enabled</td></tr><tr><td>BOOLEAN</td><td>clickable</td><td>Whether the component is clickable</td></tr></table> |
| |
| # Miscellaneous Operations |
| System-level feedback operations like Haptic Feedback. |
| |
| Operations in this category: |
| | ID | Name | Version | Size (bytes) |
| | ---- | ---- | ---- | ---- | |
| | 177 | HapticFeedback | v6 | 5 |
| |
| ## HapticFeedback |
| Generate an haptic feedback |
| |
| 1 Fields, total size 5 bytes |
| <br><table><tr><th>Type</th><th>Name</th><th>Description</th></tr> |
| <tr><td>BYTE</td><td>HapticFeedback</td><td>Value: 177</td></tr> |
| <tr><td>INT</td><td>hapticFeedbackType</td><td>Type of haptic feedback</td></tr></table> |
| |
| |
| # List of Version 6 Operations |
| |
| The following 119 operations were added in version 6 of the format. |
| |
| | ID | Name | Category | Description | |
| | ---- | ---- | ---- | ---- | |
| | 0 | Header | Document Protocol Operations | Document metadata, containing the version, original size & density, capabilities mask | |
| | 2 | ComponentStart | Layout Operations | Basic component encapsulating draw commands. This is not resizable. | |
| | 14 | AnimationSpec | Animation & Particles Operations | Define the animation specifications for a component | |
| | 16 | WidthModifierOperation | Modifier Operations | Set the width dimension on a component | |
| | 40 | PaintData | Paint & Styles Operations | Encode a Paint object with various properties | |
| | 42 | DrawRect | Canvas Operations | Draw the specified rectangle | |
| | 43 | DrawText | Text Operations | Draw a run of text, all in a single direction | |
| | 44 | DrawBitmap | Canvas Operations | Draw a bitmap | |
| | 45 | ShaderData | Paint & Styles Operations | Define a shader with associated uniforms | |
| | 46 | DrawCircle | Canvas Operations | Draw a Circle | |
| | 47 | DrawLine | Canvas Operations | Draw a line segment | |
| | 48 | DrawBitmapFontText | Text Operations | Draw text using a bitmap font | |
| | 51 | DrawRoundRect | Canvas Operations | Draw the specified round-rect | |
| | 52 | DrawSector | Canvas Operations | Draw the specified sector (pie shape)which will be scaled to fit inside the specified oval | |
| | 53 | DrawTextOnPath | Text Operations | Draw text along a path | |
| | 54 | RoundedClipRectModifierOperation | Modifier Operations | Clip the component's content to its rounded rectangular bounds | |
| | 55 | BackgroundModifierOperation | Modifier Operations | Define a background color or shape for a component | |
| | 56 | DrawOval | Canvas Operations | Draw the specified oval | |
| | 57 | DrawTextOnCircle | Text Operations | Draw text along a circle | |
| | 58 | PaddingModifierOperation | Modifier Operations | Define padding around a component | |
| | 59 | ClickModifier | Modifier Operations | Click modifier. This operation contains a list of action operations executed on click | |
| | 63 | Theme | Document Protocol Operations | Set a theme | |
| | 64 | ClickArea | Protocol Operations | Define a region you can click on | |
| | 65 | RootContentBehavior | Protocol Operations | If sizing is SIZING_SCALE, mode is one of SCALE_* values. If sizing is SIZING_LAYOUT, mode is one of LAYOUT_* values. | |
| | 66 | DrawBitmapInt | Canvas Operations | Draw a bitmap using integer coordinates | |
| | 67 | HeightModifierOperation | Modifier Operations | Set the height dimension on a component | |
| | 80 | FloatConstant | Data Operations | A float and its associated id | |
| | 81 | FloatExpression | Logic & Expressions Operations | Define a float via dynamic expression and optional animation | |
| | 101 | BitmapData | Data Operations | Embed or reference bitmap image data | |
| | 102 | TextData | Text Operations | Define a static string and associate it with an ID | |
| | 103 | RootContentDescription | Protocol Operations | Content description of root | |
| | 107 | BorderModifierOperation | Modifier Operations | Define a border for a component | |
| | 108 | ClipRectModifierOperation | Modifier Operations | Clip the component's content to its rectangular bounds | |
| | 124 | DrawPath | Canvas Operations | Draw a path | |
| | 125 | DrawTweenPath | Canvas Operations | Draw an interpolated path between two paths | |
| | 126 | MatrixScale | Matrix Operations | Scale the following draw commands | |
| | 127 | MatrixTranslate | Matrix Operations | Preconcat the current matrix with the specified translation | |
| | 128 | MatrixSkew | Matrix Operations | Current matrix with the specified skew. | |
| | 129 | MatrixRotate | Matrix Operations | Apply rotation to matrix | |
| | 130 | MatrixSave | Matrix Operations | Save the matrix and clip to a stack | |
| | 131 | MatrixRestore | Matrix Operations | Restore the matrix and clip | |
| | 133 | DrawTextAnchored | Text Operations | Draw text centered about an anchor point | |
| | 134 | ColorExpression | Paint & Styles Operations | Define a color via dynamic expression (HSV, ARGB, or Interpolation) | |
| | 135 | TextFromFloat | Text Operations | Convert a float value into a formatted string | |
| | 136 | TextMerge | Text Operations | Merge two strings into one | |
| | 137 | NamedVariable | Data Operations | Add a string name for an ID | |
| | 138 | ColorConstant | Paint & Styles Operations | Define a static color and associate it with an ID | |
| | 139 | DrawContent | Canvas Operations | Draw the component content | |
| | 140 | IntegerConstant | Data Operations | A integer and its associated id | |
| | 143 | BooleanConstant | Data Operations | A boolean and its associated id | |
| | 144 | IntegerExpression | Logic & Expressions Operations | Define an integer via dynamic expression | |
| | 145 | DataMapIds | Data Operations | Encode a collection of named variable IDs | |
| | 146 | IdListData | Data Operations | A list of IDs | |
| | 147 | IdListData | Data Operations | A list of floats | |
| | 148 | LongConstant | Data Operations | A long and its associated id | |
| | 149 | DrawBitmapScaled | Canvas Operations | Draw a bitmap with scaling and alignment options | |
| | 150 | ComponentValue | Logic & Expressions Operations | Expose a component's layout property (width, height, etc.) as a variable | |
| | 151 | TextFromFloat | Text Operations | Look up a string from a collection via index | |
| | 152 | DrawArc | Canvas Operations | Draw the specified arcwhich will be scaled to fit inside the specified oval | |
| | 153 | TextLookupInt | Text Operations | Look up a string from a collection via an integer index variable | |
| | 154 | DataMapLookup | Data Operations | Look up a value in a data map | |
| | 155 | TextMeasure | Text Operations | Measure text dimensions and store the result in a float variable | |
| | 156 | TextLength | Text Operations | Get the length of a string and store it in a float variable | |
| | 157 | TouchExpression | Logic & Expressions Operations | Define a float value derived from touch interactions | |
| | 158 | PathTween | Canvas Operations | Interpolate between two paths and store the result in a new path ID | |
| | 159 | PathCreate | Canvas Operations | Start the creation of a dynamic path | |
| | 160 | PathAppend | Canvas Operations | Append segments to an existing dynamic path | |
| | 161 | ParticlesCreate | Animation & Particles Operations | Create a particle system | |
| | 163 | ParticlesLoop | Animation & Particles Operations | Update and recycle particles in a system | |
| | 164 | ImpulseOperation | Animation & Particles Operations | Execute a list of actions once, and a process block for a fixed duration | |
| | 165 | ImpulseProcess | Animation & Particles Operations | A block of operations executed during an active impulse | |
| | 167 | BitmapFontData | Text Operations | Define a bitmap font with glyph metadata and optional kerning | |
| | 170 | TextMeasure | Text Operations | Extract text-related properties (width, length, etc.) | |
| | 171 | ImageAttribute | Logic & Expressions Operations | Extract image-related properties (width, height) | |
| | 172 | TimeAttribute | Logic & Expressions Operations | Extract time-related information (seconds, hours, etc.) | |
| | 173 | CanvasOperations | Canvas Operations | A collection of canvas operations | |
| | 174 | DrawContentOperation | Modifier Operations | A modifier that triggers drawing of the component's content | |
| | 175 | PathCombine | Canvas Operations | Combine two paths using a boolean operation (Union, Intersect, etc.) | |
| | 176 | FitBoxLayout | Layout Managers | FitBox layout implementation. Only displays the first child component that fits in the available space. | |
| | 177 | HapticFeedback | Miscellaneous Operations | Generate an haptic feedback | |
| | 178 | ConditionalOperations | Logic & Expressions Operations | Execute a list of operations if a condition is met | |
| | 179 | DebugMessage | Protocol Operations | Print debugging messages | |
| | 180 | ColorAttribute | Paint & Styles Operations | Extract components (Hue, RGB, Alpha) from a color | |
| | 200 | RootLayout | Layout Operations | Root element for a document. Other components / layout managers are children in the component tree starting from this Root component. | |
| | 201 | LayoutContent | Layout Operations | Container for child components. BoxLayout, RowLayout and ColumnLayout expect a LayoutComponentContent as a child, encapsulating the components that need to be laid out. | |
| | 202 | BoxLayout | Layout Managers | Box layout implementation. Child components are laid out independently from one another, and painted in their hierarchy order (first children drawnbefore the latter). Horizontal and Vertical positioningare supported. | |
| | 203 | RowLayout | Layout Managers | Row layout implementation, positioning components one after the other horizontally. It supports weight and horizontal/vertical positioning. | |
| | 204 | ColumnLayout | Layout Managers | Column layout implementation, positioning components one after the other vertically. It supports weight and horizontal/vertical positioning. | |
| | 205 | CanvasLayout | Layout Managers | Canvas implementation. Encapsulates drawing operations. | |
| | 208 | TextLayout | Text Operations | Text layout implementation | |
| | 209 | HostAction | Layout Operations | Host action. This operation represents a host action | |
| | 210 | HostNamedAction | Layout Operations | Host Named action. This operation represents a host action | |
| | 211 | ComponentVisibilityOperation | Modifier Operations | Set component visibility from a provided integer variable | |
| | 212 | ValueIntegerChangeActionOperation | Actions & Events Operations | Action that sets a new value for an integer variable | |
| | 213 | ValueStringChangeActionOperation | Actions & Events Operations | Action that sets a new value for a string variable | |
| | 214 | ContainerEnd | Document Protocol Operations | End tag for a container component (Row, Column, etc.) | |
| | 215 | Loop | Logic & Expressions Operations | Execute a list of operations in a loop | |
| | 216 | HostActionMetadata | Layout Operations | Host action + metadata. This operation represents a host action that can also provides some metadata | |
| | 217 | StateLayout | Layout Operations | A layout that switches between child layouts based on an index | |
| | 218 | ValueIntegerExpressionChangeActionOperation | Actions & Events Operations | Action that updates an integer variable via a dynamic expression | |
| | 219 | TouchModifier | Modifier Operations | Touch down modifier. This operation contains a list of action operations executed on touch down | |
| | 220 | TouchUpModifier | Modifier Operations | Touch up modifier. This operation contains a list of action operations executed on touch up | |
| | 221 | OffsetModifierOperation | Modifier Operations | Shift the component's position | |
| | 222 | ValueFloatChangeActionOperation | Actions & Events Operations | Action that sets a new value for a float variable | |
| | 223 | ZIndexModifierOperation | Modifier Operations | Define the Z-Index of a component | |
| | 224 | GraphicsLayerModifierOperation | Modifier Operations | Define transformations (scale, rotation, alpha) for a component | |
| | 225 | TouchCancelModifier | Modifier Operations | Touch cancel modifier. This operation contains a list of action operations executed on touch cancel | |
| | 226 | ScrollModifierOperation | Modifier Operations | Define a scrolling behavior for a component | |
| | 227 | ValueFloatExpressionChangeActionOperation | Actions & Events Operations | Action that updates a float variable via a dynamic expression | |
| | 228 | MarqueeModifierOperation | Modifier Operations | Define a scrolling marquee effect for a component | |
| | 229 | RippleModifier | Modifier Operations | Ripple modifier. This modifier will do a ripple animation on touch down | |
| | 230 | CollapsibleRow | Layout Managers | A row layout that can hide children if space is insufficient | |
| | 231 | WidthInModifierOperation | Modifier Operations | Add additional constraints to the width | |
| | 232 | HeightInModifierOperation | Modifier Operations | Add additional constraints to the height | |
| | 233 | CollapsibleColumn | Layout Managers | A column layout that can hide children if space is insufficient | |
| | 234 | ImageLayout | Layout Managers | Image layout implementation | |
| | 235 | CollapsiblePriorityModifierOperation | Modifier Operations | Add additional priority to children of collapsible layouts | |
| | 236 | RunAction | Operations | This operation runs child actions | |
| | 250 | CoreSemantics | Accessibility Operations | Define accessibility semantics for a component | |
| |
| |
| # List of Version 7 Operations |
| |
| The following 22 operations were added in version 7 of the format. |
| |
| | ID | Name | Category | Description | |
| | ---- | ---- | ---- | ---- | |
| | 49 | DrawBitmapFontTextOnPath | Text Operations | Draw text using a bitmap font along a path | |
| | 181 | MatrixFromPath | Matrix Operations | Set the matrix relative to a path position and tangent | |
| | 182 | TextSubtext | Text Operations | Extract a substring from a source string | |
| | 183 | BitmapTextMeasure | Text Operations | Measure text dimensions specifically for bitmap fonts | |
| | 184 | DrawBitmapTextAnchored | Text Operations | Draw bitmap font text anchored to a point with alignment (pan) | |
| | 185 | Rem | Document Protocol Operations | Embed a remark or comment string in the document | |
| | 186 | MatrixConstant | Matrix Operations | A constant matrix and its associated ID | |
| | 187 | MatrixExpression | Matrix Operations | A matrix defined by an expression | |
| | 188 | MatrixVectorMath | Matrix Operations | Evaluates a matrix * vector and outputs a vector | |
| | 189 | FontData | Data Operations | Embed raw font data in the document | |
| | 190 | DrawToBitmap | Canvas Operations | Draw to a bitmap | |
| | 191 | WakeIn | Protocol Operations | Wake up the render loop after a certain amount of time | |
| | 192 | IdLookup | Logic & Expressions Operations | Look up an ID from an ID collection via index | |
| | 194 | ParticlesCompare | Animation & Particles Operations | Compare particles and execute conditional logic | |
| | 196 | ColorTheme | Paint & Styles Operations | Define a color that adapts to the current theme (light/dark) | |
| | 197 | DataDynamicListFloat | Data Operations | A dynamic list of floats | |
| | 198 | UpdateDynamicFloatList | Data Operations | Update a value in a dynamic float list | |
| | 199 | TextTransform | Text Operations | Transform a string (case conversion, trimming, etc.) | |
| | 237 | AlignByModifierOperation | Modifier Operations | Align a component based on a specific baseline or anchor | |
| | 238 | LayoutCompute | Modifier Operations | Compute component position and measure via dynamic expressions | |
| | 239 | CoreText | Layout Managers | Core text layout implementation with advanced styling | |
| | 240 | FlowLayout | Layout Managers | Flow layout implementation. Positions components one after the other horizontally and wraps to the next line if space is exhausted. | |
| |
| |
| # Experimental Operations |
| |
| The following 6 operations are considered experimental and may change in future versions. |
| |
| | ID | Name | Version | Category | Description | |
| | ---- | ---- | ---- | ---- | ---- | |
| | 196 | ColorTheme | v7 | Paint & Styles Operations | Define a color that adapts to the current theme (light/dark) | |
| | 199 | TextTransform | v7 | Text Operations | Transform a string (case conversion, trimming, etc.) | |
| | 237 | AlignByModifierOperation | v7 | Modifier Operations | Align a component based on a specific baseline or anchor | |
| | 238 | LayoutCompute | v7 | Modifier Operations | Compute component position and measure via dynamic expressions | |
| | 239 | CoreText | v7 | Layout Managers | Core text layout implementation with advanced styling | |
| | 240 | FlowLayout | v7 | Layout Managers | Flow layout implementation. Positions components one after the other horizontally and wraps to the next line if space is exhausted. | |
| |
| |
| # Appendix 1: FloatExpressions |
| |
| | Name | Value | Value | EXAMPLE| |
| | ---- | ---- | ---- | ---- | |
| | ADD | Nan(1) | addition | 3,2,ADD -> 5| |
| | SUB | Nan(2) | subtraction | 3,2,SUB -> 1| |
| | MUL | Nan(3) | multiplication | 3,2,MUL -> 1| |
| | DIV | Nan(4) | division | 3,2,DIV -> 1.5 | |
| | MOD | Nan(5) | Modulus | 3,2,DIV -> 1| |
| | MIN | Nan(6) | Minimum | 3,2,MIN -> 2| |
| | MAX | Nan(7) | Maximum | 3,2,MAX -> 3 | |
| | POW | Nan(8) | power | 3,2,POW -> 9 | |
| | SQRT | Nan(9) | square root | 2,SQRT-> 1.414 | |
| | ABS | Nan(10) | absolute value | -2,ABS -> 2 | |
| | SIGN | Nan(11) | sign | -3, SIGN -> -1 | |
| | COPY_SIGN | Nan(12) | transfer sign |7, -3, COPY_SIGN -> -7 | |
| | EXP | Nan(13) | exponent | 1, EXP -> 2.7182 | |
| | FLOOR | Nan(14) | floor | 32.3,FLOOR -> 32 | |
| | LOG | Nan(15) | log() | 100, LOG -> 2 | |
| | LN | Nan(16) | ln() | 100, LN -> 4.605| |
| | ROUND | Nan(17) | round | 3.5, ROUND -> 4 | |
| | SIN | Nan(18) | sin | 3.141/2, SIN -> 1.00 | |
| | COS | Nan(19) | cosine | 3.141/2, COS -> 0.0 | |
| | TAN | Nan(20) | tan | 3.141/4, TAN -> 1.0 | |
| | ASIN | Nan(21) | asin | 1, ASIN -> 1.57 | |
| | ACOS | Nan(22) | acos | 1, ACOS -> 0 | |
| | ATAN | Nan(23) | atan | 1, ATAN -> 0.785 | |
| | ATAN2 | Nan(24) | atan2 | x,y,ATAN2 - > atan2(x,y) | |
| | MAD | Nan(25) | Multiple and add |7, -3, 2, MAD -> -7 | |
| | IFELSE | Nan(26) | ?: | 1,2,0,IFELES -> 1 | |
| | CLAMP | Nan(27) | clamp |v,min,max,CLAMP -> v | |
| | CBRT | Nan(28) | Cube root | | |
| | DEG | Nan(29) | radians to degree | | |
| | RAD | Nan(30 | degrees to radians | | |
| | CEIL | Nan(31) | Ceiling |3.14, CEIL -> 4 | |
| |
| |
| **Array operation** |
| |
| Array operation examples are for [1,2,3,4,5] |
| | Name | Value | Value | EXAMPLE| |
| | ---- | ---- | ---- | ---- | |
| | A_DEREF | Nan(32) | Get a value from array | 2, a, A_DEREF -> 3| |
| | A_MAX | Nan(33) | Maximum of array | a, A_MAX -> 5| |
| | A_MIN | Nan(34) | Minimum of array | a, A_MIN -> 1| |
| | A_SUM | Nan(35) | Sum of array | a, A_SUM -> 15| |
| | A_AVG | Nan(36) | Average of array | a, A_AVG -> 3| |
| | A_LEN | Nan(37) | Length of array | a, A_LEN -> 5| |
| WIP |
| # Appendix 2: IntegerExpressions |
| |
| | Name | Value | Value | EXAMPLE| |
| | ---- | ---- | ---- | ---- | |
| | ADD | Nan(1) | addition | 3,2,ADD -> 5| |
| | SUB | Nan(2) | subtraction | 3,2,SUB -> 1| |
| | MUL | Nan(3) | multiplication | 3,2,MUL -> 1| |
| | DIV | Nan(4) | division | 3,2,DIV -> 1 | |
| | MOD | Nan(5) | Modulus | 3,2,DIV -> 1| |
| | SHL | Nan(5) | Modulus | 3,2,SHL -> 1| |
| | SHR | Nan(5) | Modulus | 3,2,SHR -> 1| |
| | USHR | Nan(5) | Modulus | 3,2,USHR -> 1| |
| | OR | Nan(5) | Modulus | 3,2,OR -> 1| |
| | AND | Nan(5) | Modulus | 3,2,AND -> 1| |
| | XOR | Nan(5) | Modulus | 3,2,XOR -> 1| |
| | COPY_SIGN | Nan(5) | Modulus | 3,2,COPY_SIGN -> 1| |
| | MIN | Nan(5) | Modulus | 3,2,MIN -> 2| |
| | MAX | Nan(5) | Modulus | 3,2,MAX -> 3| |
| | NEG | Nan(5) | Modulus | 2,NEG -> -2| |
| | ABS | Nan(5) | Modulus | -2,ABS -> 2| |
| | INCR | Nan(5) | Modulus | 2,INCR -> 3| |
| | DECR | Nan(5) | Modulus | 2,DECR -> 1| |
| | NOT | Nan(5) | Modulus | 2,NOT -> FFFFFFFD| |
| | SIGN | Nan(5) | Modulus | 2,SIGN -> 1| |
| | CLAMP | Nan(5) | Modulus | 4,2,3CLAMP -> 3| |
| | IFELSE | Nan(5) | Modulus | 3,2,IFELSE -> 1| |
| | MAD | Nan(5) | Modulus | 4,3,2,MAD -> 1| |
| |
| <script> |
| window.markdeepOptions = { |
| tocDepth: 2, |
| detectMath: true |
| }; |
| </script> |
| <!-- Markdeep: --><style class="fallback">body{visibility:hidden;white-space:pre;font-family:monospace}</style><script src="markdeep.min.js" charset="utf-8"></script><script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js" charset="utf-8"></script><script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script> |
| |