monomer-1.4.0.0: src/Monomer/Widgets/Singles/TextField.hs
{-|
Module : Monomer.Widgets.Singles.TextField
Copyright : (c) 2018 Francisco Vallarino
License : BSD-3-Clause (see the LICENSE file)
Maintainer : fjvallarino@gmail.com
Stability : experimental
Portability : non-portable
Input field for single line 'Text'. Allows setting the maximum number of
characters and a replacement character for password.
@
textField shortTextLens
@
With configuration options:
@
textField_ shortTextLens [maxLength 100, selectOnFocus_ False]
@
-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE Strict #-}
module Monomer.Widgets.Singles.TextField (
-- * Configuration
TextFieldCfg,
textFieldDisplayChar,
-- * Constructors
textField,
textField_,
textFieldV,
textFieldV_,
textFieldD_
) where
import Control.Applicative ((<|>))
import Control.Lens (ALens')
import Data.Default
import Data.Maybe
import Data.Text (Text)
import qualified Data.Text as T
import Monomer.Core
import Monomer.Core.Combinators
import Monomer.Widgets.Singles.Base.InputField
import qualified Monomer.Lens as L
{-|
Configuration options for textField:
- 'validInput': field indicating if the current input is valid. Useful to show
warnings in the UI, or disable buttons if needed.
- 'resizeOnChange': Whether input causes ResizeWidgets requests.
- 'selectOnFocus': Whether all input should be selected when focus is received.
- 'readOnly': Whether to prevent the user changing the input text.
- 'maxLength': the maximum length of input text.
- 'textFieldDisplayChar': the character that will be displayed as replacement of
the real text. Useful for password fields.
- 'onFocus': event to raise when focus is received.
- 'onFocusReq': 'WidgetRequest' to generate when focus is received.
- 'onBlur': event to raise when focus is lost.
- 'onBlurReq': 'WidgetRequest' to generate when focus is lost.
- 'onChange': event to raise when the value changes.
- 'onChangeReq': 'WidgetRequest' to generate when the value changes.
-}
data TextFieldCfg s e = TextFieldCfg {
_tfcCaretWidth :: Maybe Double,
_tfcCaretMs :: Maybe Millisecond,
_tfcDisplayChar :: Maybe Char,
_tfcPlaceholder :: Maybe Text,
_tfcValid :: Maybe (WidgetData s Bool),
_tfcValidV :: [Bool -> e],
_tfcMaxLength :: Maybe Int,
_tfcResizeOnChange :: Maybe Bool,
_tfcSelectOnFocus :: Maybe Bool,
_tfcReadOnly :: Maybe Bool,
_tfcOnFocusReq :: [Path -> WidgetRequest s e],
_tfcOnBlurReq :: [Path -> WidgetRequest s e],
_tfcOnChangeReq :: [Text -> WidgetRequest s e]
}
instance Default (TextFieldCfg s e) where
def = TextFieldCfg {
_tfcCaretWidth = Nothing,
_tfcCaretMs = Nothing,
_tfcDisplayChar = Nothing,
_tfcPlaceholder = Nothing,
_tfcValid = Nothing,
_tfcValidV = [],
_tfcMaxLength = Nothing,
_tfcResizeOnChange = Nothing,
_tfcSelectOnFocus = Nothing,
_tfcReadOnly = Nothing,
_tfcOnFocusReq = [],
_tfcOnBlurReq = [],
_tfcOnChangeReq = []
}
instance Semigroup (TextFieldCfg s e) where
(<>) t1 t2 = TextFieldCfg {
_tfcCaretWidth = _tfcCaretWidth t2 <|> _tfcCaretWidth t1,
_tfcCaretMs = _tfcCaretMs t2 <|> _tfcCaretMs t1,
_tfcDisplayChar = _tfcDisplayChar t2 <|> _tfcDisplayChar t1,
_tfcPlaceholder = _tfcPlaceholder t2 <|> _tfcPlaceholder t1,
_tfcValid = _tfcValid t2 <|> _tfcValid t1,
_tfcValidV = _tfcValidV t1 <> _tfcValidV t2,
_tfcMaxLength = _tfcMaxLength t2 <|> _tfcMaxLength t1,
_tfcResizeOnChange = _tfcResizeOnChange t2 <|> _tfcResizeOnChange t1,
_tfcSelectOnFocus = _tfcSelectOnFocus t2 <|> _tfcSelectOnFocus t1,
_tfcReadOnly = _tfcReadOnly t2 <|> _tfcReadOnly t1,
_tfcOnFocusReq = _tfcOnFocusReq t1 <> _tfcOnFocusReq t2,
_tfcOnBlurReq = _tfcOnBlurReq t1 <> _tfcOnBlurReq t2,
_tfcOnChangeReq = _tfcOnChangeReq t1 <> _tfcOnChangeReq t2
}
instance Monoid (TextFieldCfg s e) where
mempty = def
instance CmbCaretWidth (TextFieldCfg s e) Double where
caretWidth w = def {
_tfcCaretWidth = Just w
}
instance CmbCaretMs (TextFieldCfg s e) Millisecond where
caretMs ms = def {
_tfcCaretMs = Just ms
}
instance CmbPlaceholder (TextFieldCfg s e) Text where
placeholder value = def {
_tfcPlaceholder = Just value
}
instance CmbValidInput (TextFieldCfg s e) s where
validInput field = def {
_tfcValid = Just (WidgetLens field)
}
instance CmbValidInputV (TextFieldCfg s e) e where
validInputV fn = def {
_tfcValidV = [fn]
}
instance CmbResizeOnChange (TextFieldCfg s e) where
resizeOnChange_ resize = def {
_tfcResizeOnChange = Just resize
}
instance CmbSelectOnFocus (TextFieldCfg s e) where
selectOnFocus_ sel = def {
_tfcSelectOnFocus = Just sel
}
instance CmbReadOnly (TextFieldCfg s e) where
readOnly_ ro = def {
_tfcReadOnly = Just ro
}
instance CmbMaxLength (TextFieldCfg s e) where
maxLength len = def {
_tfcMaxLength = Just len
}
instance WidgetEvent e => CmbOnFocus (TextFieldCfg s e) e Path where
onFocus fn = def {
_tfcOnFocusReq = [RaiseEvent . fn]
}
instance CmbOnFocusReq (TextFieldCfg s e) s e Path where
onFocusReq req = def {
_tfcOnFocusReq = [req]
}
instance WidgetEvent e => CmbOnBlur (TextFieldCfg s e) e Path where
onBlur fn = def {
_tfcOnBlurReq = [RaiseEvent . fn]
}
instance CmbOnBlurReq (TextFieldCfg s e) s e Path where
onBlurReq req = def {
_tfcOnBlurReq = [req]
}
instance WidgetEvent e => CmbOnChange (TextFieldCfg s e) Text e where
onChange fn = def {
_tfcOnChangeReq = [RaiseEvent . fn]
}
instance CmbOnChangeReq (TextFieldCfg s e) s e Text where
onChangeReq req = def {
_tfcOnChangeReq = [req]
}
-- | Replacement character to show instead of real text. Useful for passwords.
textFieldDisplayChar :: Char -> TextFieldCfg s e
textFieldDisplayChar char = def {
_tfcDisplayChar = Just char
}
-- | Creates a text field using the given lens.
textField :: WidgetEvent e => ALens' s Text -> WidgetNode s e
textField field = textField_ field def
-- | Creates a text field using the given lens. Accepts config.
textField_
:: WidgetEvent e => ALens' s Text -> [TextFieldCfg s e] -> WidgetNode s e
textField_ field configs = textFieldD_ (WidgetLens field) configs
-- | Creates a text field using the given value and 'onChange' event handler.
textFieldV :: WidgetEvent e => Text -> (Text -> e) -> WidgetNode s e
textFieldV value handler = textFieldV_ value handler def
-- | Creates a text field using the given value and 'onChange' event handler.
-- Accepts config.
textFieldV_
:: WidgetEvent e => Text -> (Text -> e) -> [TextFieldCfg s e] -> WidgetNode s e
textFieldV_ value handler configs = textFieldD_ widgetData newConfig where
widgetData = WidgetValue value
newConfig = onChange handler : configs
-- | Creates a text field providing a 'WidgetData' instance and config.
textFieldD_
:: WidgetEvent e => WidgetData s Text -> [TextFieldCfg s e] -> WidgetNode s e
textFieldD_ widgetData configs = inputField where
config = mconcat configs
fromText = textToText (_tfcMaxLength config)
inputConfig = InputFieldCfg {
_ifcPlaceholder = _tfcPlaceholder config,
_ifcInitialValue = "",
_ifcValue = widgetData,
_ifcValid = _tfcValid config,
_ifcValidV = _tfcValidV config,
_ifcFromText = fromText,
_ifcToText = id,
_ifcAcceptInput = acceptInput (_tfcMaxLength config),
_ifcIsValidInput = acceptInput (_tfcMaxLength config),
_ifcDefCursorEnd = True,
_ifcDefWidth = 100,
_ifcCaretWidth = _tfcCaretWidth config,
_ifcCaretMs = _tfcCaretMs config,
_ifcDisplayChar = _tfcDisplayChar config,
_ifcResizeOnChange = fromMaybe False (_tfcResizeOnChange config),
_ifcSelectOnFocus = fromMaybe False (_tfcSelectOnFocus config),
_ifcReadOnly = fromMaybe False (_tfcReadOnly config),
_ifcStyle = Just L.textFieldStyle,
_ifcWheelHandler = Nothing,
_ifcDragHandler = Nothing,
_ifcDragCursor = Nothing,
_ifcOnFocusReq = _tfcOnFocusReq config,
_ifcOnBlurReq = _tfcOnBlurReq config,
_ifcOnChangeReq = _tfcOnChangeReq config
}
inputField = inputField_ "textField" inputConfig
textToText :: Maybe Int -> Text -> Maybe Text
textToText Nothing text = Just text
textToText (Just len) text
| T.length text <= len = Just text
| otherwise = Nothing
acceptInput :: Maybe Int -> Text -> Bool
acceptInput Nothing _ = True
acceptInput (Just len) text = T.length text <= len