packages feed

brick-calendar-0.2.0.0: programs/CalendarDemo.hs

{-# LANGUAGE OverloadedStrings #-}

module Main where

import Brick
import Brick.Widgets.Border
import qualified Brick.Widgets.Center as C
import Brick.Widgets.Calendar
import qualified Graphics.Vty as V
import Data.Time
import System.IO
import Lens.Micro.Platform

-- | Data type to identify our widgets
data AppName = Calendar1
    | Calendar1Resource CalendarResource
    deriving (Eq, Ord, Show)

-- | App state
newtype AppState = AppState
    { calendar :: CalendarState AppName
    }

-- | Lens for the calendar field of AppState
calendarL :: Lens' AppState (CalendarState AppName)
calendarL = lens calendar (\s c -> s { calendar = c })

-- | Main app definition
app :: App AppState e AppName
app = App
    { appDraw = drawUI
    , appChooseCursor = showFirstCursor
    , appHandleEvent = handleEvent
    , appStartEvent = return ()
    , appAttrMap = const defaultCalendarAttrMap
    }

-- | Initialize the app state
initialState :: IO AppState
initialState = do
    today <- utctDay <$> getCurrentTime
    let (year, month, _) = toGregorian today
    let config = defaultCalendarConfig
                    { _showDayLabels = True
                    , _dayLabelStyle = DistinctInitials
                    , _outsideMonthDisplay = ShowDimmed
                    , _weekStart = Sunday
                    }
    return AppState
        { calendar = CalendarState year month (Just today) config Calendar1Resource
        }

-- | Draw the UI
drawUI :: AppState -> [Widget AppName]
drawUI s = [C.center ((border (padAll 1 ui) <=> config) <+> hLimit 40 help)]
  where
    config = vBox
        [
            displayConfig (calendar s)
        ]
    ui =
        padAll 1 $ renderCalendar (calendar s)
    help = vBox
        [ str "Controls"
        , hBorder
        , str "Navigation:"
        , str "arrow keys / hjkl - move selection"
        , str "[] / HL - previous / next month"
        , str "{} / JK - previous / next year"
        , hBorder
        , str "Settings:"
        , str "d - toggle day labels"
        , str "t - cycle label style"
        , str "o - cycle outside days"
        , str "w - toggle week start"
        , hBorder
        , str "Actions:"
        , str "Enter / Esc / q - select date and exit"
        ]

-- | Display the current configuration
displayConfig :: CalendarState AppName -> Widget n
displayConfig s =
    let config = calConfig s
        weekStartText = show (config ^. weekStart)
        labelStyleText = case config ^. dayLabelStyle of
                            SingleChar -> "Single char"
                            DoubleChar -> "Double char"
                            DistinctInitials -> "Thursday as Th"
        showLabelsText = if config ^. showDayLabels then "Show" else "Hide"
        outsideText = case config ^. outsideMonthDisplay of
                         Hide -> "Hide"
                         ShowDimmed -> "Show dimmed"
                         ShowNormal -> "Show normal"
        selectedText = maybe "None" showGregorian (calSelectedDay s)
    in withAttr (attrName "config") $
       vBox [ str $ "Week starts on: " ++ weekStartText
            , str $ "Day label style: " ++ labelStyleText
            , str $ "Day labels: " ++ showLabelsText
            , str $ "Outside month days: " ++ outsideText
            , str $ "Selected day: " ++ selectedText
            ]

-- | Handle events
handleEvent :: BrickEvent AppName e -> EventM AppName AppState ()
handleEvent (VtyEvent (V.EvKey V.KEsc [])) = halt
handleEvent (VtyEvent (V.EvKey (V.KChar 'q') [])) = halt
handleEvent (VtyEvent (V.EvKey V.KEnter [])) = halt

-- Toggle day labels
handleEvent (VtyEvent (V.EvKey (V.KChar 'd') [])) = do
    zoom calendarL $ do
        cal <- get
        let config = calConfig cal
        modify $ \s -> s { calConfig = config & showDayLabels %~ not }

-- Cycle day label style
handleEvent (VtyEvent (V.EvKey (V.KChar 't') [])) = do
    zoom calendarL $ do
        cal <- get
        let config = calConfig cal
            newStyle = case config ^. dayLabelStyle of
                         SingleChar -> DoubleChar
                         DoubleChar -> DistinctInitials
                         DistinctInitials -> SingleChar
        modify $ \s -> s { calConfig = config & dayLabelStyle .~ newStyle }

-- Cycle outside month display
handleEvent (VtyEvent (V.EvKey (V.KChar 'o') [])) = do
    zoom calendarL $ do
        cal <- get
        let config = calConfig cal
            newOutside = case config ^. outsideMonthDisplay of
                           Hide -> ShowDimmed
                           ShowDimmed -> ShowNormal
                           ShowNormal -> Hide
        modify $ \s -> s { calConfig = config & outsideMonthDisplay .~ newOutside }

-- Toggle week start
handleEvent (VtyEvent (V.EvKey (V.KChar 'w') [])) = do
    zoom calendarL $ do
        cal <- get
        let config = calConfig cal
            newStart = case config ^. weekStart of
                         Sunday -> Monday
                         Monday -> Sunday
                         _ -> error "Not implemented"
        modify $ \s -> s { calConfig = config & weekStart .~ newStart }

-- Use the default calendar event handler for navigation
handleEvent e = zoom calendarL $ handleCalendarEvent e

-- | Main function
main :: IO ()
main = do
    -- Ensure unicode support
    hSetEncoding stdout utf8

    -- Run the app
    s <- initialState
    finalState <- defaultMain app s
    case calSelectedDay $ calendar finalState of
        Just day -> putStrLn $ "Selected date: " ++ showGregorian day
        Nothing -> putStrLn "No date selected"