| Copyright | (C) 2016-2026 David M. Johnson |
|---|---|
| License | BSD3-style (see the file LICENSE) |
| Maintainer | David M. Johnson <code@dmj.io> |
| Stability | experimental |
| Portability | non-portable |
| Safe Haskell | None |
| Language | Haskell2010 |
Miso.Native.MainThread
Description
Main-thread (MTS) imperative element manipulation
Helpers for main-thread events on the Lynx dual-thread runtime. A handler
registered for a MTS event (see
mainThreadEvents) runs synchronously on the main thread and
receives the target DOMRef via a *With combinator
(e.g. onTapWith). Such a handler must be
imperative: it mutates the element directly with the functions below.
It does not go through the VDOM diff — no re-render, no patches, no background-thread round-trip. This is the low-latency path for gestures and scroll-linked animation.
-- move an element with the finger, entirely on the main thread:
view _ _ _ = view_ [ onTouchMoveWith Drag ] []
update (Drag touch domRef) = io_ $
setStyleProperty domRef "transform"
("translateY(" <> ms (touchY touch) <> "px)")
Conflict caveat. A property you drive imperatively here must not also be
set declaratively by the background-thread view for the same element: both
threads write the shared element tree through the same PAPI, with no
arbitration, so the next background re-render would clobber it (and vice
versa). Keep a single owner per (element, property) — typically compositor
properties like transform / opacity that the view leaves alone. This is
the same discipline Lynx itself requires; it is not enforced.
These call Lynx element PAPI globals and are only meaningful on the native runtime's main thread.
Synopsis
- setStyleProperty :: DOMRef -> MisoString -> MisoString -> IO ()
- setStyleProperties :: DOMRef -> [(MisoString, MisoString)] -> IO ()
- setStylePropertyTransform :: DOMRef -> [TransformFn] -> IO ()
- setAttribute :: DOMRef -> MisoString -> MisoString -> IO ()
- getAttribute :: DOMRef -> MisoString -> IO MisoString
- flushElementTree :: IO ()
- firstElementChild :: DOMRef -> IO DOMRef
- nextElementSibling :: DOMRef -> IO DOMRef
- parentElement :: DOMRef -> IO DOMRef
- eachFrame :: (Double -> IO Bool) -> IO ()
- data SystemInfo = SystemInfo {}
- getSystemInfo :: IO (Maybe SystemInfo)
- data MainThreadRef a
- mainThreadRef :: a -> MainThreadRef a
- readMainThreadRef :: MainThreadRef a -> IO a
- writeMainThreadRef :: MainThreadRef a -> a -> IO ()
- modifyMainThreadRef :: MainThreadRef a -> (a -> a) -> IO ()
- modifyMainThreadRef_ :: MainThreadRef a -> State a () -> IO ()
Imperative element mutation (main thread only)
setStyleProperty :: DOMRef -> MisoString -> MisoString -> IO () Source #
Set a single inline style property on the element, then flush.
setStyleProperty domRef "transform" "translateX(20px)"
setStyleProperties :: DOMRef -> [(MisoString, MisoString)] -> IO () Source #
Set several inline style properties, then flush once.
setStylePropertyTransform :: DOMRef -> [TransformFn] -> IO () Source #
Set the element's transform from a list of typed TransformFns
(from Miso.CSS), then flush — a typed alternative to writing the
transform string by hand.
setStylePropertyTransform ref [ CSS.translateX (CSS.px 20) ]
setAttribute :: DOMRef -> MisoString -> MisoString -> IO () Source #
Set an attribute on the element, then flush.
getAttribute :: DOMRef -> MisoString -> IO MisoString Source #
Read an attribute's current value from the element.
flushElementTree :: IO () Source #
Commit pending element-tree mutations to the screen. The set* helpers
above already flush; call this directly only when batching lower-level calls.
Element-tree navigation (main thread only)
firstElementChild :: DOMRef -> IO DOMRef Source #
First element child of a node (Lynx __FirstElement). Lets a main-thread
handler reach a different element than the event target by walking the
tree — e.g. from a scroll handler's list ref to a sibling scrollbar thumb.
nextElementSibling :: DOMRef -> IO DOMRef Source #
Next element sibling of a node (Lynx __NextElement).
Frame-driven animation (main thread only)
eachFrame :: (Double -> IO Bool) -> IO () Source #
Drive step once per animation frame until it returns False, then release
the underlying callback. step receives the frame timestamp in milliseconds.
This is the vsync-coalesced loop primitive for main-thread, scroll-linked
animation: read the latest gesture state, imperatively paint at most once per
frame (via setStyleProperty / setStylePropertyTransform), and stop by
returning False when the gesture ends.
startFollow ref = eachFrame $ \_ts -> do
d <- readDrag
if not (active d) then pure False else do
setStylePropertyTransform ref [ CSS.translateX (CSS.px (offset d)) ]
pure True
Platform info (main thread only)
data SystemInfo Source #
Lynx's lynx.SystemInfo: device pixel geometry and platform metadata. The
field names match the Lynx SystemInfo object, so it decodes directly. Fields
that Lynx omits on some realms are Maybe — notably runtimeType, which is
unavailable in the lepus (main-thread) runtime.
Constructors
| SystemInfo | |
Fields
| |
Instances
getSystemInfo :: IO (Maybe SystemInfo) Source #
Read Lynx's lynx.SystemInfo, decoded into SystemInfo. This global is
main-thread-only: present on the MTS realm and absent on the BTS realm, so
this returns Just on the main thread and Nothing on the background thread.
The undefined guard makes the background-thread read a safe Nothing rather
than a throw; a decode failure (e.g. a required field missing) is also
Nothing.
Main-thread-local mutable state
data MainThreadRef a Source #
A thin wrapper over IORef for state that lives only on the main
thread and must never reach the background thread's shared model (which the
BTS solely owns — see Miso.Runtime). Use it for transient, main-thread-local
gesture/animation state: the current drag offset, a fling velocity, whether a
follow loop is active, etc.
Reads and writes are ordinary IORef operations, safe here because the MTS is
single-threaded; no atomics are needed.
mainThreadRef :: a -> MainThreadRef a Source #
Create a top-level MainThreadRef with an initial value.
This uses unsafePerformIO to allocate the underlying IORef as a CAF, so
the ref is shared across all uses of the binding. __You must give every
top-level MainThreadRef binding a {-# NOINLINE #-} pragma__ — otherwise
GHC may inline the CAF and allocate a fresh, independent IORef at each use
site, silently splitting your state into multiple copies.
dragRef ::MainThreadRefDouble dragRef =mainThreadRef0 {-# NOINLINE dragRef #-}
readMainThreadRef :: MainThreadRef a -> IO a Source #
Read the current value of a MainThreadRef.
writeMainThreadRef :: MainThreadRef a -> a -> IO () Source #
Overwrite the value of a MainThreadRef.
modifyMainThreadRef :: MainThreadRef a -> (a -> a) -> IO () Source #
Strictly modify the value of a MainThreadRef.
modifyMainThreadRef_ :: MainThreadRef a -> State a () -> IO () Source #
Strictly modify a MainThreadRef with a computation, letting
you drive the update with the Miso.Lens operators (State a ().=, %=, +=, …).
modifyMainThreadRef_ dragRef $ do offset.=newX active.=True
Orphan instances
| FromJSON DOMRef Source # | |
| ToJSON DOMRef Source # | Lets a target ⚠ These are global orphan instances for |