packages feed

monomer-hagrid-0.3.0.0: example-basic/Main.hs

{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}

module Main (main) where

import Control.Lens (Ixed (ix), makeLensesFor, singular)
import Data.Sequence (Seq ((:|>)))
import qualified Data.Sequence as S
import Data.Text (Text, pack)
import qualified Data.Text as T
import Data.Time (Day, addDays, defaultTimeLocale, formatTime, fromGregorian)
import Monomer
import Monomer.Hagrid (Column (..), ColumnAlign (..), ColumnFooterWidget (..), ColumnSortKey (..), SortDirection (..), hagrid_, initialSort, scrollToRow, showOrdColumn, textColumn, widgetColumn)
import Text.Printf (printf)

data AppModel = AppModel
  { theme :: Theme,
    spiders :: Seq Spider,
    columns :: [AppColumn],
    rowToScrollTo :: Int
  }
  deriving (Eq, Show)

newtype AppColumn = AppColumn
  {enabled :: Bool}
  deriving (Eq, Show)

data AppEvent
  = FeedSpider Int
  | AddSpider
  | NameColumnResized Int
  | NameColumnSorted SortDirection
  | ScrollToOriginalIndex

data Spider = Spider
  { index :: Int,
    species :: Text,
    name :: Text,
    dateOfBirth :: Day,
    weightKilos :: Double
  }
  deriving (Eq, Show)

makeLensesFor [("enabled", "_enabled")] ''AppColumn
makeLensesFor [("columns", "_columns"), ("theme", "_theme"), ("rowToScrollTo", "_rowToScrollTo")] ''AppModel

main :: IO ()
main = startApp model handleEvent buildUI config
  where
    config =
      [ appWindowTitle "Hagrid Basic Example",
        appFontDef "Bold" "./assets/fonts/Cantarell/Cantarell-Bold.ttf",
        appFontDef "Regular" "./assets/fonts/Cantarell/Cantarell-Regular.ttf",
        appDisableAutoScale True,
        appWindowState (MainWindowNormal (1200, 1000))
      ]
    model =
      AppModel
        { theme = darkTheme,
          spiders = spiders,
          columns = AppColumn True <$ gridColumns,
          rowToScrollTo = 0
        }
    spiders = S.fromFunction numSpiders spider
    spider i =
      Spider
        { index = i,
          species = "Acromantula",
          name = T.pack (printf "Son of Aragog %d" (i + 1)),
          dateOfBirth = addDays (fromIntegral i) (fromGregorian 1942 3 1),
          weightKilos = fromIntegral (numSpiders + 2 - i) * 2.3
        }
    numSpiders = 100

buildUI :: UIBuilder AppModel AppEvent
buildUI _wenv model = tree
  where
    tree =
      themeSwitch_ model.theme [themeClearBg] $
        vstack
          [ grid `nodeKey` hagridKey,
            vstack_
              [childSpacing]
              (themeConfigurer : columnConfigurers <> actionButtons)
              `styleBasic` [padding 8]
          ]

    grid =
      hagrid_
        [initialSort 1 SortDescending]
        (mconcat (zipWith column model.columns gridColumns))
        model.spiders

    column (AppColumn enabled) columnDef =
      [columnDef | enabled]

    themeConfigurer =
      hstack_
        [childSpacing]
        [ labeledRadio_ "Dark Theme" darkTheme _theme [textRight],
          labeledRadio_ "Light Theme" lightTheme _theme [textRight]
        ]

    columnConfigurers =
      zipWith columnConfigurer [0 .. length model.columns - 1] gridColumns

    columnConfigurer :: Int -> Column AppEvent Spider -> WidgetNode AppModel AppEvent
    columnConfigurer idx columnDef =
      labeledCheckbox_
        columnDef.name
        (_columns . singular (ix idx) . _enabled)
        [textRight]

    actionButtons =
      [ hstack_
          [childSpacing]
          [ label "Scroll to index of unsorted list",
            numericField _rowToScrollTo,
            button "Go!" ScrollToOriginalIndex
          ],
        hstack_
          [childSpacing]
          [button "Add spider" AddSpider]
      ]

handleEvent :: EventHandler AppModel AppEvent sp ep
handleEvent _wenv _node model = \case
  FeedSpider idx
    | Just spdr <- S.lookup idx model.spiders ->
        [ Producer (const (putStrLn ("Feeding spider " <> T.unpack spdr.name))),
          Model model {spiders = S.update idx spdr {weightKilos = spdr.weightKilos + 1} model.spiders}
        ]
  FeedSpider _ ->
    []
  AddSpider ->
    [Model model {spiders = model.spiders :|> newSpider model}]
  NameColumnResized colWidth ->
    [Producer (const (putStrLn ("Name column was resized: " <> show colWidth)))]
  NameColumnSorted direction ->
    [Producer (const (putStrLn ("Name column was sorted: " <> show direction)))]
  ScrollToOriginalIndex ->
    [scrollToRow (WidgetKey hagridKey) (rowScrollIndex model)]

newSpider :: AppModel -> Spider
newSpider model =
  Spider
    { index = fromIntegral (length model.spiders),
      name = "Extra Spider " <> pack (show (length model.spiders)),
      species = "Spider plant",
      dateOfBirth = fromGregorian 2022 6 26,
      weightKilos = 0.01
    }

rowScrollIndex :: AppModel -> Seq (Spider, Int) -> Maybe Int
rowScrollIndex model items =
  snd <$> S.lookup model.rowToScrollTo items

gridColumns :: [Column AppEvent Spider]
gridColumns = cols
  where
    cols =
      [ (showOrdColumn "Index" (.index))
          { initialWidth = 120,
            align = ColumnAlignRight,
            footerWidget = CustomFooterWidget countFooter
          },
        (textColumn "Name" (.name))
          { initialWidth = 300,
            resizeHandler = Just NameColumnResized,
            sortHandler = Just NameColumnSorted
          },
        (textColumn "Species" (.species))
          { initialWidth = 200
          },
        (textColumn "Date of Birth" (T.pack . formatTime defaultTimeLocale "%Y-%m-%d" . (.dateOfBirth)))
          { initialWidth = 200
          },
        (textColumn "Weight (Kg)" (T.pack . printf "%.2f" . (.weightKilos)))
          { sortKey = SortWith (.weightKilos),
            initialWidth = 200,
            align = ColumnAlignRight,
            footerWidget = CustomFooterWidget sumWeightFooter
          },
        (widgetColumn "Actions" actionsColumn)
          { initialWidth = 100,
            paddingW = 5,
            paddingH = 5
          }
      ]

    countFooter spiders =
      labelledFooter "Count" (T.pack . show . length $ spiders)

    sumWeightFooter spiders = tree
      where
        tree = labelledFooter "Sum" (T.pack (printf "%.2f" totalWeightKilos))
        totalWeightKilos = sum ((.weightKilos) . fst <$> spiders)

    labelledFooter labelText text =
      hstack
        [ label labelText,
          filler,
          label text
            `styleBasic` [textFont "Bold"]
        ]
        `styleBasic` [padding 10]

    actionsColumn :: Int -> Spider -> WidgetNode s AppEvent
    actionsColumn idx _spdr =
      button "Feed" (FeedSpider idx)

hagridKey :: Text
hagridKey = "SpiderHagrid"