miso
Copyright(C) 2016-2026 David M. Johnson
LicenseBSD3-style (see the file LICENSE)
MaintainerDavid M. Johnson <code@dmj.io>
Stabilityexperimental
Portabilitynon-portable
Safe HaskellNone
LanguageHaskell2010

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

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).

parentElement :: DOMRef -> IO DOMRef Source #

Parent element of a node (Lynx __GetParent).

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

Instances details
Generic SystemInfo Source # 
Instance details

Defined in Miso.Native.MainThread

Show SystemInfo Source # 
Instance details

Defined in Miso.Native.MainThread

Eq SystemInfo Source # 
Instance details

Defined in Miso.Native.MainThread

FromJSVal SystemInfo Source # 
Instance details

Defined in Miso.Native.MainThread

type Rep SystemInfo Source # 
Instance details

Defined in Miso.Native.MainThread

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 :: MainThreadRef Double
dragRef = mainThreadRef 0
{-# 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 State a () computation, letting you drive the update with the Miso.Lens operators (.=, %=, +=, …).

modifyMainThreadRef_ dragRef $ do
  offset .= newX
  active .= True

Orphan instances

FromJSON DOMRef Source # 
Instance details

ToJSON DOMRef Source #

Lets a target DOMRef ride inside a *With handler's action. Native component actions must be ToJSON/FromJSON, but a raw DOMRef (a JSVal) has no meaningful serialization — and main-thread actions never cross the thread boundary anyway, so these are inert placeholders: toJSON is Null and parseJSON fails. Only import Miso.Native.MainThread where you actually dispatch main-thread events.

⚠ These are global orphan instances for JSVal; do not rely on round-tripping a DOMRef through JSON anywhere.

Instance details