packages feed

monomer-1.5.1.0: src/Monomer/Widgets/Containers/Alert.hs

{-|
Module      : Monomer.Widgets.Containers.Alert
Copyright   : (c) 2018 Francisco Vallarino
License     : BSD-3-Clause (see the LICENSE file)
Maintainer  : fjvallarino@gmail.com
Stability   : experimental
Portability : non-portable

Simple alert dialog, displaying a close button and optional title. Usually
embedded in a zstack component and displayed/hidden depending on context.

A simple text message can be displayed with 'alertMsg', providing the message
text and the event to generate when the user closes the alert:

@
alertMsg "En error occurred" AlertClosedEvent
@

Alternatively, a custom widget can be provided to display as content:

@
customAlert = alert AlertClosedEvent content where
  content = hstack [
      label "Error:",
      filler,
      label errorDescription
    ]
@
-}
{-# LANGUAGE Strict #-}

module Monomer.Widgets.Containers.Alert (
  -- * Configuration
  AlertCfg,
  -- * Constructors
  alert,
  alert_,
  alertMsg,
  alertMsg_
) where

import Control.Applicative ((<|>))
import Control.Lens ((&), (.~), (%~))
import Data.Default
import Data.Maybe
import Data.Text (Text)

import qualified Data.Sequence as Seq

import Monomer.Core
import Monomer.Core.Combinators

import Monomer.Widgets.Composite
import Monomer.Widgets.Container
import Monomer.Widgets.Containers.Box
import Monomer.Widgets.Containers.BoxShadow
import Monomer.Widgets.Containers.Keystroke
import Monomer.Widgets.Containers.Stack
import Monomer.Widgets.Singles.Button
import Monomer.Widgets.Singles.Icon
import Monomer.Widgets.Singles.Label
import Monomer.Widgets.Singles.Spacer

import qualified Monomer.Lens as L

{-|
Configuration options for alert:

- 'titleCaption': the title of the alert dialog.
- 'closeCaption': the caption of the close button.
-}
data AlertCfg = AlertCfg {
  _alcTitle :: Maybe Text,
  _alcClose :: Maybe Text
}

instance Default AlertCfg where
  def = AlertCfg {
    _alcTitle = Nothing,
    _alcClose = Nothing
  }

instance Semigroup AlertCfg where
  (<>) a1 a2 = AlertCfg {
    _alcTitle = _alcTitle a2 <|> _alcTitle a1,
    _alcClose = _alcClose a2 <|> _alcClose a1
  }

instance Monoid AlertCfg where
  mempty = def

instance CmbTitleCaption AlertCfg where
  titleCaption t = def {
    _alcTitle = Just t
  }

instance CmbCloseCaption AlertCfg where
  closeCaption t = def {
    _alcClose = Just t
  }

-- | Creates an alert dialog with the provided content.
alert
  :: (WidgetModel s, WidgetEvent e)
  => e                -- ^ The event to raise when the dialog is closed.
  -> WidgetNode () e  -- ^ The content to display in the dialog.
  -> WidgetNode s e   -- ^ The created dialog.
alert evt dialogBody = alert_ evt def dialogBody

-- | Creates an alert dialog with the provided content. Accepts config.
alert_
  :: (WidgetModel s, WidgetEvent e)
  => e                -- ^ The event to raise when the dialog is closed.
  -> [AlertCfg]       -- ^ The config options for the dialog.
  -> WidgetNode () e  -- ^ The content to display in the dialog.
  -> WidgetNode s e   -- ^ The created dialog.
alert_ evt configs dialogBody = newNode where
  config = mconcat configs
  createUI = buildUI (const dialogBody) evt config
  newNode = compositeD_ "alert" (WidgetValue ()) createUI handleEvent []

-- | Creates an alert dialog with a text message as content.
alertMsg
  :: (WidgetModel s, WidgetEvent e)
  => Text            -- ^ The message to display.
  -> e               -- ^ The event to raise when the dialog is closed.
  -> WidgetNode s e  -- ^ The created dialog.
alertMsg message evt = alertMsg_ message evt def

-- | Creates an alert dialog with a text message as content. Accepts config.
alertMsg_
  :: (WidgetModel s, WidgetEvent e)
  => Text            -- ^ The message to display.
  -> e               -- ^ The event to raise when the dialog is closed.
  -> [AlertCfg]      -- ^ The config options for the dialog.
  -> WidgetNode s e  -- ^ The created dialog.
alertMsg_ message evt configs = newNode where
  config = mconcat configs
  dialogBody wenv = label_ message [multiline]
    & L.info . L.style .~ collectTheme wenv L.dialogMsgBodyStyle
  createUI = buildUI dialogBody evt config
  newNode = compositeD_ "alert" (WidgetValue ()) createUI handleEvent []

buildUI
  :: (WidgetModel s, WidgetEvent ep)
  => (WidgetEnv s ep -> WidgetNode s ep)
  -> ep
  -> AlertCfg
  -> WidgetEnv s ep
  -> s
  -> WidgetNode s ep
buildUI dialogBody cancelEvt config wenv model = mainTree where
  title = fromMaybe "" (_alcTitle config)
  close = fromMaybe "Close" (_alcClose config)

  emptyOverlay = collectTheme wenv L.emptyOverlayStyle
  dismissButton = hstack [button close cancelEvt]
  closeIcon = icon_ IconClose [width 2]
    & L.info . L.style .~ collectTheme wenv L.dialogCloseIconStyle

  alertTree = vstack_ [sizeReqUpdater clearExtra] [
      hstack [
        label title & L.info . L.style .~ collectTheme wenv L.dialogTitleStyle,
        filler,
        box_ [alignTop, onClick cancelEvt] closeIcon
      ],
      dialogBody wenv,
      filler,
      box_ [alignRight] dismissButton
        & L.info . L.style .~ collectTheme wenv L.dialogButtonsStyle
    ] & L.info . L.style .~ collectTheme wenv L.dialogFrameStyle
  alertBox = box_ [onClickEmpty cancelEvt] (boxShadow alertTree)
    & L.info . L.style .~ emptyOverlay
  mainTree = keystroke [("Esc", cancelEvt)] alertBox

handleEvent
  :: WidgetEnv s ep
  -> WidgetNode s ep
  -> s
  -> ep
  -> [EventResponse s e sp ep]
handleEvent wenv node model evt = [Report evt]