# faststep
`faststep` is a UIKit-inspired UI framework for embedded Rust targets built on
`embedded-graphics`.
The crate is designed for projects where:
- the application should describe views and behavior, not raw touch bookkeeping
- the framework should own widgets, scrolling, alert presentation, and redraw flow
- device-specific display and touch code should stay behind traits
- the same UI layer should be able to target multiple OEM devices and, later, a simulator
This repository is preparing the first public `0.1.0` release. The goal of that
release is not to claim “finished framework”, but to publish a coherent and
documented foundation that is already useful in real device work.
## What 0.1.0 Covers
The `0.1.x` line is the first public foundation release for `faststep`.
It is intended to provide:
- a clear preferred architecture
- a documented public API
- compile-checked examples for each major widget family
- an explicit OEM or simulator boundary
- enough widget and container breadth for real UI composition
It intentionally does not try to hide that the framework will keep evolving.
The purpose of `0.1.0` is to release a sound base with good documentation, not
to wait until every future abstraction is settled.
## Design Direction
The intended authoring flow is:
1. Keep `main` thin.
2. Provide a root view implementation.
3. Let an OEM adapter construct the board, display, and touch side.
4. Start the system with `run_ui_system(...)`.
5. Let the root view configure its children and view metadata.
6. Use container views for stack, tab, split, and alert behavior.
7. Let controls keep their own interaction state.
8. Build scrolling surfaces on top of `ScrollView`.
9. Use datasource and delegate contracts for data-driven views such as lists.
That keeps responsibilities separated:
- app code owns structure and behavior
- `faststep` owns framework mechanics
- the OEM layer owns hardware and present policy
## Main Concepts
### Foundation
- `UiSystem`: owns the display, root view, theme, i18n, and registration state
- `UiView`: root or custom view contract
- `ViewRegistration`: configuration object used during `configure(...)`
- `ViewEvent` and `ViewRedraw`: explicit redraw and message flow
### Runtime and OEM
- `DisplayPort`: display backend abstraction
- `UiCanvas`: draw surface contract for overlays and dimming
- `UiRuntimeDriver`: time, sleep, and touch polling abstraction
- `UiRuntimePresenter`: redraw batching and panel-specific presentation policy
- `run_ui_system(...)`: framework-owned runtime loop
### Widgets and Containers
- `Button`
- `ImageView`
- `TextView`
- `RichTextView`
- `ScrollView`
- `ListView`
- `StackView`
- `TabView`
- `SplitView`
- `AlertView`
- `AlertHost`
- `ModalHost`
### Shared Services
- `FsTheme`: semantic palette configured once at startup
- `I18n`: lightweight localization layer
## Why It Is Shaped This Way
The main engineering goal is to avoid two common failure modes in embedded UI
projects:
1. the UI loop lives in the application binary and becomes device-specific glue
2. widget interaction state leaks upward into every screen and root view
`faststep` pushes against both:
- the runtime loop is owned by the framework
- buttons own their own press and highlight state
- alerts own their own modal timing and touch capture
- scrolling behavior is shared and centralized
- lists are driven by datasource and delegate roles instead of manual per-screen logic
This is the same reason the crate keeps a strong boundary between UI code and
OEM-specific presentation code.
## Typical Startup Shape
At a high level, a new app should look like this:
```rust
use embedded_graphics::pixelcolor::Rgb565;
use faststep::{FsTheme, I18n, run_ui_system};
let display = MyDisplayPort::new();
let theme = FsTheme::default()
.with_accent(Rgb565::new(5, 24, 29), Rgb565::new(31, 63, 31))
.with_status_colors(
Rgb565::new(7, 40, 12),
Rgb565::new(31, 43, 5),
Rgb565::new(26, 13, 10),
Rgb565::new(31, 63, 31),
);
let i18n = I18n::new("en", "en", LOCALES);
let root = RootView::new();
let driver = MyRuntimeDriver::new();
let presenter = MyPresenter::new();
run_ui_system(display, root, theme, i18n, driver, presenter)?;
```
The important part is not the exact syntax. The important part is the boundary:
- the application supplies the root view
- the framework runs the loop
- the OEM adapter owns hardware setup and present policy
## View Authoring
Views are expected to declare their own metadata and child structure in
`configure(...)`.
Typical responsibilities inside a view:
- set title
- set alpha or visibility
- enable clipping where appropriate
- register child views
- route touch into child widgets or containers
- return explicit redraw requests
Conceptually:
```rust
registration.set_title(Localized::new("root.title", "Devices"));
registration.set_clips_to_bounds(true);
registration.add_child(
ChildView::new(ViewId::DevicesList, list_frame).with_kind(ViewKind::List)
)?;
```
This is how `faststep` moves closer to a UIKit-style “view knows its structure”
model instead of a loose drawing helper model.
## Scrolling and Lists
`ScrollView` is the shared scrolling primitive.
It owns:
- drag tracking
- inertial scrolling
- overscroll and snap-back behavior
- transient scroll indicator behavior
The scroll indicator is designed to behave like a mobile list:
- hidden on touch-down
- revealed only once real scrolling starts
- visible during drag, fling, or snap-back
- faded out after motion ends
`ListView` builds on top of `ScrollView` and adds:
- datasource-driven item identity and sizing
- delegate-driven row rendering
- row highlight state
- row selection state
Rows are rendered through `ListRow` and `ListRowState`, so a row renderer can
change appearance when highlighted or selected instead of carrying that logic as
parallel app state.
## Alerts and Modal Presentation
Alerts are intended to be presentation-owned by `faststep`.
The application provides an alert spec:
```rust
let mut alerts = AlertHost::<ScreenAlert, 2>::new();
alerts.present(
ScreenAlert::DeleteConfirm,
AlertSpec::confirm(
Localized::new("delete.title", "Delete item?"),
Localized::new("delete.body", "This action cannot be undone."),
),
)?;
```
The framework then owns:
- modal state
- backdrop fade
- panel motion
- touch capture
- dismissal timing
That is the correct long-term direction for API simplicity.
## Theming
`FsTheme` is the global semantic palette for the system.
The default palette is light and neutral:
- whites and greys for structure
- blue for primary or confirm actions
- amber for warnings
- red for destructive or error actions
The theme is configured once at startup and then consumed semantically by views
and widgets instead of each screen inventing its own raw color rules.
## Examples Included In The Crate
The crate ships with compile-checked examples for the main API families:
- [examples/buttons.rs](examples/buttons.rs)
- [examples/text_and_image.rs](examples/text_and_image.rs)
- [examples/scroll_and_list.rs](examples/scroll_and_list.rs)
- [examples/containers.rs](examples/containers.rs)
- [examples/alerts.rs](examples/alerts.rs)
- [examples/runtime_shell.rs](examples/runtime_shell.rs)
- [examples/uikit_root.rs](examples/uikit_root.rs)
- [examples/root_delegate.rs](examples/root_delegate.rs)
These examples are intended to be docs.rs-friendly and are included in the
packaged crate. The manifest enables example scraping so API pages can link back
to real example code.
## Documentation Set
The first release is documented from three angles:
- [docs/DEVELOPER_GUIDE.md](docs/DEVELOPER_GUIDE.md): architecture, authoring model, runtime boundary, and core contracts
- [docs/WIDGET_CATALOG.md](docs/WIDGET_CATALOG.md): quick map of the public widget and container surface
- [examples/uikit_root.rs](examples/uikit_root.rs): end-to-end root view composition example
The docs.rs landing page is also backed by a dedicated crate overview in
`docs/CRATE_DOCS.md` so the published crate starts with more context than a
minimal API index.
## Compatibility Position
`UiApp` is still exported because existing code depends on it, but it should be
treated as a compatibility layer rather than the preferred authoring surface.
New work should start from:
- `UiSystem`
- `UiView`
- `ViewRegistration`
- container views
- datasource and delegate-driven list APIs
## Repository Rules
The repository keeps source files below the 250 LOC limit. When a module grows,
it should be split into a `src/<module>/` folder instead of leaving one long
file behind. This release prep already applies that rule to the main framework
modules.
## Current State
For the initial public release, the crate now has:
- release metadata
- crate-level rustdoc
- documented public API
- packaged examples
- a stricter module layout
- packaging verification
That is enough to keep preparing `0.1.0` deliberately instead of treating the
first release as an afterthought.