UI layout system, as simple and minimal as possible, inspired by Clay (C Layout) from Nic Baker: YouTube Video.
No text measurement (since the library is not bound to any UI engine, web or server) and so no auto-fit support yet.
Quick overview with test/Demo module running: as YT shorts.
Constructor Demo: https://shamansir.github.io/purescript-play/constructor.html
Pursuit Page: https://pursuit.purescript.org/packages/purescript-play/
Just use nix develop in the repository and everything should work.
Menu with items:
import Play (Play, (~*))
import Play as Play
menuUI =
Play.i "menu"
~* Play.width 250.0
~* Play.heightFit
~* (Play.padding $ Play.all 5.0)
~* Play.topToBottom
~* Play.childGap 5.0
~* Play.with (menuItem <$> [ "Copy", "Paste", "Delete", "Spell Check", "Dictionary", "Comment" ])
:: Play String
menuItem :: String -> Play Item
menuItem itemName =
Play.i ("item-" <> itemName)
~* Play.widthGrow
~* Play.heightFit
~* Play.leftToRight
~* (Play.padding $ Play.all 3.0)
~* Play.with
[ Play.i itemName
~* Play.widthGrow
~* Play.height 60.0
, Play.i (ic (HA.RGB 94 64 157) "icon")
~* Play.width 60.0
~* Play.height 60.0
]Noodle Horizontal Node UI:
-- surely you can use sum-type here instead
data NItem = NItem Color String
ic :: Color -> String -> NItem
ic = NItem
noodleHorzNodeUI :: Play NItem
noodleHorzNodeUI =
let
titleWidth = 30.0
channelsHeight = 25.0
bodyWidth = 700.0
bodyHeight = 120.0
channelWidth = 70.0
connectorWidth = 15.0
inletsCount = 5
outletsCount = 7
inlet n =
Play.i (ic (HA.Named "transparent") "")
~* Play.width channelWidth
~* Play.heightGrow
~* Play.with
[ Play.i (ic (HA.RGB 83 105 7) "connector")
~* Play.width connectorWidth
~* Play.heightGrow
, Play.i (ic (HA.RGB 175 48 41) $ show n <> " inlet")
~* Play.widthGrow
~* Play.heightGrow
]
inlets = inlet <$> Array.range 0 inletsCount
outlet n =
Play.i (ic (HA.Named "transparent") "")
~* Play.width channelWidth
~* Play.heightGrow
~* Play.with
[ Play.i (ic (HA.RGB 83 105 7) "connector")
~* Play.width connectorWidth
~* Play.heightGrow
, Play.i (ic (HA.RGB 175 48 41) $ show n <> " outlet")
~* Play.widthGrow
~* Play.heightGrow
]
outlets = outlet <$> Array.range 0 outletsCount
in Play.i (ic (HA.Named "blue") "background")
~* Play.widthFit
~* Play.heightFit
~* Play.leftToRight
~* Play.with
[ Play.i (ic (HA.Named "magenta") "title + paddings")
~* Play.width titleWidth
~* Play.heightFit
~* Play.topToBottom
~* Play.with
[ Play.i (ic (HA.RGB 32 94 166) "padding top")
~* Play.widthGrow
~* Play.height channelsHeight
, Play.i (ic (HA.Named "black") "title")
~* Play.widthGrow
~* Play.height bodyHeight
, Play.i (ic (HA.RGB 32 94 166) "padding bottom")
~* Play.widthGrow
~* Play.height channelsHeight
]
, Play.i (ic (HA.RGB 49 35 78) "") -- inlets + body + outlets
~* Play.widthFit
~* Play.heightFit
~* Play.topToBottom
~* Play.with
[ Play.i (ic (HA.RGB 79 27 57) "inlets")
~* Play.widthFit
~* Play.height channelsHeight
~* Play.with inlets
, Play.i (ic (HA.Named "lightgray") "body bg")
~* Play.widthFitGrow
~* Play.height bodyHeight
~* Play.with
[ Play.i (ic (HA.RGB 90 189 172) "body")
~* Play.width bodyWidth
~* Play.height bodyHeight
]
, Play.i (ic (HA.RGB 79 27 57) "outlets")
~* Play.widthFit
~* Play.height channelsHeight
~* Play.with outlets
]
]Noodle Node UI alternative definition:
data NodePart
= Title
| TitleArea -- Title + Paddings
| ControlButton
| TitlePadding
| Inlet Int InletDefRec
| InletName
| InletConnector
| Outlet Int OutletDefRec
| OutletName
| OutletConnector
| NodeBackground
| BodyArea -- Body + Inlets + Outltets
| Inlets
| Outlets
| BodyBackground
| Body
type NodeParams =
{ inlets :: Array InletDefRec
, outlets :: Array OutletDefRec
, bodyWidth :: Number
, bodyHeight :: Number
}
horzNodeUI :: NodeParams -> Play NodePart
horzNodeUI params =
let
titleWidth = 16.0
channelsHeight = 20.0
channelWidth = 70.0
connectorWidth = 15.0
inlet n def =
Play.i (Inlet n def)
~* Play.width channelWidth
~* Play.heightGrow
~* Play.with
[ Play.i InletConnector
~* Play.width connectorWidth
~* Play.heightGrow
, Play.i InletName
~* Play.widthGrow
~* Play.heightGrow
]
inlets = mapWithIndex inlet params.inlets
outlet n def =
Play.i (Outlet n def)
~* Play.width channelWidth
~* Play.heightGrow
~* Play.with
[ Play.i OutletConnector
~* Play.width connectorWidth
~* Play.heightGrow
, Play.i OutletName
~* Play.widthGrow
~* Play.heightGrow
]
outlets = mapWithIndex outlet params.outlets
in Play.i NodeBackground
~* Play.widthFit
~* Play.heightFit
~* Play.leftToRight
~* Play.with
[ Play.i TitleArea
~* Play.width titleWidth
~* Play.heightFit
~* Play.topToBottom
~* Play.with
[ Play.i ControlButton
~* Play.widthGrow
~* Play.height channelsHeight
, Play.i Title
~* Play.widthGrow
~* Play.height params.bodyHeight
, Play.i TitlePadding
~* Play.widthGrow
~* Play.height channelsHeight
]
, Play.i BodyArea
~* Play.widthFit
~* Play.heightFit
~* Play.topToBottom
~* Play.padding { top : 0.0, left : 5.0, right : 0.0, bottom : 0.0 }
~* Play.with
[ Play.i Inlets
~* Play.widthFit
~* Play.height channelsHeight
~* Play.with inlets
, Play.i BodyBackground
~* Play.widthFitGrow
~* Play.height params.bodyHeight
~* Play.with
[ Play.i Body
~* Play.width params.bodyWidth
~* Play.height params.bodyHeight
]
, Play.i Outlets
~* Play.widthFit
~* Play.height channelsHeight
~* Play.with outlets
]
]
vertNodeUI :: NodeParams -> Play NodePart
vertNodeUI params =
let
titleHeight = 30.0
channelNameMinWidth = 100.0
paddingWidth = channelNameMinWidth + connectorWidth
channelHeight = 20.0
connectorWidth = 15.0
inlet n def =
Play.i (Inlet n def)
~* Play.widthFit
~* Play.heightFit
~* Play.with
[ Play.i InletName
~* Play.width channelNameMinWidth
~* Play.height channelHeight
, Play.i InletConnector
~* Play.width connectorWidth
~* Play.heightGrow
]
inlets = mapWithIndex inlet params.inlets
outlet n def =
Play.i (Outlet n def)
~* Play.widthFit
~* Play.heightFit
~* Play.with
[ Play.i OutletConnector
~* Play.width connectorWidth
~* Play.heightGrow
, Play.i OutletName
~* (Play.width channelNameMinWidth)
~* (Play.height channelHeight)
]
outlets = mapWithIndex outlet params.outlets
in Play.i NodeBackground
~* Play.widthFit
~* Play.heightFit
~* Play.topToBottom
~* Play.with
[ Play.i TitleArea
~* Play.widthFit
~* Play.height titleHeight
~* Play.leftToRight
~* Play.with
[ Play.i TitlePadding
~* Play.width paddingWidth
~* Play.heightGrow
, Play.i Title
~* Play.width params.bodyWidth
~* Play.height titleHeight
, Play.i TitlePadding
~* Play.width paddingWidth
~* Play.heightGrow
]
, Play.i BodyArea
~* Play.widthFit
~* Play.heightFit
~* Play.leftToRight
~* Play.with
[ Play.i Inlets
~* Play.widthFit
~* Play.heightFit
~* Play.topToBottom
~* Play.with inlets
, Play.i BodyBackground
~* Play.width params.bodyWidth
~* Play.heightFitGrow
~* Play.with
[ Play.i Body
~* Play.width params.bodyWidth
~* Play.height params.bodyHeight
]
, Play.i Outlets
~* Play.widthFit
~* Play.heightFit
~* Play.topToBottom
~* Play.with outlets
]
]You may find much more examples in Test.Demo.Examples source.
Or just take a look at a code that is generated with Constructor.
To convert your UI definition to the layout definition, use Play.layout function:
menuLayout = Play.layout menuUI :: Play.Layout String
noodleHorzNodeLayout = Play.layout noodleHorzUI :: Play.Layout NItemThe Layout a can be converted to an array of items with positions Array (Play.WithRect a) with Play.flattenLayout,
or you may preferPlay.layoutToTree to get a Tree (Play.WithRect a) if you want to keep all the parent-child relations
(we use Yoga.Tree to represent them here).
menuPositionedItems = Play.flattenLayout menuLayout :: Array (Play.WithRect String)
menuPositionedTree = Play.layoutToTree menuLayout :: Yoga.Tree (Play.WithRect String)
noodleHorzNodeItems = Play.flattenLayout noodleHorzNodeLayout :: Array (Play.WithRect NItem)
noodleHorzNodeTree = Play.layoutToTree noodleHorzNodeLayout :: Yoga.Tree (Play.WithRect NItem)