packages feed

brick-calendar-0.2.0.0: src/Brick/Widgets/Calendar/Internal/Actions.hs

{-# LANGUAGE LambdaCase #-}

module Brick.Widgets.Calendar.Internal.Actions
  ( -- * Navigation actions
    moveUp
  , moveDown
  , moveLeft
  , moveRight
  , setMonthBefore
  , setMonthAfter
  , setYearBefore
  , setYearAfter
    -- * Event handler for common calendar navigation
    -- You can also use the individual actions above to create your own custom event handler
  , handleCalendarEvent
  ) where

import Data.Time
import Data.Time.Calendar.Month
import Brick ( modify, EventM, BrickEvent(..) )
import qualified Graphics.Vty as V

import Brick.Widgets.Calendar.Internal.Core

moveUp :: CalendarState n -> CalendarState n
moveUp =
  navigateSelection (addDays (-7))

moveDown :: CalendarState n -> CalendarState n
moveDown =
  navigateSelection (addDays 7)

moveLeft :: CalendarState n -> CalendarState n
moveLeft =
  navigateSelection (addDays (-1))

moveRight :: CalendarState n -> CalendarState n
moveRight =
  navigateSelection (addDays 1)

setMonthBefore :: CalendarState n -> CalendarState n
setMonthBefore =
  navigateMonth (addMonths (-1))

setMonthAfter :: CalendarState n -> CalendarState n
setMonthAfter =
  navigateMonth (addMonths 1)

setYearBefore :: CalendarState n -> CalendarState n
setYearBefore =
  navigateMonth (addMonths (-12))

setYearAfter :: CalendarState n -> CalendarState n
setYearAfter =
  navigateMonth (addMonths 12)

navigateSelection :: (Day -> Day) -> CalendarState n -> CalendarState n
navigateSelection dayTransform s =
  case calSelectedDay s of
    Nothing -> 
      -- When no day is selected, select the first day of the current month
      s { calSelectedDay = Just $ fromGregorian (calYear s) (calMonth s) 1 }
    Just day ->
      let newDay = dayTransform day
          (y, m, _) = toGregorian newDay
      in if m /= calMonth s || y /= calYear s
         -- If we've moved to a different month, update the view
         then s { calYear = y, calMonth = m, calSelectedDay = Just newDay }
         -- Otherwise just update the selected day
         else s { calSelectedDay = Just newDay }

navigateMonth :: (Month -> Month) -> CalendarState n -> CalendarState n
navigateMonth monthTransform s = 
  let currentYM = YearMonth (calYear s) (calMonth s)
      newYM = monthTransform currentYM
      YearMonth newYear newMonth = newYM
      
      -- Keep the same day if possible in the new month
      newDay = case calSelectedDay s of
        Nothing -> Nothing
        Just day -> 
          let (_, _, d) = toGregorian day
              lastDayInNewMonth = periodLength newYM
              adjustedDay = min d lastDayInNewMonth
          in Just $ fromGregorian newYear newMonth adjustedDay
          
  in s { calYear = newYear, calMonth = newMonth, calSelectedDay = newDay }

handleCalendarEvent :: BrickEvent n e -> EventM n (CalendarState n) ()
handleCalendarEvent = \case
  -- Navigate between days
  VtyEvent (V.EvKey V.KUp [])      -> modify moveUp
  VtyEvent (V.EvKey V.KDown [])    -> modify moveDown
  VtyEvent (V.EvKey V.KLeft [])    -> modify moveLeft
  VtyEvent (V.EvKey V.KRight [])   -> modify moveRight
  VtyEvent (V.EvKey (V.KChar 'h') []) -> modify moveLeft
  VtyEvent (V.EvKey (V.KChar 'l') []) -> modify moveRight
  VtyEvent (V.EvKey (V.KChar 'j') []) -> modify moveDown
  VtyEvent (V.EvKey (V.KChar 'k') []) -> modify moveUp
  
  -- Navigate between months
  VtyEvent (V.EvKey (V.KChar '[') []) -> modify setMonthBefore
  VtyEvent (V.EvKey (V.KChar ']') []) -> modify setMonthAfter
  VtyEvent (V.EvKey (V.KChar 'H') []) -> modify setMonthBefore
  VtyEvent (V.EvKey (V.KChar 'L') []) -> modify setMonthAfter
  
  -- Navigate between years
  VtyEvent (V.EvKey (V.KChar '{') []) -> modify setYearBefore
  VtyEvent (V.EvKey (V.KChar '}') []) -> modify setYearAfter
  VtyEvent (V.EvKey (V.KChar 'J') []) -> modify setYearBefore
  VtyEvent (V.EvKey (V.KChar 'K') []) -> modify setYearAfter

  _ -> return ()