ansiform-0.9.0.5: examples/Demo.hs
-- | Interactive gallery for the Ansiform library.
--
-- Run with:
--
-- > cabal run ansiform-demo
module Main where
import Data.Char (isSpace, toLower)
import Data.List (elemIndex)
import Data.Maybe (listToMaybe)
import Ansiform
--------------------------------------------------------------------------------
-- Application
--------------------------------------------------------------------------------
-- Version of this demo, following the package version.
demoVersion :: String
demoVersion = "0.9.0.5"
-- The philosophers shown in the Groups screen.
data Philosopher
= Socrates | Plato | Aristotle | Diogenes | Epicurus
| Cicero | Seneca | Lucretius | MarcusAurelius | Virgil
| Hobbes | Spinoza | Descartes | Locke | Hume
deriving (Eq, Show)
-- The demo is a small screen machine.
data Screen = Menu | List | Groups | GroupsPrompt | Tables | Rich | Article
| Blocks | PhilosopherPage Philosopher
deriving (Eq, Show)
main :: IO ()
main = withImmediateInput $ do
state <- start (screenPage Menu) demoSettings
loop Menu state
loop :: Screen -> State -> IO ()
loop screen state = do
key <- readKeyPress
case fmap toLower key of
Nothing -> finish
Just 'l' -> open List state
Just 'g' -> open Groups state
Just 't' -> open Tables state
Just 'h' -> open Rich state
Just 'a' -> open Article state
Just 'c' -> case screen of
Groups -> choosePhilosopher state
_ -> open Blocks state
Just 'r' -> do state' <- redraw state
loop screen state'
Just 'b' -> open (backScreen screen) state
Just 'x' -> finish
Just '\n' -> loop screen state
Just ' ' -> loop screen state
Just _ -> loop screen state
where
open next st = do
st' <- draw (screenPage next) st
loop next st'
choosePhilosopher st = do
(st', input) <- readForm (screenPage GroupsPrompt)
[promptAt 2 2 promptInputColumn] st
open (chosenScreen (either (const Nothing) listToMaybe input)) st'
chosenScreen input = case input >>= philosopherByNumber . trim of
Just p -> PhilosopherPage p
Nothing -> Groups
finish = putStrLn "\nAnsiform demo finished."
-- Returns to the screen that contains the current one.
backScreen :: Screen -> Screen
backScreen (PhilosopherPage _) = Groups
backScreen _ = Menu
demoSettings :: Settings
demoSettings = settings [minSpace 2]
--------------------------------------------------------------------------------
-- Reusable components
--------------------------------------------------------------------------------
demoTheme :: Theme
demoTheme = defaultTheme
header :: Section
header = sectionTop 1
[ printerSolidLineCenter 0 1 (themeBar demoTheme)
[ blockSimple "Ansiform" (themeHeader demoTheme)
, blockSimple demoVersion (themeHeader demoTheme) ]
, printerEmpty 1
, printerLineCenter 4
[ blockSimple "CLI UI Library" (themeSubtitle demoTheme)
, blockSimple "Gallery" (themeMuted demoTheme)
, blockSimple "Demo" (themeAccent demoTheme) ]
]
bottom :: Section
bottom = sectionRule ruleBottom
footer :: String -> [[Block]] -> Section
footer message commandLines = sectionBodyRule 1 ruleFooter
( [ printerLeft 2 (blockSimple message (themeFooter demoTheme))
, printerEmpty 1
] ++ fmap (printerLineCenter 4) commandLines )
-- Footer with a single prompt line above the command keys.
footerPrompt :: String -> [[Block]] -> Section
footerPrompt message commandLines = sectionBodyRule 1 ruleFooter
( [ printerLeft promptOffset (blockSimple message (themeFooter demoTheme))
, printerEmpty 1
] ++ fmap (printerLineCenter 4) commandLines )
promptOffset :: Offset
promptOffset = 2
promptMessage :: String
promptMessage = "Choose a philosopher by number and press Enter: "
-- The input column is the prompt offset plus its display width.
promptInputColumn :: Offset
promptInputColumn = formNumber (unNumber promptOffset + textWidth promptMessage)
ruleBody, ruleFooter, ruleBottom :: Rule
ruleBody = rule '%' (themeRule demoTheme)
ruleFooter = rule ':' (themeRule demoTheme)
ruleBottom = ruleDefault (themeRule demoTheme)
--------------------------------------------------------------------------------
-- Screens
--------------------------------------------------------------------------------
screenPage :: Screen -> Page
screenPage screen = page [header, screenBody screen, screenFooter screen, bottom]
screenBody :: Screen -> Section
screenBody Menu = sectionBodyRule 1 ruleBody [printerColumn 8 menuBlocks]
screenBody List = sectionBodyRule 1 ruleBody [printerList 8 6 composers]
screenBody Groups = sectionBodyRule 1 ruleBody [printerGroups' 4 groups]
screenBody Tables = sectionBodyRule 1 ruleBody [printerTableFormat' demoTablesFormat 4 1 simpleTable]
screenBody Rich = sectionBodyRule 1 ruleBody [printerTableBordered BordersLight defaultTableFormat 8 0 richTable]
screenBody Article = sectionBodyRule 1 ruleBody [printerArticle 2 0 article]
screenBody Blocks = blocksBody
screenBody (PhilosopherPage p) =
sectionBodyRule 1 ruleBody [printerArticle 2 0 (philosopherArticle p)]
screenBody GroupsPrompt = screenBody Groups
screenFooter :: Screen -> Section
screenFooter Menu = footer "Choose an option above: " menuCommands
screenFooter Groups = footer "Press C to choose a philosopher by number." groupsCommands
screenFooter GroupsPrompt =
footerPrompt promptMessage pageCommands
screenFooter _ = footer "Press B to go back or R to reload." pageCommands
menuCommands, pageCommands, groupsCommands :: [[Block]]
menuCommands =
[ [ blockMenuStyle "L" "List" (themeAccent demoTheme)
, blockMenuStyle "G" "Groups" (themeAccent demoTheme)
, blockMenuStyle "T" "Tables" (themeAccent demoTheme)
, blockMenuStyle "H" "Rich table" (themeAccent demoTheme) ]
, [ blockMenuStyle "A" "Article" (themeAccent demoTheme)
, blockMenuStyle "C" "Content builders" (themeAccent demoTheme)
, blockMenuStyle "X" "Exit" (themeDanger demoTheme) ]
]
pageCommands =
[ [ blockMenuStyle "B" "Back" (themeAccent demoTheme)
, blockMenuStyle "R" "Reload" (themeAccent demoTheme)
, blockMenuStyle "X" "Exit" (themeDanger demoTheme) ]
]
groupsCommands =
[ [ blockMenuStyle "C" "Choose" (themeAccent demoTheme)
, blockMenuStyle "B" "Back" (themeAccent demoTheme)
, blockMenuStyle "R" "Reload" (themeAccent demoTheme) ]
, [ blockMenuStyle "X" "Exit" (themeDanger demoTheme) ]
]
--------------------------------------------------------------------------------
-- Menu and content
--------------------------------------------------------------------------------
menuBlocks :: [Block]
menuBlocks =
[ blockMenuStyle "L" "List composers" (themeAccent demoTheme)
, blockMenuStyle "G" "Group philosophers" (themeAccent demoTheme)
, blockMenuStyle "T" "Tabulate composers" (themeAccent demoTheme)
, blockMenuStyle "H" "Rich flight table" (themeAccent demoTheme)
, blockMenuStyle "A" "Article about Ansiform" (themeAccent demoTheme)
, blockMenuStyle "C" "Content builders" (themeAccent demoTheme)
, blockMenuStyle "X" "Exit" (themeDanger demoTheme)
]
blocksBody :: Section
blocksBody = sectionBodyRule 1 ruleBody
[ printerItem (item "A single Item" (solid cyan))
, printerEmpty 1
, printerColumn 4
[ blockSimple "Content builders" (themeSubtitle demoTheme)
, blockVal "Version" demoVersion cyan
, blockBars ["Left", "Center", "Right"] styleDefault (normal magenta)
, blockSlash "Input" "Output" blue
, blockCycle [ ("Cycle", bold green)
, (" with ", styleDefault)
, ("styles", bold green) ]
, blockSeq ["A", "B", "C"] (normal blue) " -> " (normal black')
, blockAssoc' ["Third", "Second", "First"] styleDefault (normal magenta)
, blockMarked 'k' "marked letter" (bold green)
]
, printerEmpty 1
, printerLeft 4 (blockSimple "left aligned line" (normal green))
, printerCenter 4 (blockSimple "center aligned line" (normal yellow))
, printerRight 4 (blockSimple "right aligned line" (normal cyan))
]
composers :: [Block]
composers =
[ blockEnum "00" "Tchaikovsky" blue
, blockEnum "01" "Sibelius" blue
, blockEnum "02" "Mussorgsky" blue
, blockEnum "03" "Brahms" blue
, blockEnum "04" "Chopin" blue
, blockEnum "05" "Offenbach" blue
, blockEnum "06" "Khachaturian" blue
, blockEnum "07" "Holst" blue
, blockEnum "08" "Bruckner" blue
, blockEnum "09" "Prokofiev" blue
, blockEnum "10" "Rimsky-Korsakov" blue
, blockEnum "11" "Suppe" blue
]
--------------------------------------------------------------------------------
-- Philosophers
--------------------------------------------------------------------------------
philosopherGroups :: [(String, [Philosopher])]
philosopherGroups =
[ ("Greek", [Socrates, Plato, Aristotle, Diogenes, Epicurus])
, ("Roman", [Cicero, Seneca, Lucretius, MarcusAurelius, Virgil])
, ("Enlightenment", [Hobbes, Spinoza, Descartes, Locke, Hume])
]
philosophers :: [Philosopher]
philosophers = concatMap snd philosopherGroups
groups :: [Group]
groups =
[ groupSimple (blockSimple title (normal yellow))
[ philosopherBlock p | p <- ps ]
| (title, ps) <- philosopherGroups ]
philosopherBlock :: Philosopher -> Block
philosopherBlock p =
blockEnum (philosopherNumber p) (philosopherName p) (philosopherColor p)
philosopherName :: Philosopher -> String
philosopherName p = case p of
Socrates -> "Socrates"
Plato -> "Plato"
Aristotle -> "Aristotle"
Diogenes -> "Diogenes"
Epicurus -> "Epicurus"
Cicero -> "Cicero"
Seneca -> "Seneca"
Lucretius -> "Lucretius"
MarcusAurelius -> "Marcus Aurelius"
Virgil -> "Virgil"
Hobbes -> "Hobbes"
Spinoza -> "Spinoza"
Descartes -> "Descartes"
Locke -> "Locke"
Hume -> "Hume"
philosopherColor :: Philosopher -> Color
philosopherColor p = case p of
Cicero -> magenta
Locke -> green
_ -> black
philosopherNumber :: Philosopher -> String
philosopherNumber p = case elemIndex p philosophers of
Just i -> pad2 (i + 1)
Nothing -> "??"
pad2 :: Int -> String
pad2 n | n < 10 = '0' : show n
| otherwise = show n
philosopherByNumber :: String -> Maybe Philosopher
philosopherByNumber s = case reads s :: [(Int, String)] of
[(n, "")] -> lookup n [ (read (philosopherNumber p) :: Int, p) | p <- philosophers ]
_ -> Nothing
trim :: String -> String
trim = f . f
where f = reverse . dropWhile isSpace
--------------------------------------------------------------------------------
-- Tables
--------------------------------------------------------------------------------
richTable :: Maybe Table
richTable = tableBlocks richHeader richRows
richHeader :: [Block]
richHeader =
[ blockSimple "Flight" (themeTableHeader demoTheme)
, blockSimple "Company" (normal yellow)
, blockAssoc ["Origin", "Destination"] (normal yellow) (normal magenta)
, blockSimple "Departure" (normal cyan)
, blockSimple "Arrival" (normal red)
]
richRows :: [[Block]]
richRows =
[ [ blockSimple "1929" (bold green)
, blockSimple "American Airlines" (normal green)
, blockAssoc ["PORTLAND", "CHARLOTTE"] styleDefault (normal magenta)
, blockSimple "22:20" (normal cyan)
, blockSimple "06:09" (normal red) ]
, [ blockSimple "710" (bold green)
, blockSimple "Arik Air" (normal green)
, blockAssoc ["LAGOS", "ABUJA"] styleDefault (normal magenta)
, blockSimple "06:00" (normal cyan)
, blockSimple "07:15" (normal red) ]
, [ blockSimple "309" (bold green)
, blockSimple "British Airways" (normal green)
, blockAssoc ["PARIS", "LONDON"] styleDefault (normal magenta)
, blockSimple "12:10" (normal cyan)
, blockSimple "12:30" (normal red) ]
, [ blockSimple "3002" (bold green)
, blockSimple "EgyptAir" (normal green)
, blockAssoc ["CAIRO", "DUBAI"] styleDefault (normal magenta)
, blockSimple "19:15" (normal cyan)
, blockSimple "00:35" (normal red) ]
, [ blockSimple "5139" (bold green)
, blockSimple "AerCaribe Peru" (normal green)
, blockAssoc ["BOGOTA", "LIMA"] styleDefault (normal magenta)
, blockSimple "06:00" (normal cyan)
, blockSimple "23:00" (normal red) ]
]
simpleTable :: Maybe Table
simpleTable = tableSimple simpleHeader (themeTableBody demoTheme) simpleRows
-- Right aligns the numeric columns and truncates long composer names.
demoTablesFormat :: TableFormat
demoTablesFormat = tableFormat
[ defaultColumnSpec { columnMaxWidth = Just 11
, columnEllipsis = EllipsisChar '…' }
, defaultColumnSpec
, defaultColumnSpec { columnAlign = AlignRight }
, defaultColumnSpec { columnAlign = AlignRight }
, defaultColumnSpec { columnAlign = AlignRight }
]
simpleHeader :: [Block]
simpleHeader =
[ blockSimple "Composer" (bold white)
, blockSimple "Country" (bold cyan)
, blockSimple "Born" (bold green)
, blockSimple "Died" (bold green)
, blockSimple "Works" (bold yellow)
]
simpleRows :: [[String]]
simpleRows =
[ ["Tchaikovsky", "Russia", "1840", "1893", "6"]
, ["Sibelius", "Finland", "1865", "1957", "7"]
, ["Mussorgsky", "Russia", "1839", "1881", "5"]
, ["Brahms", "Germany", "1833", "1897", "4"]
, ["Chopin", "Poland", "1810", "1849", "6"]
, ["Offenbach", "Germany", "1819", "1880", "5"]
, ["Holst", "England", "1874", "1934", "5"]
, ["Prokofiev", "Russia", "1891", "1953", "7"]
]
--------------------------------------------------------------------------------
-- Article
--------------------------------------------------------------------------------
article :: Article
article = [paragraph1, paragraph2, paragraph3]
paragraph1, paragraph2, paragraph3 :: Paragraph
paragraph1 =
[ blockSimple "Ansiform " textBold
, blockSimple "is a formatting DSL for command line interfaces. " textRegular
, blockSimple "Builders are pure, " textItalic
, blockSimple "pages are immutable, " textUnderline
, blockSimple "and drawing is the only layer that talks to the terminal." textRegular
]
paragraph2 =
[ blockSimple "Layout adapts to the terminal width. " textRegular
, blockSimple "Tables reject malformed input, " (textMark yellow)
, blockSimple "and paragraphs always make progress, " (textColor cyan)
, blockSimple "even when a single word is wider than the screen." textRegular
, blockSimple " Lists flow into columns, " textItalic
, blockSimple "groups share space evenly, " textBold
, blockSimple "and tables can mix independently styled cells." textRegular
]
paragraph3 =
[ blockSimple "The standard interaction is immediate: " textRegular
, blockSimple "press a key and the page changes, " textBold
, blockSimple "without a prompt and without Enter. " textRegular
, blockSimple "Styles are suppressed on redirected output, " textItalic
, blockSimple "and wide characters, combining marks and emoji " textRegular
, blockSimple "keep their alignment " (textMark cyan)
, blockSimple "because layout measures terminal columns, not code points." textRegular
]
--------------------------------------------------------------------------------
-- Philosopher articles
--------------------------------------------------------------------------------
ciceroArticle :: Article
ciceroArticle = [ciceroParagraph1, ciceroParagraph2]
ciceroParagraph1, ciceroParagraph2 :: Paragraph
ciceroParagraph1 =
[ blockSimple "Marcus Tullius Cicero " textBold
, blockSimple "(106-43 BC) was a Roman statesman, orator and philosopher. " textRegular
, blockSimple "He was consul in 63 BC " textItalic
, blockSimple "and is known for the Catilinarian orations and for his many speeches." textRegular
]
ciceroParagraph2 =
[ blockSimple "His philosophical works, " textRegular
, blockSimple "such as De Re Publica and De Officiis, " (textMark yellow)
, blockSimple "helped bring Greek philosophy into Latin. " textRegular
, blockSimple "He was killed during the proscriptions of 43 BC." (textColor cyan)
]
lockeArticle :: Article
lockeArticle = [lockeParagraph1, lockeParagraph2]
lockeParagraph1, lockeParagraph2 :: Paragraph
lockeParagraph1 =
[ blockSimple "John Locke " textBold
, blockSimple "(1632-1704) was an English philosopher and physician. " textRegular
, blockSimple "He is a central figure of empiricism " textItalic
, blockSimple "and argued that the mind begins as a blank slate." textRegular
]
lockeParagraph2 =
[ blockSimple "His Two Treatises of Government " textRegular
, blockSimple "defended natural rights and government by consent. " (textMark yellow)
, blockSimple "The Essay Concerning Human Understanding " textItalic
, blockSimple "shaped modern epistemology and liberal thought." (textColor cyan)
]
philosopherArticle :: Philosopher -> Article
philosopherArticle p = case p of
Socrates -> bio "Socrates " "(c. 470-399 BC) was an Athenian philosopher. "
"He examined ethical concepts through dialogue and questioning. "
"He wrote nothing and is known mainly through Plato."
Plato -> bio "Plato " "(c. 427-347 BC) was an Athenian philosopher. "
"He developed the theory of Forms and founded the Academy. "
"His dialogues include the Republic and the Symposium."
Aristotle -> bio "Aristotle " "(384-322 BC) was a Greek philosopher and student of Plato. "
"He worked on logic, ethics, politics and natural science. "
"He also tutored Alexander the Great."
Diogenes -> bio "Diogenes " "(c. 412-323 BC) was a Cynic philosopher. "
"He rejected wealth and convention and lived with extreme simplicity. "
"His anecdotes illustrate independence and moral frankness."
Epicurus -> bio "Epicurus " "(341-270 BC) was a Greek philosopher. "
"He founded a school known as the Garden and taught that pleasure is the absence of pain. "
"His atomism influenced later materialist thought."
Cicero -> ciceroArticle
Seneca -> bio "Seneca " "(c. 4 BC-AD 65) was a Roman Stoic philosopher and statesman. "
"He wrote letters and essays on ethics and advised Nero. "
"He was forced to commit suicide in AD 65."
Lucretius -> bio "Lucretius " "(c. 99-55 BC) was a Roman poet and philosopher. "
"His poem De Rerum Natura explains Epicurean atomism. "
"It is a major source for ancient materialist ideas."
MarcusAurelius -> bio "Marcus Aurelius " "(AD 121-180) was a Roman emperor and Stoic philosopher. "
"His Meditations record private reflections on duty and self-control. "
"He is often called the philosopher emperor."
Virgil -> bio "Virgil " "(70-19 BC) was a Roman poet. "
"He wrote the Eclogues, the Georgics and the Aeneid. "
"The Aeneid became a foundational text of Roman literature."
Hobbes -> bio "Thomas Hobbes " "(1588-1679) was an English philosopher. "
"He is known for Leviathan and for social contract theory. "
"He argued for a strong sovereign to prevent civil war."
Spinoza -> bio "Baruch Spinoza " "(1632-1677) was a Dutch philosopher. "
"His Ethics presents a rationalist and monist system. "
"He defended freedom of thought and religious toleration."
Descartes -> bio "René Descartes " "(1596-1650) was a French philosopher and mathematician. "
"He is known for the cogito and for mind-body dualism. "
"He helped shape modern rationalism and the scientific method."
Locke -> lockeArticle
Hume -> bio "David Hume " "(1711-1776) was a Scottish philosopher and historian. "
"He developed empiricism and an influential analysis of causation. "
"His works include A Treatise of Human Nature and An Enquiry Concerning Human Understanding."
bio :: String -> String -> String -> String -> Article
bio name dates role detail =
[ [ blockSimple name textBold
, blockSimple dates textRegular
, blockSimple role textItalic
, blockSimple detail textRegular
] ]