packages feed

tinytools-vty-0.1.0.7: src/Potato/Reflex/Vty/Widget/Popup.hs

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RecursiveDo     #-}

module Potato.Reflex.Vty.Widget.Popup (
  popupPane
  , popupPaneSimple
) where

import           Relude

import           Potato.Reflex.Vty.Helpers

import qualified Graphics.Vty.Input.Events as V
import           Reflex
import           Reflex.Network
import           Reflex.Potato.Helpers
import           Reflex.Vty

import           Data.Default

data PopupPaneSize = PopupPaneSize {
    _popupPaneSize_minWidth      :: Int
    ,_popupPaneSize_minHeight    :: Int
    , _popupPaneSize_widthRatio  :: Float
    , _popupPaneSize_heightRatio :: Float
  }

instance Default PopupPaneSize where
  def = PopupPaneSize 0 0 0.5 0.5

mulRatio :: Int -> Float -> Int
mulRatio i r =  ceiling . (*r) . fromIntegral $ i

type PopupInputWidget t m a =
  Event t () -- ^ escape button pressed
  -> Event t () -- ^ click outside box
  -> m (Event t (), Event t a) -- ^ (close event, output event)

-- TODO reduce constraints
popupPaneInternal :: forall t m a. (MonadWidget t m)
  => PopupPaneSize
  -> PopupInputWidget t m a -- ^ widget to be displayed in the popup
  -> m (Event t a, Event t ()) -- ^ (inner widget event, closed event)
popupPaneInternal PopupPaneSize {..} widgetFnEv = do
  screenWidthDyn <- displayWidth
  screenHeightDyn <- displayHeight
  let
    widthDyn = ffor screenWidthDyn (\sw -> max (mulRatio sw _popupPaneSize_widthRatio) _popupPaneSize_minWidth)
    heightDyn = ffor screenHeightDyn (\sh -> max (mulRatio sh _popupPaneSize_heightRatio) _popupPaneSize_minHeight)
    regionDyn = ffor2 ((,) <$> screenWidthDyn <*> screenHeightDyn) ((,) <$> widthDyn <*> heightDyn) $ \(sw,sh) (w,h) -> Region {
        _region_left = (sw - w) `div` 2
        , _region_top = (sh - h) `div` 2
        , _region_width = w
        , _region_height = h
      }
  escapeEv <- key V.KEsc
  outsideMouseEv <- mouseDown V.BLeft
  (outputEv, closeEv) <- pane regionDyn (constDyn True) $ do
    insideMouseEv <- mouseDown V.BLeft
    (closeEv', outputEv') <- widgetFnEv (void escapeEv) (void $ difference outsideMouseEv insideMouseEv)
    return (outputEv', closeEv')
  return (outputEv, closeEv)

-- TODO reduce constraints
-- | popupPane can only emit a single event before closing itself
-- clicking outside the popup closes the popup and emits no events (conisder disabling this as default behavior?)
popupPane :: forall t m a. (MonadWidget t m)
  => PopupPaneSize
  -> Event t (PopupInputWidget t m a)
  -> m (Event t a, Dynamic t Bool) -- ^ (inner widget event, popup state)
popupPane size widgetEv = mdo
  let
    emptyPopupWidget _ _ = return (never, never)
    inputEv = leftmost [widgetEv, canceledEv $> emptyPopupWidget]
  innerDynEv :: Dynamic t (Event t a, Event t ())
    <- networkHold (return (never, never)) (fmap (popupPaneInternal size) inputEv)
  let
    innerWidgetEv = switchDyn (fmap fst innerDynEv)
    canceledEv = switchDyn (fmap snd innerDynEv)
  outputStateDyn <- holdDyn False $ leftmostWarn "popupOverride" [widgetEv $> True, canceledEv $> False]
  return (innerWidgetEv, outputStateDyn)


-- | a simple popup pane
-- the inner popup pane event closes the popup pane (e.g. notification dialog box with "ok" button)
-- clicking outside or pressing escape closes the popup and emits no events
popupPaneSimple :: forall t m a. (MonadWidget t m)
  => PopupPaneSize
  -> Event t (m (Event t a)) -- ^ when inner event fires, popup is disabled
  -> m (Event t a, Dynamic t Bool) -- ^ (inner widget event, popup state)
popupPaneSimple size widgetEv = popupPane size fancyWidgetEv where
  fmapfn w = \escEv clickOutsideEv -> fmap (\outputEv -> (leftmost [escEv, clickOutsideEv, void outputEv], outputEv)) w
  fancyWidgetEv = fmap fmapfn widgetEv