threepenny-editors
Composable algebraic editors
https://github.com/pepeiborra/threepenny-editors
Version on this page: | 0.4.1 |
LTS Haskell 11.22: | 0.5.6 |
Stackage Nightly 2018-03-12: | 0.5.6 |
Latest on Hackage: | 0.5.6.1 |
threepenny-editors-0.4.1@sha256:5abbcb87f06915c17a40974cf539d8d19eb33c35307190bcfd8ac9afa02c75ed,1877
Module documentation for 0.4.1
- Graphics
- Graphics.UI
threepenny-editors
Introduction
A library allowing to easily create threepenny-gui widgets for editing algebraic datatypes.
The library provides a set of editors for primitive and base types, a set of editor
constructors for building editors for type constructors, and a set of combinators for
composing editors - the EditorFactory
type has an Applicative
-like structure, with two
combinators for horizontal and vertical composition, as well as
a Profunctor
instance. Don’t worry if you are not familiar with these concepts as they are
not required to perform simple tasks with this library.
newtype EditorFactory a b
instance Profunctor EditorFactory
(|*|) :: EditorFactory s (b->a) -> EditorFactory s b -> EditorFactory s a
(-*-) :: EditorFactory s (b->a) -> EditorFactory s b -> EditorFactory s a
The library also provides an Editable
type class to associate a default EditorFactoy
with
a type:
class Editable a where
editor :: EditorFactory a a
Example
Let’s start with something simple, obtaining an EditorFactory
for a newtype:
newtype Brexiteer = Brexiteer {unBrexiteer::Bool} deriving (Bounded, Enum, Eq, Read, Show, Ord, Generic)
Since we already have an Editable
instance for Bool
that displays a checkbox,
we can obtain an Editable
instance for Brexiteer
for free:
deriving instance Editable Brexiteer
We can also wrap the existing Bool
editor manually if we want to using dimap
:
editorBrexiteer = dimap unBrexiteer Brexiteer (editor :: Editor Bool Bool)
The type annotation above is only for illustrative purposes.
Perhaps we are not happy with the default checkbox editor and want to have a different UI? The code below shows how to use a textbox instead:
editorBrexiteerText :: EditorFactory Brexiteer Brexiteer
editorBrexiteerText = editorReadShow
Or a combo box:
editorBrexiteerChoice :: EditorFactoy Brexiteer Brexiteer
editorBrexiteerChoice = editorEnumBounded
Let’s move on to a union type now:
data Education
= Basic
| Intermediate
| Other_ String
deriving (Eq, Read, Show)
We could define an editor for Education
with editorReadShow
, but maybe we want a more user
friendly UI that displays a choice of education type, and only in the Other
case a free form
text input. The editorSum
combinator takes a list of choices and an editor for each choice:
editorEducation :: EditorFactory Education Education
editorEducation = do
let selector x = case x of
Other _ -> "Other"
_ -> show x
editorSum
[ ("Basic", const Basic <$> editorUnit)
, ("Intermediate", const Intermediate <$> editorUnit)
, ("Other", dimap (fromMaybe "" . getOther) Other editor)
]
selector
getOther :: Education -> Maybe String
getOther (Other s) = Just s
getOther _ = Nothing
Or more simply, we could just use editorGeneric
to achieve the same effect, provided that
Education
has got SOP.Generic and SOP.HasDatatypeInfo instances
import GHC.Generics
import qualified Generics.SOP as SOP
deriving instance Generic Education
instance SOP.HasDatatypeInfo Education
instance SOP.Generic Education
-- Derive an Editable instance that uses editorGeneric
instance Editable Education
-- Explicitly call editorGeneric
editorEducation :: EditorFactory Education Education
editorEducation = editorGeneric
Moving on to a record type, let’s look at how to compose multiple editors together:
data Person = Person
{ education :: Education
, firstName, lastName :: String
, age :: Maybe Int
, brexiteer :: Brexiteer
, status :: LegalStatus
}
deriving (Generic, Show)
The field
combinator encapsulates the common pattern of pairing a label and a base editor
to build the editor for a record field:
field :: String -> (out -> inn) -> EditorFactory inn a -> EditorFactory out a
field name f e = string name *| lmap f e
Where *|
prepends a UI Element to an Editor horizontally:
(*|) :: UI Element -> EditorFactory s a -> EditorFactory s a
Armed with field
and applicative composition (vertical ‘--’ and horizontal ’||’),
we define the editor for Person
almost mechanically:
editorPerson :: EditorFactory Person Person
editorPerson =
(\fn ln a e ls b -> Person e fn ln a b ls)
<$> field "First:" firstName editor
-*- field "Last:" lastName editor
-*- field "Age:" age editor
-*- field "Education:" education editorEducation
-*- field "Status" status (editorJust $ editorSelection (pure [minBound..]) (pure (string.show)))
-*- field "Brexiter" brexiteer editor
The only bit of ingenuity in the code above is the deliberate reordering of the fields.
It is also possible to generically derive the editor for person in the same way as before, in which case the labels are taken from the field names, and the order from the declaration order.
Changes
0.4.1 (2017-07-13)
* Improved the rendering of constructors in generic sum editors
0.4.0 (2017-07-13)
* Fixed a bug in the layout engine
* Dropped editorDefSetup
* Exposed the Layout primitives
0.3.0 (2017-07-13)
* Version bump required to comply with the Haskell PvP, as the recent layout changes
were breaking changes due to deleted constructors.
0.2.0.16 (2017-07-10)
* Improvements to the layout engine to always produce a grid. Experimental.
0.2.0.15 (2017-07-10)
* Expose Vertically and Horizontally for use with ApplicativeDo
0.2.0.14 (2017-07-01)
* Improved rendering of field names in generic editors.
0.2.0.13 (2017-06-22)
* Added `liftEditor` to expose the underlying `Element` of an editor.
This enables setting attributes in the element, including class and id.
0.2.0.12 (2017-06-21)
* Export `EditorDef` and `EditorFactory` constructors to allow for
wrapping of custom controls
0.2.0.11 (2017-06-10)
* Documentation only release.
0.2.0.10 (2017-05-23)
* Nested grids. All layouts are now grid based.
0.2.0.9 (2017-05-23)
* Detect grid layouts and render them accordingly
0.2.0.8 (2017-05-21)
* Bug fixes
0.2.0.7 (2017-05-20)
* Added `editorSelection`.
0.2.0.6 (2017-05-15)
* Fix the `Editable` instance for `Identity` and remove reexports.
0.2.0.5 (2017-05-14)
* Add `editorGeneric` and `editorGenericSimple` for types with generics-sop instances.
The latter is only for record and newtypes, whereas the former supports also
Union types, but comes with additional type class constraints.
* Give `Editable` default implementations for generic types.