packages feed

miso-1.12.0.0: src/Miso/Subscription/Window.hs

-----------------------------------------------------------------------------
{-# LANGUAGE RecordWildCards   #-}
{-# LANGUAGE OverloadedStrings #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Miso.Subscription.Window
-- 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
--
-- = Overview
--
-- "Miso.Subscription.Window" provides subscriptions that listen to
-- <https://developer.mozilla.org/en-US/docs/Web/API/Window#events window-level events>.
-- It also exposes 'windowSub' and 'windowSubWithOptions' as the generic
-- primitives on which 'Miso.Subscription.Mouse.mouseSub' and other
-- per-event wrappers are built.
--
-- = Quick start
--
-- @
-- import "Miso"
-- import "Miso.Subscription.Window"
--
-- data Action
--   = MouseMoved 'Miso.Canvas.Coord'         -- (clientX, clientY)
--   | PointerMoved 'Miso.Event.Types.PointerEvent' -- full pointer data
--
-- subs :: ['Miso.Effect.Sub' Action]
-- subs =
--   [ 'windowCoordsSub'      MouseMoved    -- simple (x, y) pair
--   , 'windowPointerMoveSub' PointerMoved  -- full PointerEvent
--   ]
-- @
--
-- = Subscription variants
--
-- * 'windowCoordsSub' — fires @(clientX, clientY)@ as a 'Miso.Canvas.Coord'
--   on every @pointermove@. Simplest option when only screen position is needed.
--
-- * 'windowPointerMoveSub' — fires the full 'Miso.Event.Types.PointerEvent'
--   (pressure, tilt, pointer type, …) on every @pointermove@.
--
-- * 'windowSub' — listen to __any named window event__ by providing an event
--   name and a 'Miso.Event.Decoder.Decoder':
--
-- @
-- resizeSub :: ('Miso.Canvas.Coord' -> action) -> 'Miso.Effect.Sub' action
-- resizeSub f = 'windowSub' \"resize\" 'Miso.Event.Decoder.emptyDecoder' (const (f (0,0)))
-- @
--
-- * 'windowSubWithOptions' — same as 'windowSub' but accepts
--   'Miso.Event.Types.Options' to call @preventDefault@ or @stopPropagation@
--   on the raw event before forwarding it.
--
-- = See also
--
-- * "Miso.Subscription.Mouse" — 'Miso.Subscription.Mouse.mouseSub' (thin alias over 'windowPointerMoveSub')
-- * "Miso.Event.Decoder" — 'Miso.Event.Decoder.Decoder', 'Miso.Event.Decoder.pointerDecoder'
-- * "Miso.Event.Types" — 'Miso.Event.Types.PointerEvent', 'Miso.Event.Types.Options'
-- * "Miso.Subscription.Util" — 'Miso.Subscription.Util.createSub' used internally
----------------------------------------------------------------------------
module Miso.Subscription.Window
  ( -- *** Subscription
    windowSub
  , windowCoordsSub
  , windowPointerMoveSub
  , windowSubWithOptions
  -- *** Types
  , Coord
  ) where
-----------------------------------------------------------------------------
import           Control.Monad
-----------------------------------------------------------------------------
import           Miso.DSL
import           Miso.Event
import           Miso.Effect
import qualified Miso.FFI.Internal as FFI
import           Miso.JSON hiding (Options, defaultOptions)
import           Miso.String
import           Miso.Subscription.Util
import           Miso.Canvas (Coord)
-----------------------------------------------------------------------------
-- | Captures window coordinates changes as they occur and writes them to
-- an event sink.
windowCoordsSub
  :: (Coord -> action)
  -- ^ Callback fired with @(clientX, clientY)@ on every @pointermove@ event
  -> Sub action
windowCoordsSub f = windowPointerMoveSub (f . client)
-----------------------------------------------------------------------------
-- | @windowSub eventName decoder toAction@ provides a subscription
-- to listen to [window level events](https://developer.mozilla.org/en-US/docs/Web/API/Window#events).
windowSub
  :: MisoString
  -- ^ DOM event name to listen for on @window@ (e.g. @\"resize\"@, @\"pointermove\"@)
  -> Decoder r
  -- ^ 'Decoder' for extracting a value from the raw event object
  -> (r -> action)
  -- ^ Callback fired with the decoded value on each event
  -> Sub action
windowSub = windowSubWithOptions defaultOptions
-----------------------------------------------------------------------------
-- | @windowSubWithOptions options eventName decoder toAction@ provides a
-- subscription to listen to [window level events](https://developer.mozilla.org/en-US/docs/Web/API/Window#events).
windowSubWithOptions
  :: Options
  -- ^ Propagation options (@preventDefault@, @stopPropagation@)
  -> MisoString
  -- ^ DOM event name to listen for on @window@
  -> Decoder result
  -- ^ 'Decoder' for extracting a value from the raw event object
  -> (result -> action)
  -- ^ Callback fired with the decoded value on each event
  -> Sub action
windowSubWithOptions Options{..} eventName Decoder {..} toAction sink =
  createSub acquire release sink
    where
      release =
        FFI.windowRemoveEventListener eventName
      acquire =
        FFI.windowAddEventListener eventName $ \e -> do
          decodeAtVal <- toJSVal decodeAt
          v <- fromJSValUnchecked =<< FFI.eventJSON decodeAtVal e
          case parseEither decoder v of
            Left s ->
              FFI.consoleError ("windowSubWithOptions: Parse error on " <> eventName <> ": " <> ms s)
            Right r -> do
              when _stopPropagation (FFI.eventStopPropagation e)
              when _preventDefault (FFI.eventPreventDefault e)
              sink (toAction r)
-----------------------------------------------------------------------------
-- | @window.addEventListener ("pointermove", (event) => handle(event))@
-- A 'Sub' to handle t'PointerEvent's on window.
windowPointerMoveSub
  :: (PointerEvent -> action)
  -- ^ Callback fired with the full 'PointerEvent' on every @pointermove@
  -> Sub action
windowPointerMoveSub = windowSub "pointermove" pointerDecoder
-----------------------------------------------------------------------------