default_test_size : { width : I32, height : I32 }
The window size Input.for_tests reports. Ordinary rather than special:
a test that depends on the size should say so with with_window.
The value a RocRay app folds in once per cycle, and the small records that ride on it.
Input(msg) is what the platform hands update!: the device snapshot, the
window geometry, the simulation clock, the task messages that arrived, the
recording status, and the files dropped onto the window. Everything here is
pure data with pure receivers, so a package can accept a whole input rather
than an open record of the fields it happens to read, and a test can build
one from for_tests without a host.
The platform's App re-exports these, so an app writes App.Input(Msg) and
never depends on this package directly. msg is the app's own message type;
the platform's requires block binds it, which is what makes an Input(msg)
parameter the witness that pins a task's return type. Starting a task is an
effect and lives in the platform's Task.
default_test_size : { width : I32, height : I32 }
The window size Input.for_tests reports. Ordinary rather than special:
a test that depends on the size should say so with with_window.
One file dropped onto the window, and where the pointer was when it landed.
The path is the one the window system reported, absolute on every platform. Reading the file is a separate, waiting effect, so a drop is handled by starting a task:
Task.spawn!(input, || Opened(Files.read_bytes!(drop.path)))
position is the pointer position the host sampled for the cycle the
drop arrived on, in the same logical coordinates as
input.devices.mouse.position(), so an app that has more than one drop
target can tell which one the file landed on.
:= {
devices : Snapshot,
window : Snapshot,
time : Cycle,
messages : List(msg),
capture : Status,
dropped : List(Dropped),
dropped_overflow : Bool,
}
Everything the host observed for one cycle, handed to update!.
messages contains every task message delivered for this cycle, in the
order the tasks finished. Independent tasks may finish in any order; the
order they were spawned in does not constrain it.
capture contains the recording status sampled for this cycle.
dropped contains the files dropped onto the window since the previous
input, in the order the window system reported them. Like a key press it
is an interval event rather than a latest value: it is empty on almost
every cycle, and exactly one call to update! sees any given drop. At
most 64 paths are delivered per cycle; a single drop carrying more has
its extra paths discarded, and dropped_overflow says so.
fields : Input(msg) -> {
devices : Snapshot,
window : Snapshot,
time : Cycle,
messages : List(msg),
capture : Status,
dropped : List(Dropped),
dropped_overflow : Bool,
}
Return the complete structural input for platform-independent libraries.
from_fields : {
devices : Snapshot,
window : Snapshot,
time : Cycle,
messages : List(msg),
capture : Status,
dropped : List(Dropped),
dropped_overflow : Bool,
} -> Input(msg)
Build an input by stating every sampled field at once.
This is the from-scratch constructor; for_tests is the one to reach
for when only a field or two matters, since it supplies neutral values
for the rest.
Pass a structural record written out here. Use fields when reading an
existing input and the with_* receivers when changing one field.
for_tests : { } -> Input(msg)
A neutral input for testing an app's pure update logic from an expect.
Nothing is pressed, the window is an ordinary focused
default_test_size, the clock reads zero on its first cycle, no
messages arrived, and nothing is recording. Customize it with the
with_* receivers, which is what makes a test say only the one thing
it is about:
expect
input = App.Input.for_tests({}).with_devices(Devices.none.with_key_pressed(KeyEscape))
decide(model, input) == Quit
Building the model this is called with is the other half: every host
resource an app can hold has a resource-free stub
(Draw.Font.stub, Audio.Sound.stub, Text.Prepared.stub,
Assets.Texture.stub, ...), so a Model full of assets can be written
down in a pure test.
update! itself is effectful, and an expect cannot call it. Keep
the decisions in pure functions -- which message to fold in, whether
to quit, what work to start -- and test those; update! is the thin
shell that performs them.
with_devices : Input(msg), Snapshot -> Input(msg)
Replace this input's sampled device snapshot. Build one from Devices.none.
with_window : Input(msg), Snapshot -> Input(msg)
Replace this input's sampled window geometry and visibility.
with_time : Input(msg), Cycle -> Input(msg)
Replace this input's clock sample. Use it to drive a second cycle:
input.with_time({ ..Time.first_cycle, cycle_count: 1 }).
with_messages : Input(msg), List(msg) -> Input(msg)
Deliver task messages on this input, in the order the tasks finished.
with_message : Input(msg), msg -> Input(msg)
Deliver one more task message on this input, after any already there.
with_capture : Input(msg), Status -> Input(msg)
Replace this input's sampled recording status.
with_dropped : Input(msg), List(Dropped) -> Input(msg)
Deliver files dropped onto the window on this input.
with_dropped_overflow : Input(msg), Bool -> Input(msg)
Say that this cycle's drop carried more paths than the host delivers,
which is what input.dropped_overflow reports.