{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}
{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-}
module Person (main) where
import Control.Monad
import Data.Maybe
import Data.Text (Text)
import qualified Data.Text as T
import qualified Generics.SOP as SOP
import Generics.SOP.TH
import GHC.Generics (Generic)
import Graphics.UI.Threepenny.Core
import Graphics.UI.Threepenny.Editors
import Graphics.UI.Threepenny.Editors.Layout (above, beside)
import Graphics.UI.Threepenny.Editors.Validation
import Graphics.UI.Threepenny.Elements
import Prelude hiding (span)
main :: IO ()
main = startGUI defaultConfig setup
-- | A dual purpose data type that doubles as a value and as a widget depending on the type argument.
data PersonF (purpose :: Purpose) = Person
{ education :: Field purpose Education
, firstName, lastName :: Field purpose Text
, age :: Field purpose (Maybe Int)
, brexiteer :: Field purpose Brexiteer
, status :: Field purpose LegalStatus
}
deriving (Generic)
type Person = PersonF Data
type PersonEditor = PersonF Edit
instance Validable Person where
validate Person{..} = fromWarnings $
[ "First name cannot be null" | T.null firstName ] ++
[ "Last name cannot be null" | T.null lastName ] ++
[ "Age must be a natural number" | Just x <- [age], x <= 0]
data LegalStatus
= Single
| Married
| Divorced
| Widowed
deriving (Bounded, Enum, Eq, Ord, Show, Generic)
instance HasEmpty LegalStatus
instance Editable LegalStatus
instance SOP.HasDatatypeInfo LegalStatus
instance SOP.Generic LegalStatus
data Education
= Basic
| Intermediate
| Other Text
deriving (Eq, Ord, Read, Show, Generic)
getOther :: Education -> Maybe Text
getOther (Other s) = Just s
getOther _ = Nothing
-- | A manually defined editor for 'Education'.
-- It is also possible to derive this 'Editor' via Generics.SOP, as done below.
editorEducation :: Editor Education Layout Education
editorEducation = do
let selector x = case x of
Other _ -> "Other"
_ -> show x
editorSum beside
[ ("Basic", const Basic <$> withSomeWidget editorUnit)
, ("Intermediate", const Intermediate <$> withSomeWidget editorUnit)
, ("Other", dimapE (fromMaybe mempty . getOther) Other someEditor)
]
selector
instance HasEmpty Education
instance Editable Education
instance SOP.HasDatatypeInfo Education
instance SOP.Generic Education
newtype Brexiteer = Brexiteer Bool deriving (Eq, Show, Ord, Generic)
instance HasEmpty Brexiteer
instance Editable Brexiteer where editor = editorGeneric
instance SOP.HasDatatypeInfo Brexiteer
instance SOP.Generic Brexiteer
deriving instance Show Person
instance HasEmpty Person
instance Editable Person where editor = editorGeneric
-- | An editor for 'Person' values that uses the 'Columns' layout builder
editorPersonColumns :: Editor Person Columns Person
editorPersonColumns = do
firstName <- fieldLayout Next "First:" firstName editor
lastName <- fieldLayout Next "Last:" lastName editor
age <- fieldLayout Next "Age:" age editor
education <- fieldLayout Break "Education:" education editorEducation
status <- fieldLayout Next "Status" status (editorJust $ editorSelection (pure [minBound..]) (pure (string.show)))
brexiteer <- fieldLayout Next "Brexiter" brexiteer editor
return Person{..}
-- | A editor for 'Person' values with a fully fledged Widget type.
-- The UI and layout are defined in the 'Renderable' instance for the widget.
personEditor :: Editor Person PersonEditor Person
personEditor = editorGenericBi
instance Renderable PersonEditor where
getLayout Person{..} =
( ("First: " ||| firstName) ===
("Last: " ||| lastName) ===
("Status: " ||| status)
) |||
(("Age:" ||| age) ===
("Brexiteer: " ||| brexiteer) ===
("Education: " ||| education))
where
a ||| b = getLayout a `beside` getLayout b
a === b = getLayout a `above` getLayout b
-- -------
-- Driver
defPerson :: Person
defPerson = Person Basic (T.pack "First") (T.pack "Last") Nothing (Brexiteer False) Single
setup :: Window -> UI ()
setup w = void $ mdo
_ <- return w # set title "Threepenny editors example"
person1C <- create editorPersonColumns person1B
person2 <- create editorGeneric person1B
person3e <- create personEditor person1B
-- When using a biapplicative editor, we can set the attributes of the field editors after creation.
_ <- element (firstName (widgetControl person3e)) # set style [("background-color", "Blue")]
person1B <- accumB defPerson (updateIfValid . head <$> unions
[ edited person1C
, edited person2
, edited person3e
])
-- We can attach validation to any editor
validation <-
stepper ok (validate . head <$>
unions [ edited person1C
, edited person2
, edited person3e])
getBody w #+ [grid
[ [span # sink text (show <$> validation) # set style [("color", "red")]]
, [render person1C]
, [hr]
, [render person2]
, [hr]
, [render person3e]
, [hr]
]]
deriveGeneric ''PersonF