threepenny-editors-0.5.2: src/Graphics/UI/Threepenny/Editors.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -Wno-orphans #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}
{-# OPTIONS_GHC -Wno-duplicate-exports #-}
module Graphics.UI.Threepenny.Editors
( -- * Widgets
GenericWidget(..)
, edited
, contents
, widgetControl
, widgetTidings
-- * Editors
, Editor(Horizontally, horizontally, Vertically, vertically)
, someEditor
, create
, lmapE
, dimapE
, Editable(..)
, EditorWidgetFor(..)
, Field
, Usage(..)
-- ** Editor composition
, (|*|), (|*), (*|)
, (-*-), (-*), (*-)
, field
, fieldLayout
, pattern Horizontally
, pattern Vertically
-- ** Editor layout
, withLayout
, withSomeWidget
, construct
-- ** Editor constructors
, editorUnit
, editorIdentity
, editorReadShow
, editorEnumBounded
, editorSelection
, editorSum
, editorJust
-- ** Generic editors
, editorGeneric
, editorGenericSimple
-- * Layouts
, Layout
, above
, beside
-- ** Monoidal layouts
, Vertical(..)
, Horizontal(..)
, Columns(..)
-- ** Type level layouts
, type (|*|)(..)
, type (-*-)(..)
-- ** Layout manipulation
, Renderable(..)
) where
import Data.Biapplicative
import Data.Char
import Data.Default
import Data.Functor.Compose
import Data.Functor.Identity
import Data.Maybe
import Generics.SOP hiding (Compose)
import Graphics.UI.Threepenny.Core as UI
import Graphics.UI.Threepenny.Widgets
import Text.Casing
import Graphics.UI.Threepenny.Editors.Layout
import Graphics.UI.Threepenny.Editors.Types
-- | The class of 'Editable' datatypes.
-- .
-- Define your own instance by using the 'Applicative' composition operators or
-- derive it via 'Generics.SOP'.
class Renderable (EditorWidget a) => Editable a where
type family EditorWidget a
type EditorWidget a = Layout
-- | The editor factory
editor :: Editor a (EditorWidget a) a
default editor :: (Generic a, HasDatatypeInfo a, (All (All Editable `And` All Default) (Code a)), EditorWidget a ~ Layout) => Editor a (EditorWidget a) a
editor = editorGeneric
-- | Conceal the widget type of some 'Editor'
withSomeWidget :: Renderable w => Editor a w b -> Editor a Layout b
withSomeWidget = first getLayout
-- | A version of 'editor' with a concealed widget type.
someEditor :: Editable a => Editor a Layout a
someEditor = withSomeWidget editor
-- | A container for 'EditorWidget'.
data EditorWidgetFor a where
EditorWidgetFor :: Editable a => EditorWidget a -> EditorWidgetFor a
-- | 'Usage' is a kind for type level 'Field's
data Usage = Value | Edit
-- | Type level fields. Use this helper to define EditorWidget types. Example:
--
-- > data PersonF (usage :: Usage) = Person
-- > { education :: Field usage Education
-- > , firstName, lastName :: Field usage String
-- > , age :: Field usage (Maybe Int)
--
-- > type Person = PersonF Value
-- > type PersonEditor = PersonF Edit
type family Field (usage :: Usage) a where
Field 'Value a = a
Field 'Edit a = EditorWidget a
instance Editable () where
type EditorWidget () = Element
editor = editorUnit
instance a ~ Char => Editable [a] where
type EditorWidget [a] = TextEntry
editor = editorString
instance Editable Bool where
type EditorWidget Bool = Element
editor = editorCheckBox
instance Editable (Maybe Int) where
type EditorWidget (Maybe Int) = TextEntry
editor = editorReadShow
instance Editable (Maybe Double) where
type EditorWidget (Maybe Double) = TextEntry
editor = editorReadShow
instance Editable Int where
type EditorWidget Int = TextEntry
editor = editorJust editor
instance Editable Double where
type EditorWidget Double = TextEntry
editor = editorJust editor
instance (Editable a, Editable b) => Editable (a,b) where
type EditorWidget (a,b) = EditorWidget a |*| EditorWidget b
editor = bipure (:|*|) (,) <<*>> lmapE fst editor <<*>> lmapE snd editor
instance Editable a => Editable (Identity a) where
type EditorWidget (Identity a) = EditorWidget a
editor = editorIdentity editor
{--------------------------------------------
Generic derivations
---------------------------------------------}
-- | A generic editor for record types.
editorGenericSimple
:: forall a xs.
(Generic a, HasDatatypeInfo a, All Editable xs, Code a ~ '[xs])
=> Editor a Layout a
editorGenericSimple = dimapE from to $ editorGenericSimple' (datatypeInfo(Proxy @ a))
editorGenericSimple'
:: forall xs.
(All Editable xs)
=> DatatypeInfo '[xs] -> Editor (SOP I '[xs]) Layout (SOP I '[xs])
editorGenericSimple' (ADT _ _ (c :* Nil)) = constructorEditorFor c
editorGenericSimple' (Newtype _ _ c) = constructorEditorFor c
constructorEditorFor
:: (All Editable xs)
=> ConstructorInfo xs
-> Editor (SOP I '[xs]) Layout (SOP I '[xs])
constructorEditorFor (Record _ fields) = dimapE (unZ . unSOP) (SOP . Z) $ constructorEditorFor' fields
constructorEditorFor (Constructor _) = dimapE (unZ . unSOP) (SOP . Z) someEditor
constructorEditorFor Infix{} = dimapE (unZ . unSOP) (SOP . Z) someEditor
-- | A generic editor for SOP types.
editorGeneric
:: forall a .
(Generic a, HasDatatypeInfo a, (All (All Editable `And` All Default) (Code a)))
=> Editor a Layout a
editorGeneric = dimapE from to $ editorGeneric' (datatypeInfo(Proxy @ a))
editorGeneric'
:: forall xx.
(All (All Editable `And` All Default) xx)
=> DatatypeInfo xx -> Editor (SOP I xx) Layout (SOP I xx)
editorGeneric' (ADT _ _ (c :* Nil)) = constructorEditorFor c
editorGeneric' (ADT _ _ cc) = editorSum above editors constructor where
editors :: [(Tag, Editor (SOP I xx) Layout (SOP I xx))]
editors = first Tag <$> constructorEditorsFor cc
constructors = hmap (K . constructorName) cc
constructor a = Tag $ hcollapse $ hliftA2 const constructors (unSOP a)
editorGeneric' (Newtype _ _ c) = constructorEditorFor c
newtype Tag = Tag String deriving (Eq, Ord)
instance Show Tag where show (Tag t) = init $ toFieldLabel t
constructorEditorsFor
:: forall xx . (All (All Editable `And` All Default) xx)
=> NP ConstructorInfo xx -> [(String, Editor (SOP I xx) Layout (SOP I xx))]
constructorEditorsFor cc =
hcollapse $ hcliftA3 p (\c i p -> (constructorName c,) `mapKK` constructorEditorForUnion c i p) cc
(injections :: NP (Injection (NP I) xx) xx)
(projections :: NP (Projection (Compose Maybe (NP I)) xx) xx)
where
p = Proxy @ (All Editable `And` All Default)
constructorEditorForUnion
:: (SListI xx, All Editable xs, All Default xs)
=> ConstructorInfo xs
-> Injection (NP I) xx xs
-> Projection (Compose Maybe (NP I)) xx xs
-> K (Editor (SOP I xx) Layout (SOP I xx)) xs
constructorEditorForUnion (Constructor _) inj prj = K $ composeEditor inj prj editor
constructorEditorForUnion Infix{} inj prj = K $ composeEditor inj prj editor
constructorEditorForUnion (Record _ fields) inj prj = K $ composeEditor inj prj $ constructorEditorFor' fields
composeEditor
:: forall xss xs.
(SListI xss, All Default xs) =>
Injection (NP I) xss xs
-> Projection (Compose Maybe (NP I)) xss xs
-> Editor (NP I xs) Layout (NP I xs)
-> Editor (SOP I xss) Layout (SOP I xss)
composeEditor (Fn inj) (Fn prj) = dimapE f (SOP . unK . inj)
where
f :: SOP I xss -> NP I xs
f = fromMaybe def . getCompose . prj . K . hexpand (Compose Nothing) . hmap (Compose . Just) . unSOP
constructorEditorFor' :: (SListI xs, All Editable xs) => NP FieldInfo xs -> Editor (NP I xs) Layout (NP I xs)
constructorEditorFor' fields = vertically $ hsequence $ hliftA Vertically $ fieldsEditor (hliftA (K . fieldName) fields)
-- | Tuple editor without fields
instance All Editable xs => Editable (NP I xs) where
type EditorWidget (NP I xs) = Layout
editor = horizontally $ hsequence $ hliftA Horizontally tupleEditor
tupleEditor :: forall xs . All Editable xs => NP (Editor (NP I xs) Layout) xs
tupleEditor = go id sList where
go :: forall ys. All Editable ys => (forall f . NP f xs -> NP f ys) -> SList ys -> NP (Editor (NP I xs) Layout) ys
go _ SNil = Nil
go f SCons = lmapE (unI . hd . f) someEditor :* go (tl . f) sList
fieldsEditor :: forall xs . All Editable xs => NP (K String) xs -> NP (Editor (NP I xs) Layout) xs
fieldsEditor = go id sList where
go :: forall ys. All Editable ys => (forall f . NP f xs -> NP f ys) -> SList ys -> NP (K String) ys -> NP (Editor (NP I xs) Layout) ys
go _ SNil Nil = Nil
go f SCons (K fn :* xs) = field (toFieldLabel fn) (unI . hd . f) someEditor :* go (tl . f) sList xs
toFieldLabel :: String -> String
toFieldLabel (fromAny -> Identifier (x:xx)) =
unwords (onHead toUpper x : fmap (onHead toLower) xx) ++ ":"
where
onHead f (x:xx) = f x : xx
onHead _ [] = []
toFieldLabel _ = ""
instance (Applicative f, All Default xs) => Default (NP f xs) where
def = hcpure (Proxy @ Default) (pure def)