{-# 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 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 String , age :: Field purpose (Maybe Int) , brexiteer :: Field purpose Brexiteer , status :: Field purpose LegalStatus } deriving (Generic) type Person = PersonF Data type PersonEditor = PersonF Edit defPerson :: Person defPerson = Person Basic "First" "Last" Nothing (Brexiteer False) Single instance Validable Person where validate Person{..} = fromWarnings $ [ "First name cannot be null" | null firstName ] ++ [ "Last name cannot be null" | 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 Editable LegalStatus instance SOP.HasDatatypeInfo LegalStatus instance SOP.Generic LegalStatus data Education = Basic | Intermediate | Other String deriving (Eq, Ord, Read, Show, Generic) getOther :: Education -> Maybe String 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 "" . getOther) Other someEditor) ] selector instance Editable Education instance SOP.HasDatatypeInfo Education instance SOP.Generic Education newtype Brexiteer = Brexiteer Bool deriving (Eq, Show, Ord, Generic) instance Editable Brexiteer where editor = editorGeneric instance SOP.HasDatatypeInfo Brexiteer instance SOP.Generic Brexiteer deriving instance Show Person instance Editable Person -- | 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 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