packages feed

monomer-1.6.0.0: src/Monomer/Widgets/Animation/Zoom.hs

{-|
Module      : Monomer.Widgets.Animation.Zoom
Copyright   : (c) 2023 Ruslan Gadeev, Francisco Vallarino
License     : BSD-3-Clause (see the LICENSE file)
Maintainer  : fjvallarino@gmail.com
Stability   : experimental
Portability : non-portable

Zoom animation widget. Wraps a child widget whose content will be animated.

Messages:

- Accepts a 'AnimationMsg', used to control the state of the animation.
-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE Strict #-}

module Monomer.Widgets.Animation.Zoom (
  -- * Configuration
  ZoomCfg,
  -- * Constructors
  animZoomIn,
  animZoomIn_,
  animZoomOut,
  animZoomOut_
) where

import Control.Lens ((&), (.~))
import Data.Default
import Data.Maybe

import Monomer.Helper
import Monomer.Widgets.Container
import Monomer.Widgets.Animation.Transform

import qualified Monomer.Lens as L

{-|
Configuration options for zoom:

- 'autoStart': whether the first time the widget is added, animation should run.
- 'duration': how long the animation lasts in ms.
- 'onFinished': event to raise when animation is complete.
- 'onFinishedReq': 'WidgetRequest' to generate when animation is complete.
-}
newtype ZoomCfg s e = ZoomCfg {
  _zmcTransformCfg :: TransformCfg s e
} deriving (Eq, Show)

instance Default (ZoomCfg s e) where
  def = ZoomCfg {
    _zmcTransformCfg = def
  }

instance Semigroup (ZoomCfg s e) where
  (<>) zc1 zc2 = ZoomCfg {
    _zmcTransformCfg = _zmcTransformCfg zc1 <> _zmcTransformCfg zc2
  }

instance Monoid (ZoomCfg s e) where
  mempty = def

instance CmbAutoStart (ZoomCfg s e) where
  autoStart_ start = def {
    _zmcTransformCfg = autoStart_ start
  }

instance CmbDuration (ZoomCfg s e) Millisecond where
  duration dur = def {
    _zmcTransformCfg = duration dur
  }

instance WidgetEvent e => CmbOnFinished (ZoomCfg s e) e where
  onFinished handler = def {
    _zmcTransformCfg = onFinished handler
  }

instance CmbOnFinishedReq (ZoomCfg s e) s e where
  onFinishedReq req = def {
    _zmcTransformCfg = onFinishedReq req
  }

-- | Animates a widget to fully visible by increasing scale.
animZoomIn
  :: WidgetEvent e
  => WidgetNode s e  -- ^ The child node.
  -> WidgetNode s e  -- ^ The created animation container.
animZoomIn managed = animZoomIn_ def managed

-- | Animates a widget to fully visible by increasing scale. Accepts config.
animZoomIn_
  :: WidgetEvent e
  => [ZoomCfg s e]     -- ^ The config options.
  -> WidgetNode s e  -- ^ The child node.
  -> WidgetNode s e  -- ^ The created animation container.
animZoomIn_ configs managed = makeNode configs managed True
  & L.info . L.widgetType .~ "animZoomIn"

-- | Animates a widget to not visible by decreasing scale.
animZoomOut
  :: WidgetEvent e
  => WidgetNode s e  -- ^ The child node.
  -> WidgetNode s e  -- ^ The created animation container.
animZoomOut managed = animZoomOut_ def managed

-- | Animates a widget to not visible by decreasing scale. Accepts config.
animZoomOut_
  :: WidgetEvent e
  => [ZoomCfg s e]     -- ^ The config options.
  -> WidgetNode s e  -- ^ The child node.
  -> WidgetNode s e  -- ^ The created animation container.
animZoomOut_ configs managed = makeNode configs managed False
  & L.info . L.widgetType .~ "animZoomOut"

makeNode
  :: WidgetEvent e
  => [ZoomCfg s e]
  -> WidgetNode s e
  -> Bool
  -> WidgetNode s e
makeNode configs managed isZoomIn = node where
  node = animTransform_ [_zmcTransformCfg] f managed
  f t (Rect _ _ w h) =
    [ animTranslation $ Point (ft t w) (ft t h)
    , animScale $ Point (fs t) (fs t)
    ]
  ft t s = (1-(fs t))*s/2
  fs t = if isZoomIn
    then fwdStep t
    else 1-(fwdStep t)
  fwdStep t = clamp 0 1 $ t/(fromIntegral dur)
  dur = fromMaybe 500 _tfcDuration
  TransformCfg{..} = _zmcTransformCfg
  ZoomCfg{..} = mconcat configs