packages feed

monomer-1.4.0.0: src/Monomer/Widgets/Containers/Base/LabeledItem.hs

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

Container for items with an associated clickable label. Mainly used with radio
and checkbox.

For usage examples, see:

- "Monomer.Widgets.Singles.LabeledCheckbox"
- "Monomer.Widgets.Singles.LabeledRadio"
-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}

module Monomer.Widgets.Containers.Base.LabeledItem (
  labeledItem
) where

import Control.Applicative ((<|>))
import Data.Default
import Control.Lens ((&), (^.), (^?), (^?!), (.~), (<>~), ix)
import Data.Maybe
import Data.Sequence ((|>))
import Data.Text (Text)

import qualified Data.Sequence as Seq

import Monomer.Core
import Monomer.Core.Combinators as Cmb

import Monomer.Widgets.Container
import Monomer.Widgets.Containers.Stack
import Monomer.Widgets.Singles.Label
import Monomer.Widgets.Singles.Spacer

import qualified Monomer.Lens as L

{-|
Creates a stack with a label and the provided widget, passing to this widget all
the click events received. Positioning is configurable.
-}
labeledItem
  :: WidgetEvent e
  => WidgetType
  -> RectSide
  -> Maybe Double
  -> Text
  -> LabelCfg s e
  -> WidgetNode s e
  -> WidgetNode s e
labeledItem wtype textSide childSpacing caption labelCfg itemNode = labeledNode where
  widget = makeLabeledItem textSide childSpacing caption labelCfg itemNode
  labeledNode = defaultWidgetNode wtype widget

makeLabeledItem
  :: WidgetEvent e
  => RectSide
  -> Maybe Double
  -> Text
  -> LabelCfg s e
  -> WidgetNode s e
  -> Widget s e
makeLabeledItem textSide childSpacing caption labelCfg itemNode = widget where
  widget = createContainer () def {
    containerInit = init,
    containerMerge = merge,
    containerFilterEvent = filterEvent
  }

  createChildNode wenv node = newNode where
    nodeStyle = node ^. L.info . L.style
    labelStyle = def
      & collectStyleField_ L.text nodeStyle
      & collectStyleField_ L.cursorIcon nodeStyle
    itemStyle = def
      & collectStyleField_ L.fgColor nodeStyle
      & collectStyleField_ L.hlColor nodeStyle
      & collectStyleField_ L.sndColor nodeStyle
      & collectStyleField_ L.cursorIcon nodeStyle

    baseLabel = label_ caption [labelCfg] `styleBasic` [cursorHand]
    labelNode = baseLabel
      & L.info . L.style <>~ labelStyle
    styledNode = itemNode
      & L.info . L.style <>~ itemStyle

    childNode
      | textSide == SideLeft = hstack_ stackCfg [ labelNode, styledNode ]
      | textSide == SideRight = hstack_ stackCfg [ styledNode, labelNode ]
      | textSide == SideTop = vstack_ stackCfg [ labelNode, styledNode ]
      | otherwise = vstack_ stackCfg [ styledNode, labelNode ]
    stackCfg =
      [maybe Cmb.childSpacing Cmb.childSpacing_ childSpacing]
    newNode = node
      & L.children .~ Seq.singleton childNode

  init wenv node = result where
    result = resultNode (createChildNode wenv node)

  merge wenv node oldNode oldState = result where
    result = resultNode (createChildNode wenv node)

  filterEvent :: ContainerFilterHandler s e
  filterEvent wenv node target evt = case evt of
    Click p btn clicks
      | isPointInNodeVp labelNode p -> Just (newPath, newEvt) where
        newEvt = Click targetCenter btn clicks

    ButtonAction p btn BtnPressed clicks
      | isPointInNodeVp labelNode p -> Just (newPath, newEvt) where
        newEvt = ButtonAction targetCenter btn BtnPressed clicks

    _ -> Just (target, evt)
    where
      labelIdx
        | textSide `elem` [SideLeft, SideTop] = 0
        | otherwise = 1
      targetIdx = 1 - labelIdx
      newPath = Seq.take (length target - 1) target |> targetIdx
      labelNode = node ^. L.children . ix 0 . L.children ^?! ix labelIdx
      targetNode = node ^. L.children . ix 0 . L.children ^?! ix targetIdx
      targetCenter = rectCenter (targetNode ^. L.info . L.viewport)