Tools & Editors
Tools and Editors are the high-level primitives that you will use to build complex, interactive user workflows. They serve as the bridge between user actions (like mouse clicks in the 3D viewport or slider adjustments in a side panel) and the authoritative project state.
Building Interactive Tools
A Tool in the Foresight Development Kit is an asynchronous process that guides the user through a specific task. Unlike standard Bevy systems that run every frame, a Tool has a defined lifecycle and is managed exclusively by the Runtime.
The Tool Lifecycle
Only one Tool can be active in the client at any given time. This exclusivity prevents conflicting interactions and ensures a clear, linear workflow for the user. When a tool is activated:
- Setup: The SDK initializes the tool’s environment, displaying its dedicated UI panel and any associated Viewport Overlays.
- The Event Loop: The tool’s asynchronous
runmethod enters a loop, waiting for and reacting to inputs. These inputs can come from property changes in the UI panel, direct interactions in the 3D viewport, or specialized ECS systems that track spatial state. - Committing Changes: When the user performs an action (like clicking to place a point), the tool processes that data and commits a Statement or a Transaction to modify the project.
- Teardown: Once the task is completed or the user cancels, the tool is stopped, and the SDK automatically cleans up the UI and overlays.
Example: A Measurement Tool
This example illustrates how a tool can wait for user input across the network and the 3D viewport to perform a spatial calculation:
pub struct MyMeasurementTool;
impl Tool for MyMeasurementTool {
type FromViewportOverlay = DVec3; // The coordinate sent when clicking
async fn run(&self, ctx: &mut ToolCtx<Self>) -> anyhow::Result<()> {
// Wait for the user to click the first point in the 3D viewport
let start = ctx.wait_for_input().await?;
// Wait for the second point
let end = ctx.wait_for_input().await?;
// Commit a mutation to create a permanent measurement object in the scene
ctx.commit_transaction(|builder, _| {
async move {
builder.push(CreateMeasurement { start, end }).await
}.boxed()
}).await?;
Ok(())
}
}Form-Driven Design
We use a Form-First approach to tool development. Instead of writing manual layout code, you define your tool’s properties as a serializable struct. This ensures that every tool in the SDK shares a consistent design language and is automatically compatible with future UI updates or scripting enhancements.
Composing Editors
While a Tool handles a specific interaction, an Editor is the container that assembles multiple Tools, Panels, and Toolbars into a task-oriented interface.
Think of an Editor as a “workmode” for the user. For example, a “Voxel Editor” might compose a Brush Tool, an Erase Tool, and a Palette Panel into a single cohesive layout.
Reusable Workflows
By keeping your Tools small and focused, you can reuse them across different Editors. A “Selection Tool” you build for a basic viewer can be reused in a complex engineering editor without any changes.
Editors are assigned unique URI paths, allowing the user to navigate between them as if they were web pages. Navigating to an Editor path reconfigures the client by swapping out the sidebar, updating the viewport overlays, and setting the active Tool to provide what the user needs for their current task.