-- | Widgets for reducer-style applications.
--
-- Each function draws a widget from the model and, when the user changes it,
-- emits a message instead of returning the new value. The backend's reducer
-- runner (@runSdlAppReduce@, @runRgfwAppReduce@) folds the frame's messages
-- into the model with your update function.
--
-- The names match "NanoUI", so import this module qualified:
--
-- @
-- import NanoUI.Emit qualified as Emit
--
-- data Msg = Increment | Decrement
--
-- view :: Int -> NanoUI ()
-- view n = row $ do
--   Emit.button "-" Decrement
--   label (T.pack (show n))
--   Emit.button "+" Increment
-- @
module NanoUI.Emit
  ( emit
  , button
  , checkbox
  , slider
  , select
  , radio
  , textInput
  , textArea
  , tabs
  )
where

import Control.Monad (when)
import Data.Text (Text)
import Data.Typeable (Typeable)
import Effectful (Eff, type (:>))
import NanoUI.Monad (Ui, emit)
import NanoUI.Widgets.Button qualified as W
import NanoUI.Widgets.Checkbox qualified as W
import NanoUI.Widgets.Node (respChanged)
import NanoUI.Widgets.Radio qualified as W
import NanoUI.Widgets.TextArea qualified as W
import NanoUI.Widgets.Select qualified as W
import NanoUI.Widgets.Slider qualified as W
import NanoUI.Widgets.Tabs (Tab)
import NanoUI.Widgets.Tabs qualified as W
import NanoUI.Widgets.TextInput qualified as W

-- | Emit @msg@ when the button is clicked.
button :: (Typeable msg, Ui :> es) => Text -> msg -> Eff es ()
button :: forall msg (es :: [Effect]).
(Typeable msg, Ui :> es) =>
Text -> msg -> Eff es ()
button Text
txt msg
msg = do
  clicked <- Text -> Eff es Bool
forall (es :: [Effect]). (Ui :> es) => Text -> Eff es Bool
W.button Text
txt
  when clicked (emit msg)

checkbox :: (Typeable msg, Ui :> es) => Text -> Bool -> (Bool -> msg) -> Eff es ()
checkbox :: forall msg (es :: [Effect]).
(Typeable msg, Ui :> es) =>
Text -> Bool -> (Bool -> msg) -> Eff es ()
checkbox Text
txt Bool
checked Bool -> msg
toMsg = do
  new <- Text -> Bool -> Eff es Bool
forall (es :: [Effect]). (Ui :> es) => Text -> Bool -> Eff es Bool
W.checkbox Text
txt Bool
checked
  when (new /= checked) (emit (toMsg new))

slider :: (Typeable msg, Ui :> es) => Float -> Float -> Float -> (Float -> msg) -> Eff es ()
slider :: forall msg (es :: [Effect]).
(Typeable msg, Ui :> es) =>
Float -> Float -> Float -> (Float -> msg) -> Eff es ()
slider Float
minV Float
maxV Float
value Float -> msg
toMsg = do
  new <- Float -> Float -> Float -> Eff es Float
forall (es :: [Effect]).
(Ui :> es) =>
Float -> Float -> Float -> Eff es Float
W.slider Float
minV Float
maxV Float
value
  when (new /= value) (emit (toMsg new))

select :: (Foldable f, Typeable msg, Ui :> es) => f Text -> Int -> (Int -> msg) -> Eff es ()
select :: forall (f :: * -> *) msg (es :: [Effect]).
(Foldable f, Typeable msg, Ui :> es) =>
f Text -> Int -> (Int -> msg) -> Eff es ()
select f Text
options Int
index Int -> msg
toMsg = do
  new <- f Text -> Int -> Eff es Int
forall (f :: * -> *) (es :: [Effect]).
(Foldable f, Ui :> es) =>
f Text -> Int -> Eff es Int
W.select f Text
options Int
index
  when (new /= index) (emit (toMsg new))

radio :: (Foldable f, Typeable msg, Ui :> es) => f Text -> Int -> (Int -> msg) -> Eff es ()
radio :: forall (f :: * -> *) msg (es :: [Effect]).
(Foldable f, Typeable msg, Ui :> es) =>
f Text -> Int -> (Int -> msg) -> Eff es ()
radio f Text
options Int
index Int -> msg
toMsg = do
  new <- f Text -> Int -> Eff es Int
forall (f :: * -> *) (es :: [Effect]).
(Foldable f, Ui :> es) =>
f Text -> Int -> Eff es Int
W.radio f Text
options Int
index
  when (new /= index) (emit (toMsg new))

textInput :: (Typeable msg, Ui :> es) => Text -> (Text -> msg) -> Eff es ()
textInput :: forall msg (es :: [Effect]).
(Typeable msg, Ui :> es) =>
Text -> (Text -> msg) -> Eff es ()
textInput Text
value Text -> msg
toMsg = do
  new <- Text -> Eff es Text
forall (es :: [Effect]). (Ui :> es) => Text -> Eff es Text
W.textInput Text
value
  when (new /= value) (emit (toMsg new))

-- | Emit the new text after an edit. Caret and scroll changes emit nothing.
textArea :: (Typeable msg, Ui :> es) => Text -> (Text -> msg) -> Eff es ()
textArea :: forall msg (es :: [Effect]).
(Typeable msg, Ui :> es) =>
Text -> (Text -> msg) -> Eff es ()
textArea Text
value Text -> msg
toMsg = do
  (resp, new) <- Text -> Eff es (Response, Text)
forall (es :: [Effect]).
(Ui :> es) =>
Text -> Eff es (Response, Text)
W.textArea' Text
value
  when (respChanged resp && new /= value) (emit (toMsg new))

-- | Emit the newly active key when the user switches tabs.
tabs :: (Foldable f, Eq a, Typeable msg, Ui :> es) => a -> f (Tab a (Eff es ())) -> (a -> msg) -> Eff es ()
tabs :: forall (f :: * -> *) a msg (es :: [Effect]).
(Foldable f, Eq a, Typeable msg, Ui :> es) =>
a -> f (Tab a (Eff es ())) -> (a -> msg) -> Eff es ()
tabs a
active f (Tab a (Eff es ()))
ts a -> msg
toMsg = do
  new <- a -> f (Tab a (Eff es ())) -> Eff es a
forall (f :: * -> *) a (es :: [Effect]).
(Foldable f, Eq a, Ui :> es) =>
a -> f (Tab a (Eff es ())) -> Eff es a
W.tabs a
active f (Tab a (Eff es ()))
ts
  when (new /= active) (emit (toMsg new))