Repository navigation
Releases: aoprisan/fbui
Release list
fbui v0.5.0
Added
fbui-baretimers:Timers<M>—send,send_after,send_every,
cancelled through aTimerhandle — theno_stdcounterpart of
fbui::Proxy's timers (fixed-delay repeats; dropping a handle detaches).
The app gets the handle in the newApp::on_start(default: no-op, so
existing apps are unchanged); board code gets one fromRunner::timers.
Deadlines feedRunner::next_deadline, soBoard::waitsleeps exactly
until the next timer and idle still costs nothing. Timers armed before the
runner has seen the clock count from its first reading.fbui-barerotation:Runner::with_rotation/set_rotation(also at
run time) turn the UI on a sideways- or upside-down-mounted panel through
the existing copy-out rotation; input stays in panel coordinates and is
mapped back, andFramebuffer::flushgets panel-space rects.
fbui_bare::Rotationre-exportsfbui_render::Rotation.fbui-bareRGB565 dithering: anRgb565framebuffer now gets the
ordered (4×4 Bayer) dithered copy-out by default, as the Linux runner
already does, so gradients don't band on 16-bit panels. This changes what
existing RGB565 boards show (32-bit ones are unaffected);
Runner::set_dither(false)restores the plain truncation.- Banded rendering (
NOSTD.md§6.1):Runner::new_banded(app, info, scale, band_rows)paints through a shadow a few rows tall instead of a
whole-screen one, copying each band to the framebuffer as it is drawn
(Surface::banded,Surface::paint_banded,Ui::paint_banded,
Ui::request_full_paint). A 320×240 UI's peak heap drops from 800 KiB to
161 KiB with static fonts and 16-row bands. Output is byte-identical to a
whole-screen paint — pinned byfbui-render/tests/banded.rsand
fbui-widgets/tests/banded.rs— except bilinear-scaled images (±1 level).
tiny-skia's hairline rasterizer is vendored (fbui-render/src/hairline/,
BSD-3) so hairlines stay exact across band edges. - Bitmap fonts (
NOSTD.md§6.2,fbui_render::text::bitmap): text
with no shaping engine or rasterizer at run time..fbffiles hold glyphs
pre-rasterized at fixed sizes (4-bit coverage, read in place, validated
without panics);FontContext::with_bitmap_fonts,add_bitmap_font,
BitmapFont,BitmapFontWriter, andfbui_bare::App::bitmap_fonts. The
make_bitmap_fontexample generates them from any TTF/OTF;
bundled-bitmap-fontships Inter at 12/16/20/24 px (Latin-1, 75 KB). Every
widget works on them. The 320×240 counter UI drops to 45 KiB live / 68 KiB
peak heap, and its firmware from 1.78 MiB to 0.66 MiB of flash with
outline-textoff. No kerning or shaping, fixed sizes, whole-pixel glyph
positions. outline-textfeature (default on infbui-render,fbui-widgetsand
fbui-bare): cosmic-text and swash are now optional.FontContext::set_scale
(called byUi) lets bitmap fonts choose their size in device pixels.- Fonts used in place:
FontContext::with_static_fonts/
load_static_fontandfbui_bare::App::static_fontstake&'static
font data without copying it into the heap (~300 KiB for Inter).
FontContext::with_default_fontnow uses the bundled font in place too —
on Linux as well. The doc viewer passes its compiled-in font this way. Runner::run— the main loop on a runner you configured;fbui_bare::run
is now shorthand forRunner::new(..).run(..).fbui-bare/tests/runner.rs: direct tests of the bare runner — copy-out
and padded stride, idle,invalidate, key and scaled pointer input,
rotation (pixels and input for every quarter turn), RGB565 dithering,
and timers.
Fixed
ProgressBar,SliderandTextInputno longer stretch vertically in a
column with free space. They setflex_growto fill a row, but in a column
it grew them in height: a counter screen's progress bar was laid out
296×139 instead of 296×8. They now cap their height at their control
height (max_size), keeping the row behaviour. Found while tracing a 41 KB
first-frame allocation, which was the stretched bar's coverage mask.
Changed
- Breaking for
default-features = falseusers offbui-render/
fbui-widgets/fbui-bare: outline text is now theoutline-text
feature. A dependent that turns default features off must add it to keep
TTF/OTF text (FontContext::with_fontsand friends); without it only
bitmap fonts are available. Default builds are unchanged. Painterno longer clones the clip mask (a full surface-sized buffer) for
every primitive drawn under a clip; peak heap while painting a clipped
region drops by one mask (75 KiB at 320×240). Output is unchanged.fbui-render's license expression is now
(MIT OR Apache-2.0) AND BSD-3-Clause AND OFL-1.1: the crate's own code
is unchanged in license, but it ships the vendored tiny-skia hairline
rasterizer (BSD-3-Clause) and the Inter font files (OFL-1.1), whose
license texts are included in the package.
fbui v0.4.0
Changed
- Breaking for
default-features = falseusers:fbui-renderand
fbui-widgetsgain a defaultstdfeature (andfbui-widgetsa
defaultall-widgets). A dependent that already turned default
features off must now liststd(plusall-widgets, andharnessas
before) to keep the API it had. Default builds are unchanged.
Added
- fbui with no operating system — the
no_stdtrack (NOSTD.md).fbui-renderandfbui-widgetsbuild#![no_std]+allocwith
default-features = false. A new defaultstdfeature keeps the
hosted build exactly as it was; without it theimage-crate codecs, PNG
export, file paths,Ui::request_screenshot, the flow harness and
profileare unavailable.fbui_render::math::F32Extsupplies thef32
math API fromlibm(inherent methods still win understd).- Minimal widget set:
fbui-widgetswithout the newall-widgets
feature (on by default) compiles onlyLabel,Button,Container,
Stack,ScrollView,List,ImageViewandProgressBar. fbui-bare(new crate): the bare-metal runner. A board supplies a
Framebuffer(info/pixels/flush) and aBoard(poll_input,
now_ms,wait);fbui_bare::runis the main loop, keeping the idle
rule (no damage, no animation →wait(None)).Runnersteps it for
tests. A footprint test gates a 320×240 UI under 2 MiB of heap
(measured: 875 KiB peak).fbui-doc(new crate, alwaysno_std): PNG/JPEG decoding to
tiny-skia pixmaps and a PDF subset renderer — xref tables/streams,
object streams, a reconstructing fallback; Flate/LZW/ASCII85/ASCIIHex/
RunLength/DCT; paths, clipping, shadings, tiling patterns, images with
soft masks; embedded TrueType/OpenType/CFF/Type 1/Type 3 fonts, Type 0
with Identity-H, and a fallback face for non-embedded fonts. Fixtures
from reportlab, fpdf2 and pikepdf, and a no-panic mutation test.fbui_render::Image::from_pixmapwraps a premultiplied tiny-skia
pixmap without a copy.apps/doc-viewer: ano_stdPDF/PNG/JPEG viewer app (library,
paging, zoom, pan) onfbui-bare, built from the minimal widgets plus a
customPageView;apps/doc-viewer-rpi3boots it on a Raspberry Pi
3 with no OS (MMU, mailbox framebuffer, UART keys,wfiidle), verified
in QEMUraspi3bbyscripts/qemu_drive.py.- CI gains a
nostdjob:thumbv7em-none-eabihfbuilds, hostno_std
tests, the Pi 3 image, and a scripted QEMU session.
Fixed
FontContext::layoutno longer panics when the font database is empty
(it lays out nothing) — ano_stdtarget that loaded no font shows no
text instead of crashing.
fbui v0.3.0
Added
-
crates.io publishing. All five crates are now publishable (
publish = falseis gone) and carry the metadata crates.io and docs.rs want: a root
README.md, keywords/categories,LICENSE-MIT/LICENSE-APACHEtexts, and
[package.metadata.docs.rs]feature sets. Sibling crates pin each other
exactly (=x.y.z, via[workspace.dependencies]), matching the lockstep
policy. Pushing av<version>tag runs.github/workflows/release.yml:
consistency check, tests,cargo package --workspace, an ordered
cargo publish --workspace(trusted publishing, or aCARGO_REGISTRY_TOKEN
for the first upload), and a GitHub release from this changelog.
scripts/release-check.shguards the pins/changelog/tag, and CI now
packages and verifies every crate on each push. SeeRELEASING.md. -
fbui-ctland the remote console's text endpoints — the third flow
executor, and the field-support loop.fbui-ctl(a binary infbui,--features remote,std::netonly):
tree,json,shot,tap/press/release/move/wheel,type,
key,trace [--follow],metrics, andrun <flow.txt>— which resolves
a flow's references against the live device viaGET /treeand injects
throughPOST /input.FBUI_CTLpicks the console;FBUI_REMOTE_TOKEN
authenticates.GET /tree.txtservesUi::inspect_text(), andGET /tracethe
last 500 trace lines — which the console records whenever it is enabled,
even withoutFBUI_TRACE.fbui::remote::parse_treereads the/treedocument back into an
InspectNode, pinned to the writer by a round-trip test.docs/tooling.mddocuments the whole track;PHASE-TOOLING.mdrecords
verified-vs-pending status and every deviation from the design;
CLAUDE.mdgains the no-screen workflow.- CI gains a
headlessjob: every committed flow, every example lint-checked,
and a monkey session on three examples — nosudo, nomodprobe.
-
Traces, diagnostics and lints — reading why, and catching what an eye
would catch.FBUI_TRACE=path(or-) writes one line per notable event on the
replay/wall clock:start,input(with the named widget under the
pointer),msg,mutate(ops and damage),frame,timer,proxy,
expect,lint. That is the causal chain input → message → mutation →
damage → frame, which makes "the button did nothing" a one-line
diagnosis: nomsgafter theinputmeans the callback is missing; a
msgwith nomutatemeansupdatematched the wrong arm.App::describe_message(defaultNone) gives the trace the app's own
vocabulary; a#[derive(Debug)]message type is a one-line impl.Ui::diagnostics()/take_diagnostics()— mutations, damage rects
and area, layouts, paints, messages, events. A few integer increments per
operation, always on, so a test can assert cost rather than only pixels.Ui::lint()reports what a tree dump never will:touch-target,
truncated-text,unreachable-focus,off-surface,overflow,
empty-scroll,stacked-modals,duplicate-name,undescribed, and
(from the script resolver)ambiguous-ref.Ui::allow_lintsuppresses
one rule on one widget andUi::set_touch_targetsets the tappable
minimum (default 24 logical px; a touch-only kiosk should set 44).
FBUI_LINT=1runs the pass after every layout and reports each finding
once;expect no-lintsmakes it a flow failure.- All ten shipped examples are lint-clean. Getting there fixed two real
bugs the pass found: theshowcase's panels row pushed its content off a
1024x600 screen, andcustom_widget'sDotdescribed nothing.
Changed
-
Container::shrink()— the flexboxmin-size: 0idiom, so a growing
container bounded by its parent wins over an oversized child. Without it a
windowing child (aListof 50 rows) demands its full content height and
pushes the page off the screen, which is what the newoverflowlint found
in theshowcaseexample. -
Flow scripts (
fbui-rec 2) and two executors — a UI interaction
written as steps and expectations, meaning the same thing in acargo test
and under the runner.fbui-rec 2 tap #inc type "milk" key Enter wait settle expect #count text "1 item"fbui_widgets::scriptholds everything that decides meaning: the
parser, the reference resolver (#name,#screen/field,Kind "text",
@x,y), and expectation evaluation. References resolve against the live
tree at the moment their step runs, so a flow follows the layout; a
reference that matches nothing — or matches a widget that is not on
screen — fails the step instead of tapping the void.fbui_widgets::harnessruns a flow in-process against aUi
(run_text,assert_flow), feeding widget events through the same path
the behavior tests use and calling the caller'supdatefor every
message. A failure panics with the failing line, the actual value, and
the tree.- The runner plays v2 flows wherever
FBUI_REPLAYtakes a recording;
the header picks the format. Input goes through the real gesture
recognizer, a flow exits 0 when every expectation held and non-zero when
one did not, and a failure writes<flow>.fail.txtand.fail.png
beside the flow.FBUI_REPLAY_STEP(default 50 ms) paces the steps.
Raw@mslines may be mixed in and keep their own timestamps. - Committed flows for three shipped examples under
fbui/flows/, which run
headless with no display, tty or privileges. Ui::name-based addressing means the examples no longer keep
Option<WidgetId>fields for their own labels.- Recordings are annotated: a live press is written with the named
widget under it (@120 b l p # tap #inc), so a recorded session can be
read — and hand-converted into a flow — without replaying it.
-
Widget names,
Widget::describe, and the tree as text — the widget
tree becomes readable without looking at the screen.Widget::describe(fbui-widgets) reports a widget's user-visible
content and state as ordered key/value pairs — a label's words, a text
field's content and caret,checked/open/selected, a slider's value
and range, a scroll offset. Every built-in widget implements it; the
default is empty, so third-party widgets keep compiling. It runs only
onUi::inspect, never on the paint or event path.- Names:
Ui::name,Ui::add_named,Ui::find,Ui::name_of,
Ui::names. A name is tree-unique, optional, and dies with its widget;
find("form/name")scopes a name by its named ancestors. Ui::inspect_text()renders the laid-out tree one widget per line —
Kind #name [x,y wxh] "text" prop=value focused— which is what an
author reads instead of a screenshot, and what a review diff shows when
the screen changes.FBUI_REPLAY_TREE=pathwrites that dump at the end of a replay,
besideFBUI_REPLAY_SHOT. An empty replay file is now a valid empty
recording, soFBUI_REPLAY=/dev/nullrenders the first screen, writes
the artifacts and exits.
Changed
-
InspectNode.nameis nowInspectNode.kind(the widget type), and
nameholds the app-assigned name fromUi::name(Option<String>). The
node also growstext,propsandvisible. The remote console's
GET /treeJSON follows:kindreplacesname, and nodes gain optional
name,text,propsand avisibleflag. The built-in console UI shows
all of them. Pre-1.0 rename, in one change, with the only in-repo consumer
updated. -
A headless display backend (
fbui-platform, featureheadless, on by
default) —FBUI_BACKEND=headlessruns the unmodified runner against two
RAM back buffers that present nowhere: no display device, no tty, no seat,
no input devices, no privileges. This is what lets CI and an author who
cannot see the screen drive a real app (FBUI_REPLAY,FBUI_MONKEY,
FBUI_REPLAY_SHOT, the remote console) and get artifacts back. It is a
backend rather than a test runner on purpose: the frame clock, gestures,
timers,Proxy, power policy and record/replay are the real ones, so a
headless result is evidence about the real app.FBUI_HEADLESS_SIZE=WxHsets the surface (default1024x600); a
malformed value is a hard error like the otherFBUI_*knobs.- Buffer ages follow the DRM double-buffered sequence exactly, so the
partial redraw path runs headless rather than theage = 0
repaint-everything path a single buffer would force. - Rows are padded to 64 bytes, so
stride != width * bppand any code that
recomputes the stride breaks loudly in CI instead of quietly on a device. - Presents complete synchronously, so the event loop needs no pacing timer
and an idle headless app still burns ~0% CPU. SIGUSR1(ordisplay::headless::request_mode) simulates a hotplug /
mode change, givingon_display_changedan off-device test for the first
time.NullSeat(fbui-platform) is the shared device-less seat behind the
terminal and headless backends.
-
Text editing:
TextArea, the clipboard, and a fullerTextInput—
closing the "clipboard-less, single-line" v1 scope from Phase 3.TextArea(fbui-widgets): a multi-line text box with word-wrapped
paragraphs, a caret that moves by visual line (Up/Downkeep the
goal column across shorter lines,Home/Endare per line,
Ctrl+Home/Enddocument-wide,PageUp/PageDownby viewport),Enter
inserting a line break, selection across lines (drawn per line with a
line-end tail), and a vertical scroll that follows the caret with a thumb
when content overflows — the wheel scrolls it and bubbles ...
0.1
What's Changed
- Phase 0: kernel-facing spike (DRM/KMS, shadow buffer, VT guard, fbdev) by @aoprisan in #1
- Phase 1: Implement fbui-platform, the stable foundation layer by @aoprisan in #2
New Contributors
Full Changelog: https://github.com/aoprisan/fbui/commits/0.1