-
released this
2026-10-03 20:33:21 +02:00 | 557 commits to main since this release0.2.0 - 2026-10-03
0.2 takes forma from one window to a desktop application: several windows,
the platform around them, and forms, data tables, dates, documents and
charts. It breaks the API; the migration guide
shows every break with code before and after. The minimum Rust version
stays 1.90.0.2 was tested by hand on Linux only. It compiles for macOS and Windows,
but the native menu, file dialogs, notifications, window positions and the
system theme have not been run there yet.New optional dependencies, each behind a feature:
pulldown-cmark
(markdown),lyon_tessellation(plot) andjiff(jiff) in
forma-ui;rfd(dialogs),muda(native-menu, on by default, macOS
and Windows only),notify-rust(notifications) anddark-light
(system-theme, on by default, Linux and the BSDs only) in forma-winit.forma-ui
Changed (breaking)
Style::scrolltakes the axes to scroll on:.scroll()becomes
.scroll(Axes::Y)for the old behavior, orAxes::X/Axes::BOTH.
Style::scrollandWidget::scrollareAxesinstead ofbool.Ui::scroll_offset,set_scroll_offsetandmax_scroll_offsettake
and returnVec2instead off32: use.yfor the old value, and
Vec2::new(0.0, y)to set it.- The wheel chains: a nested scroll container at its end hands the wheel to
the one around it, instead of swallowing it. MenuItem::shortcuttakes aShortcutinstead of text:
.shortcut("Ctrl+S")becomes.shortcut(Shortcut::primary(Key::Char('s'))),
shown as "Ctrl+S" or "⌘S" depending on the platform.FindBar::statustakes theUi, to speak its language:
bar.status()becomesbar.status(&ui).DrawCommandhas a newMeshvariant, andDrawCommand::Imagehas two
new fields,uvandfilter: a renderer of its own handles both, and
code building anImagecommand addsuv: Rect::UNIT, filter: ImageFilter::Linearfor the old behavior.- The caret, the arrow keys, Backspace and Delete move by grapheme
cluster instead of by code point. Fontsis a handle whose clones share one set of fonts.
Ui::font_system_mut()andFonts::system_mut()return a guard instead
of&mut FontSystem: pass&mut ui.font_system_mut()to a renderer, and
drop the guard before laying out again.CursorHinthas a newPointervariant (a hand, over links): a backend
of its own maps it, as forma-winit does toCursorIcon::Pointer.Tableshows aTableModeland its selection holds row ids:
TableEvent::SelectedandActivatedcarry the row's id instead of its
index, andselected(),set_selectedandselection()take and
return ids (selected_row()gives the index).Tableis
Table<M = VecModel>;set_rowsstill fills itsVecModel,rows()
is gone (readmodel()), and a header click sorts aVecModelitself,
reportingSortRequestedonly for models that cannot sort.
TableEventis no longerCopy.TreeNodehas a new field,lazy, andTreeRowa new field,busy:
aTreeNode { .. }orTreeRow { .. }literal adds them.- taffy 0.14 instead of 0.12. forma re-exports taffy (
forma::taffy,
forma::prelude,Style::layout), so code using taffy's types through
forma follows taffy's changes:min_sizeandmax_sizeare now
Size<LengthPercentageAuto>. The upgrade fixes layout time that doubled
with every level of nested containers. MenuItem::Action'sshortcutfield is anOption<Shortcut>instead of
anOption<String>, asMenuItem::shortcutabove.Stylehas new public fields (accepts_files,image_fit,invalid):
aStyle { .. }literal adds them, or starts fromStyle::new().icons::ALLholds 12 icons instead of 11 (the newCALENDAR), so its
array type changed.Statsis#[non_exhaustive]: it gains counters, so it is read, not
built.MenuPartshas a private field: it is made by the menu, or with
Default.
Added
- Horizontal and two-axis scrolling, with a horizontal scrollbar. Shift and
the wheel scroll sideways. - A text area that does not wrap scrolls sideways with the wheel and a
scrollbar, and keeps its scroll while unfocused. Tablescrolls sideways when its columns are wider than it, the header
with the rows.VirtualList::with_row_widthmakes rows at least that
wide.- App shortcuts:
Ui::shortcuts_mut()binds aShortcutto an id, scoped
to a widget subtree or global; pressing it reportsEvent::Shortcut(id)
after the widgets had their turn.Actions::on_shortcutmaps it to a
message,Shortcuts::bind_menubinds a menu's shortcuts, and
Shortcut::labelwrites it the platform's way.Modifiers::PRIMARYis
Cmd on macOS and Ctrl elsewhere. - Localization hooks:
Ui::set_localetakes aLocale(separators, date
names and order, 12- or 24-hour time, the language for font fallback),
andUi::set_stringstakes aStringsthat translates forma's own
phrases (Phrase).NumberFieldandMask::Decimaluse the locale's
decimal separator;Locale::format_number,format_groupedand
parse_numberare there for the app. - An invalid state:
Style::invalid(message)/invalid_if, or
Ui::set_invalidafter build. The control is outlined in the danger
color, a screen reader hears it as invalid with the message, and the
message is its tooltip unless it has one. Ui::set_enabledandUi::is_enabled: enable or disable a widget and
its subtree after build.- Triangle meshes with a color per vertex (
DrawCommand::Mesh,
PaintCx::mesh), and image regions with nearest or linear sampling
(PaintCx::image_region,ImageFilter), drawn by forma-wgpu.
Rect::UNITis the whole texture. - Color emoji, from a color emoji font: emoji are shaped in
Fonts::emoji_family(found byload_system_fonts, or set with
set_emoji_family) even where the text's font has a black-and-white
glyph. widgets::SplitPane: fixed and shared panes with draggable, focusable
dividers; collapsible panes come back at their old size
(SplitPaneEvent,SplitPaneState).widgets::SplitButton: a button with a menu of alternatives under an
arrow beside it (SplitEvent::Primary,Chosen(id)).Phrasehas a new
MoreOptions.- Dates and times:
Date,TimeandDateTimeas plain civil values
without time zones (forma::date), with calendar arithmetic
(add_days,add_months,weekday,from_unix_days). Dates run from
the year -9999 to 9999 (Date::MIN,Date::MAX), and the arithmetic
stops there. Withserde, only real dates and times deserialize. The new
jifffeature converts them to and from jiff's civil types and adds
Date::today(). widgets::DatePicker: a date field with a calendar popup (month grid,
today marked, keys for days, weeks, months and years, min/max and a
closure for allowed days), and a range mode with two fields
(DateEvent).widgets::TimePicker: hour, minute and optional second
fields that step and wrap, with AM/PM on a 12-hour clock (TimeEvent).
widgets::DateTimePickerputs the two side by side (DateTimeEvent).- Table models: the
TableModeltrait (row_count,row_id,cell,
optionalsortandrow_of),Cell(text withCellAlignand a
color, orPending), andVecModel, which sorts naturally and filters
by text or a predicate.Table::with_model,update_modeland
set_model; pending rows on screen are asked for with
TableEvent::RowsNeeded(range), once while they stay pending.
compare_naturalsorts the way a person does: empty cells, then numbers
by value, then text with its digits by value.VecModelfinds rows by id
in constant time and refuses two rows with one id. - Image fit modes:
Style::fit(ImageFit::Contain | Cover | None | Fill),
andUi::set_image_size, which also makes an image widget measure to
its picture.widgets::ImageViewerzooms toward the pointer, pans by
dragging, draws pixels sharp above 100 % over a checkerboard, and
reportsViewerEvent::Zoomed. - Charts, with the new
plotfeature (lyon_tessellation; forma-winit has
aplotfeature that turns it on):forma::plot::Plotdraws line,
scatter, area, step and bar series (grouped or stacked) on linear, log
or time axes with locale-written ticks, a legend that toggles series, a
hover readout, wheel zoom, drag pan, box zoom and double-click reset.
set_datatakes live data with a redraw only;decimatethins long
series per pixel column andfillturns an outline into a mesh with a
soft edge.InputCx::modifierstells a custom widget the held keys. - Markdown, with the new
markdownfeature (pulldown-cmark; forma-winit
has amarkdownfeature that turns it on):widgets::MarkdownViewshows
CommonMark with tables, task lists and strikethrough as widgets, with
links, task boxes that edit the source, an image resolver and a code
highlighter hook (MarkdownEvent,MarkdownStyle).
widgets::MarkdownEditorcolors Markdown source, continues lists on
Enter, wraps the selection with Ctrl+B and Ctrl+I, and can show a
preview that follows it in a split pane (EditorEvent). - Lazy trees:
TreeNode::lazynodes reportTreeEvent::ChildrenNeeded
when first expanded and show a busy row untilTreeView::set_children;
children_needed()covers nodes expanded from a saved state. Tree
filters (filter_text,filter) show matches and their ancestors,
opened;sort_byorders every level.Phrase::Loadingnames the busy
row. form::Form<K>: fields kept by the app's own keys, with rules
(Rule::required,min_len,range, and closures withRule::check)
checked on change, on blur or on submit (ValidateOn). It builds each
field with a message line beneath, andvalidatemarks the failing
fields and focuses the first.set_value(ui, ..)andreset(ui)show on
screen at once. Numbers are plain decimals in the locale: no "NaN" or
exponents. New phrasesRequired,TooShort,
NotANumberandOutOfRange.Locale::format_date,parse_dateandformat_time, and a new
Locale::am_pm.Mask::Date(separator)for typed dates, the
icons::CALENDARicon, and new phrases for the pickers (ChooseDate,
PreviousMonth,NextMonth,PreviousYear,NextYear,Hour,
Minute,Second).Toasts::push_messagetakes aToastMessagewith a title and an action
button, reported asToastEvent::Action(id);ToastPartshas a new
actionsfield.- Files dropped from outside the app:
Style::accepts_filesmarks a
target, highlighted while files hover over it;Event::FilesHovering
andEvent::FilesDropped { target, paths }report them, one event for a
whole drop. NewInputEventsFileHovered,FileHoverCancelledand
FileDropped, which forma-winit sends. Table::state/restore(TableState) andTreeView::expanded/
restore_expandedkeep column widths, the sort and the expanded nodes.- Rich text:
Ui::rich_textshows aRichTextbuilt from runs, each with
aRunStyle(family, weight, italic, size, color, underline,
strikethrough, background, link). Links report
Event::LinkActivated, take the keyboard one at a time with Tab, and are
links of their own to screen readers. Ui::record_edit: an app's own change to a text field (a toolbar's
Bold button) as one undo step.FontSystemGuard, the typeUi::font_system_mutreturns, is exported.Ui::copy_text: put text on the clipboard from app code (a "Copy key"
button), reportingEvent::ClipboardFailedon failure like Copy does.Dialog::with_danger(index)draws a destructive answer ("Delete") in the
danger color.Harness::try_find_text(Nonewhen nothing shows the text) and
Harness::find_all_text(every match, in tree order).
Fixed
- A focused widget's own
Style::claimscome before its built-in keys: a
text area that claims Enter getsEvent::Commandinstead of a newline.
An open date picker calendar takes Escape. - The first pointer move onto a custom widget reaches it, and a custom
widget that consumes input is redrawn. - A rebuild that passes a different icon to
Ui::iconnow draws the new
icon; the widget used to keep its first icon for good. - A wrapping label is drawn wrapped at the width its rect got after a
relayout that only a sibling caused. Before, a label next to a button
in a growing column could be drawn on one line, past its box. The same
goes for rich text. - A caption one glyph too wide for its box is no longer shaped again on
every measure at the same width. - Tab (or Enter in a single-line field) into a text area no longer selects
its whole text, where the next keystroke would replace the document. The
caret goes back where it was, or to the end. Single-line fields are still
selected whole. - A menu no longer panics when two inputs for a submenu arrive in one frame
(two quick keys, or a key and a pointer move); keys go to the deepest
menu open by then. - Clicking a
Select's button while its list is open closes the list
instead of opening it again. Toasts::with_duration(Duration::MAX)no longer panics: such toasts stay
until closed.- A wheel delta that is not a finite number is ignored instead of leaving
a text area's scroll broken for good.
forma-wgpu
Added
- Color glyphs drawn from an RGBA atlas page, for color emoji.
- Meshes (
DrawCommand::Mesh) and image regions with nearest or linear
sampling. - One
Rendererdraws into several targets
(add_target,prepare_target,render_target,remove_target),
sharing its glyph atlas and images;WindowSurfaceis one window's
surface drawn through a shared renderer.SurfaceRendererkeeps its API.
Gpu::for_surfaceacquires a GPU for a surface, blocking.
Fixed
- A translucent polyline is blended once where its segments meet, instead
of darker at every join. - A glyph, emoji or icon wider than the glyph atlas no longer panics in
wgpu: the atlas grows to fit it, and what does not fit even the largest
atlas (8192 px) is skipped, with a debug assertion. Gpu::requestasks for the adapter's largest texture size instead of
wgpu's default 8192, so larger images can be registered, and window
surfaces are clamped to the device's limit instead of panicking.update_image_rgbaafterupdate_imageon the same image shows the new
pixels.
forma-winit
Changed (breaking)
App::buildtakes the window it builds:
fn build(&mut self, ui: &mut Ui)becomes
fn build(&mut self, _window: WindowKey, ui: &mut Ui).Frame::exit
closes every window.Settingshas new fields:system_fonts(on by default:runloads
the machine's fonts),exit_when,theme_mode,stateandmenu. A
Settings { .. }literal without..Settings::new(..)adds them.
Added
- Several windows in
run:Frame::open_window(WindowSettings)returns
aWindowKeyand opens the window after the callback;
Frame::close_window,Frame::window_key,App::window_openedand
App::window_closed(window, state), which hears about every window that
goes, on every path, with its finalWindowState. The windows share the GPU, renderer, images and
fonts; screen readers see each.Settings::exit_when(ExitWhen) ends
runwith the last window (the default) or with the main one. - Native menus on macOS and Windows, with forma-winit's new default
native-menufeature (muda):Settings::menubecomes the platform's
menu bar - on macOS with an app menu (About, Hide, Quit) and a Window
menu - andApp::menu_chosen(frame, id)reports choices.
Frame::native_menusays whether it is shown (not on Linux, which keeps
the in-windowMenuBar);Frame::set_menureplaces it.
App::quit_requesteddecides about the Quit of that app menu and its
Cmd+Q. Quit in the Dock, logging out, and Cmd+Q in an app without
Settings::menucannot be refused; the windows still get
window_closed, andApp::exitingruns. - System notifications, with the new
notificationsfeature
(notify-rust):notify(&Notification).Frame::request_attention
(Attention) flashes a window's taskbar entry or bounces its dock icon. - Native file dialogs, with forma-winit's new
dialogsfeature (rfd):
Frame::file_dialog(FileDialog)shows an open, open-many, save or folder
dialog over the window without blocking it, and
App::dialog_closed(frame, DialogId, paths)hands back what was picked. - Dropped files are passed on to forma (not on Wayland, where winit has
no drag and drop). - Windows open where they were:
Frame::window_stateandwindow_closed
give aWindowState(the last normal size and position, and whether it
was maximized or fullscreen), andSettings::restore/
WindowSettings::restoreopen a window that way again. A position is
restored with the scale of its screen, and dropped unless enough of the
window's top edge lands on a screen; an unusable size falls back to the
default.WindowSettingshas
maximizedandfullscreen. The newserdefeature serializes
WindowStateand turns on forma's. runfollows the system's light or dark appearance:
App::theme_changed(frame, SystemTheme)runs when a window opens and
when the system switches, and appliesTheme::light()ordark()by
default.Settings::theme_mode(ThemeMode) can fix it instead. On
Linux and the BSDs the new defaultsystem-themefeature asks the
desktop portal (dark-light); "no preference", GNOME's default, counts
as light.App::close_requesteddecides whether a window the user closes closes
(CloseResponse::Close, the default) or stays open (Keep), to ask
first;App::exitingruns once as the app ends, after every window got
window_closed.markdownandplotfeatures, which turn on forma's.forma_winit::winitre-exports winit, so matching window events in
App::unhandledneeds no direct winit dependency of the same version.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads