{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}
{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-}
import Control.Monad
import Data.Biapplicative
import Data.Default
import Data.Maybe
import qualified Generics.SOP as SOP
import GHC.Generics
import Graphics.UI.Threepenny.Core
import Graphics.UI.Threepenny.Editors
import Graphics.UI.Threepenny.Editors.Types
import Graphics.UI.Threepenny.Elements
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 (usage :: Usage) = Person
{ education :: Field usage Education
, firstName, lastName :: Field usage String
, age :: Field usage (Maybe Int)
, brexiteer :: Field usage Brexiteer
, status :: Field usage LegalStatus
}
deriving (Generic)
type Person = PersonF Value
type PersonEditor = PersonF Edit
data LegalStatus
= Single
| Married
| Divorced
| Widowed
deriving (Bounded, Enum, Eq, Ord, Show, Generic)
instance Editable LegalStatus
instance SOP.HasDatatypeInfo LegalStatus
instance SOP.Generic LegalStatus
instance Default LegalStatus where def = Single
data Education
= Basic
| Intermediate
| Other String
deriving (Eq, Ord, Read, Show, Generic)
instance Default Education where def = Basic
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 :: EditorFactory 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", dimapEF (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 Default Brexiteer where def = Brexiteer False
instance Editable Brexiteer where editor = editorGenericSimple
instance SOP.HasDatatypeInfo Brexiteer
instance SOP.Generic Brexiteer
deriving instance Show Person
instance Editable Person
instance SOP.HasDatatypeInfo Person
instance SOP.Generic Person
instance Default Person where def = Person Basic "First" "Last" (Just 18) def def
-- | An editor for 'Person' values that combines the 'Horizontal' and 'Vertical' layout builders
editorPersonHV :: EditorFactory Person Vertical Person
editorPersonHV = do
(firstName, lastName) <- withLayout Vertical $ construct $ do
firstName <- fieldLayout Horizontal "First:" firstName editor
lastName <- fieldLayout Horizontal "Last:" lastName editor
return (firstName, lastName)
(age, education) <- withLayout Vertical $ construct $ do
age <- fieldLayout Horizontal "Age:" age editor
education <- fieldLayout Horizontal "Education:" education editorEducation
return (age, education)
(status, brexiteer) <- withLayout Vertical $ construct $ do
status <- fieldLayout Horizontal "Status" status (withSomeWidget $ editorJust $ editorSelection (pure [minBound..]) (pure (string.show)))
brexiteer <- fieldLayout Horizontal "Brexiter" brexiteer editor
return (status, brexiteer)
return Person{..}
-- | An editor for 'Person' values that uses the 'Columns' layout builder
editorPersonColumns :: EditorFactory 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 (withSomeWidget $ 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 :: EditorFactory Person PersonEditor Person
personEditor =
bipure Person Person
<<*>> edit education editor
<<*>> edit firstName editor
<<*>> edit lastName editor
<<*>> edit age editor
<<*>> edit brexiteer editor
<<*>> edit status editor
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"
person1HV <- createEditor editorPersonHV person1B
person1C <- createEditor editorPersonColumns person1B
person2 <- createEditor editorGeneric person1B
person3 <- createEditor personEditor person1B
person1B <- stepper def (head <$> unions
[ edited person1HV
, edited person1C
, edited person2
, edited person3
])
getBody w #+ [grid
[ [return $ _editorElement person1HV]
, [hr]
, [return $ _editorElement person1C]
, [hr]
, [return $ _editorElement person2]
, [hr]
, [return $ _editorElement person3]
, [hr]
]]