Animations

Every animation here runs from the same formula and the same numbers as the C code, and each one names the file those numbers come from.

A zrnui app does not call any of these. libzrnui has no animation API at commit ea16771: page changes, presses, switches and toasts animate by themselves, and the style decides how. Swivel, fade and the other named transitions are not in libzrnui. They exist in ZrnMetroCore (ztOS's own apps) and in the older Python SDK's animate().

Where you areWhat you getHow
zrn-sdk, libzrnuiPage change, press, switch, pivot, toast, busy, progress, flingAutomatic. No call, no way to change the numbers
Older Python SDK, ztosIts own turnstile and tilt, plus 13 named transitionsztos.animate(widget, "swivel-in"). Some values differ: see below
ZrnMetroCore (ztmetro/core)13 named transitions, the Swivel board, zmc_turnzmc_transition(), zmc_board_open(). For apps in the ztOS tree, not an SDK

Using these demos

Replay runs it again. The speed button cycles 1x, 0.25x and 0.1x. The slider shows any single millisecond. Demos start when you scroll to them. If your system asks for reduced motion, nothing starts by itself and each demo rests on its last frame, so you can step through with the slider.

Where the web version only approximates
  • Same: durations, staggers, angles, pivots, opacity, easing formulas and the eye distance (a 34 degree field of view on a 740 pt tall screen, gfx.c, zrnmetrogpu.c). A browser's perspective is the same divide the shader does.
  • Different: the fonts and row layouts are plain CSS stand-ins, not Segoe UI through the SDF renderer. The real look is in the renders.
  • Different: the demos draw in the Metro look only. DotMatrix runs the same motion through its lattice, as the real frames show.
  • Different: phones here run at your browser's frame rate, not the S9's, and the C code samples its clock once per compositor frame.
  • Different: libzrnui rotates each row's quads, while these demos rotate a whole row box. Each named-transition demo shows one rectangle in a 300 pt tall stage, while the eye distance stays that of a 740 pt screen.
  • Not compared to the phone: these were compared by eye with frames from the laptop's virtual compositor, not with the S9's display.

The curves

Progress over time for each easing. libzrnui has three, in libzrnui/src/core.c: zui_ease_out is 1 - (1-t)^3, zui_ease_in is t^3, and zui_ease_emph is a cubic Bezier (0.2, 0.8, 0.2, 1). ZrnMetroCore rebuilds the firmware's named curves in zt_shape, with k = weight / 30: Exp is (e^(k t) - 1) / (e^k - 1) and Log is 1 - (e^(k (1-t)) - 1) / (e^k - 1). The firmware stores names and weights. The formula itself is a reconstruction, as the core's own comment says.

zui_ease_out
zui_ease_in
zui_ease_emph
Log 200 (swivel in)
Exp 150 (swivel out)
Log 60
Exp 60
SCurve 7.5

Page change: the turnstile libzrnui

Automatic on zui_push and zui_back, in both styles, with the same numbers. Runs 720 ms in all. Rows leave one at a time, 28 ms apart, each over 128 ms, turning 50 degrees about the left edge on zui_ease_in toward the viewer, with opacity 1 - u^2. New rows start at 330 ms, also 28 ms apart, each over 180 ms from 80 degrees behind the screen, on zui_ease_out. Going back, both run bottom up and the old rows turn the other way.

Source: page_xf, OUT_MS, IN_MS, STAGGER, GAP_AT in libzrnui/src/style-metro.c. The comment above them says 33 ms apart; the code that runs says 28.

Forward

Back

Real frames, from zrn run --headless in a virtual compositor, one run per picture. Frames the compositor delayed past the end are dropped, and the times are not exact: this shows the shape, while the numbers above come from the code. Metro, then DotMatrix.

Real frames of the page change in Metro
Real frames of the page change in DotMatrix

Page change: ZrnMetroCore turnstile ztOS apps only

The built-in apps' own version, for comparison: rows 33 ms apart, 128 ms out on t^2, a 120 ms black gap, 180 ms in on 1 - (1-t)^3. The total depends on the row count. zmc_turn plays this on one page when its content changes. Source: ROW_OUT_MS, ROW_IN_MS, ROW_STAGGER_MS, GAP_MS and TURN_OUT_DEG in ztmetro/core/zrnmetrocore.c. Simplified here: the real one also holds the tapped row until last, and an app's first page arrives by a different curve (zmr_entrance, 28 ms stagger, 190 ms rows, 200 ms title), which is not shown.

Forward

Back

Swivel: the domino board ztOS apps only

The jump grid and the pickers. Each of the 28 tiles turns about a horizontal line through its own middle, in reading order, and each starts while the one before is still moving.

AngleTimeCurveVisibility
In-45 to 0 degrees0.3 sLog 200opacity 0 at 0 s, 1 at 0.01 s
Out0 to 60 degrees0.2 sExp 150opacity 1 until 0.199 s, 0 at 0.2 s

These come from the Lumia 530 firmware's ANIMATIONS.UIX (SwivelTransitionIn at 0x3f29, SwivelTransitionOut at 0x3eb3), recorded in ztmetro/core/zrnboard.h as ZB_SWIVEL_*. The firmware fixes each tile's motion but not the stagger. The 12 ms step (ZB_STAGGER_MS) is ztOS's own choice, and so is using the same order going out. The source says which is which. Tile sizes are also from there and CONSTANTS.UIX: 99 wide with a 12 gap at 480, scaled by 0.75.

A picker adds fixed steps this demo leaves out: the box lit for 116 ms, the page gone in 50 ms, the surface in 33 ms (ZB_BOX_MS, ZB_PAGE_OUT_MS, ZB_PAGE_IN_MS).

Board in, 500 ms held here for viewing, then out

Every named transition ZrnMetroCore, older Python SDK

The table ztanims[] in ztmetro/core/zrnmetrocore.c, read from the Lumia 530's compiled Iris markup. The readerboard pair stays only for the motion catalogue in zrndevtest.

NameAxisDegreesPivotOthermsCurve
turnstile-inY-80 to 0left edgeno350Log 60
turnstile-outY0 to 50left edge1 to 0128Exp 60
swivel-inX-45 to 0middleno (shown from its start)300Log 200
swivel-outX0 to 60middleno (hidden at its end)200Exp 150
continuum-hideX0 to 80top edge1 to 0, drops 120 pt120Exp 60
continuum-showX-50 to 0top edge0 to 1, scale 1.5 to 1, from -60 pt350Log 60
readerboard-inX-45 to 0top edge0 to 1166Log 60
readerboard-outX0 to 90top edge1 to 0166Exp 60
slide-up-fade-innonenonenone0 to 1, up from 24 pt240Log 150
rotate-90Z90 to 0middleno167SCurve 7.5
fade-innonenonenone0 to 1350Log 150
fade-outnonenonenone1 to 0150Exp 150
tile-flip-notifyX-180, 35, -6, 0middleno520Log 10, SCurve 10, linear

turnstile-in

turnstile-out

swivel-in

swivel-out

continuum-hide

continuum-show

readerboard-in

readerboard-out

slide-up-fade-in

rotate-90

fade-in

fade-out

tile-flip-notify

Press and tilt libzrnui

Press a row in this demo: it follows your pointer. What is under the finger leans toward it by up to 14 degrees, but its far edge never lifts more than 40 pt, and it sinks 4.5 %. Rows tilt. Sliders and fields do not move, and a button fills instead. In takes 90 ms, and the release takes 200 ms (160 ms when the press is cancelled by a scroll), all on zui_ease_out. DotMatrix uses the same press_xf.

Source: press_xf, TILT_DEG, TILT_LIFT, PRESS_SINK in style-metro.c, timing in core.c. ZrnMetroCore shares the numbers (ZMC_TILT_*).

Tilt: press it

Small things libzrnui

WhatNumbersSource
Switch block180 ms, zui_ease_outzui_set_on, core.c
Pivot240 ms from where the finger left it, zui_ease_out. Past a quarter of a screen a swipe goes on to the next tabzui_tabs_select, extras.c; core.c
Toast2600 ms: in over 200, out over the last 300. A bar drops 60 pt from the topcore.c, toast in style-metro.c
Busy, unknown progressfive 4 pt dots every 2600 ms, each 7.5 % later, on 0.5 + 4 (p - 0.5)^3 across the widthdots, style-metro.c
Flingspeed times 0.9972^ms, a further 0.8^(ms/16) past an end, stop under 0.02 pt/ms, then back to the end with a 70 ms time constantcore.c
Glide to the end of a chata 45 ms time constant, for up to 600 mszui_page_scroll_end, core.c

A field's box turns from grey to white the moment it has focus. That change is not animated.

Switch

Pivot

Toast

Busy

Fling at 3.2 pt/ms

Older Python SDK: where its numbers differ older

ztos.animate(widget, name, repeat=False, on_done=None) plays a named transition over any Qt widget. Its table is TRANSITIONS in sdk/runtime/ztos/__init__.py. Most entries match the list above, but its swivel predates the firmware decode now in zrnboard.h:

ZrnMetroCore (firmware)Older Python SDK
swivel-in-45 to 0, 300 ms, Log 200, no fade-60 to 0, 300 ms, Log 200, fades 0 to 1
swivel-out0 to 60, 200 ms, Exp 150, no fade0 to 60, 199 ms, Exp 60, fades 1 to 0
Press in, out90, 200 ms70, 160 ms, divided by the menu speed setting
Row out128 ms110 ms, divided by the menu speed setting

Everything else that moves in that SDK is also scaled by ANIM_SPEED, which comes from a user setting, so its real durations vary.

Checked against zrn-sdk commit ea16771. Pictures are real renders of code that compiled. Source and proof.