157 lines
6.5 KiB
Haskell
157 lines
6.5 KiB
Haskell
module TaskMachine.UI
|
|
( myApp
|
|
, startUIState
|
|
, loadTasks
|
|
) where
|
|
|
|
import qualified Brick as B
|
|
import qualified Brick.Focus as B
|
|
import qualified Brick.Themes as B
|
|
import qualified Brick.Widgets.Edit as B
|
|
import qualified Data.Text.Zipper as T
|
|
import qualified Data.Vector as V
|
|
import qualified Graphics.Vty.Input.Events as VTY
|
|
import Text.Megaparsec
|
|
|
|
import TaskMachine.LTask
|
|
import TaskMachine.Options
|
|
import TaskMachine.Task
|
|
import TaskMachine.UI.Popup
|
|
import TaskMachine.UI.TaskList
|
|
import TaskMachine.UI.Types
|
|
|
|
{- Rendering -}
|
|
|
|
placeholderTopBar :: B.Widget n
|
|
placeholderTopBar = B.str "Prune | Reload | Search: " B.<+> B.vLimit 1 (B.fill '_')
|
|
|
|
placeholderNewTask :: B.Widget RName
|
|
placeholderNewTask = B.str "New: " B.<+> B.vLimit 1 (B.fill '_')
|
|
|
|
drawTaskList :: Bool -> UIState -> B.Widget RName
|
|
drawTaskList focused s = renderTaskList (taskEdit s) focused (tasks s)
|
|
|
|
drawBaseLayer :: UIState -> B.Widget RName
|
|
drawBaseLayer s = B.vBox [placeholderTopBar, drawTaskList True s, placeholderNewTask]
|
|
|
|
drawUIState :: UIState -> [B.Widget RName]
|
|
drawUIState s@UIState{errorPopup=Just p} = [renderPopupOk p, drawBaseLayer s]
|
|
drawUIState s = [drawBaseLayer s]
|
|
|
|
{- Actions -}
|
|
|
|
loadTasks :: UIState -> IO UIState
|
|
loadTasks s = do
|
|
let file = oTodofile $ options s
|
|
result <- loadLTasks file
|
|
case result of
|
|
-- TODO: Improve error handling when loading files
|
|
Left errorMessage -> pure s{errorPopup=Just $ popupOk "Error loading tasks" errorMessage}
|
|
Right ltasks -> pure s{tasks=taskList RTaskList ltasks}
|
|
|
|
{- Updating the state -}
|
|
|
|
rootBehavior :: UIState -> VTY.Event -> NewState
|
|
rootBehavior s _ = B.continue s
|
|
|
|
closeBehavior :: (UIState -> VTY.Event -> NewState) -> UIState -> VTY.Event -> NewState
|
|
closeBehavior _ s (VTY.EvKey VTY.KEsc []) = B.halt s
|
|
closeBehavior _ s (VTY.EvKey (VTY.KChar 'q') []) = B.halt s
|
|
closeBehavior f s e = f s e -- wrapper around another behavior
|
|
|
|
focusBehavior :: (UIState -> VTY.Event -> NewState) -> UIState -> VTY.Event -> NewState
|
|
focusBehavior _ s (VTY.EvKey (VTY.KChar '\t') []) = B.continue $ bigFocusNext s
|
|
focusBehavior _ s (VTY.EvKey VTY.KBackTab []) = B.continue $ bigFocusPrev s
|
|
focusBehavior f s e = f s e -- wrapper around another behavior
|
|
|
|
taskListBehavior :: UIState -> VTY.Event -> NewState
|
|
-- Mark/unmark a task as completed
|
|
taskListBehavior s (VTY.EvKey (VTY.KChar 'x') []) = undefined s
|
|
-- Delete tasks
|
|
taskListBehavior s (VTY.EvKey (VTY.KChar 'd') []) = undefined s
|
|
-- Start editing a new task
|
|
taskListBehavior s (VTY.EvKey (VTY.KChar 'e') []) =
|
|
case taskListSelectedElement (tasks s) of
|
|
Nothing -> B.continue s -- TODO: Add notification popup
|
|
Just t ->
|
|
let edit = B.editor RTaskEdit (Just 1) (formatTask t)
|
|
in B.continue s{taskEdit=Just edit}
|
|
-- Update the task list (scroll etc.)
|
|
taskListBehavior s e = do
|
|
newTasks <- updateTaskList e (tasks s)
|
|
B.continue s{tasks=newTasks}
|
|
|
|
taskEditBehavior :: B.Editor String RName -> UIState -> VTY.Event -> NewState
|
|
taskEditBehavior _ s (VTY.EvKey VTY.KEsc []) = B.continue s{taskEdit=Nothing}
|
|
taskEditBehavior edit s (VTY.EvKey VTY.KHome []) = B.continue s{taskEdit=Just (B.applyEdit T.gotoBOL edit)}
|
|
taskEditBehavior edit s (VTY.EvKey VTY.KEnd []) = B.continue s{taskEdit=Just (B.applyEdit T.gotoEOL edit)}
|
|
taskEditBehavior edit s (VTY.EvKey VTY.KEnter []) = do -- TODO: Save changes to file
|
|
let editedText = unlines $ B.getEditContents edit
|
|
case parse pTask "edited task" editedText of
|
|
Left parseError -> undefined parseError -- TODO: Add notification here
|
|
Right newTask -> do
|
|
let newTaskList = taskListModify (const newTask) (tasks s)
|
|
B.continue s{tasks=newTaskList, taskEdit=Nothing}
|
|
taskEditBehavior edit s e = do
|
|
newEdit <- B.handleEditorEvent e edit
|
|
B.continue s{taskEdit=Just newEdit}
|
|
|
|
selectBehavior :: UIState -> VTY.Event -> NewState
|
|
-- Deal with popup if there is one
|
|
selectBehavior s@UIState{errorPopup=Just popup} e = undefined popup s e
|
|
-- Under the assumption that tasks can only be edited while the task list is focused, edit a task
|
|
selectBehavior s@UIState{taskEdit=Just edit} e = taskEditBehavior edit s e
|
|
-- If nothing immediately jumps out at you, see which part has focus.
|
|
selectBehavior s e =
|
|
case B.focusGetCurrent (focus s) of
|
|
Just BRTopBar -> closeBehavior (focusBehavior rootBehavior) s e
|
|
Just BRTaskList -> closeBehavior (focusBehavior taskListBehavior) s e
|
|
Just BRNewTask -> closeBehavior (focusBehavior rootBehavior) s e
|
|
Nothing -> closeBehavior (focusBehavior rootBehavior) s e
|
|
|
|
updateUIState :: UIState -> B.BrickEvent RName () -> NewState
|
|
updateUIState s (B.VtyEvent e) = selectBehavior s e
|
|
updateUIState s _ = B.continue s
|
|
|
|
{-
|
|
updateUIState :: UIState -> B.BrickEvent RName () -> B.EventM RName (B.Next UIState)
|
|
-- Closing error popup
|
|
updateUIState s@UIState{errorPopup=Just _} (B.VtyEvent (VTY.EvKey VTY.KEnter [])) = B.continue s{errorPopup=Nothing}
|
|
updateUIState s@UIState{errorPopup=Just _} (B.VtyEvent (VTY.EvKey VTY.KEsc [])) = B.continue s{errorPopup=Nothing}
|
|
--updateUIState s@UIState{errorPopup=Just p} (B.VtyEvent e) = do
|
|
-- newPopup <- handlePopupEvent e p
|
|
-- B.continue s{errorPopup=Just newPopup}
|
|
updateUIState s e =
|
|
case B.focusGetCurrent (focus s) of
|
|
Nothing -> B.halt s
|
|
(Just BRTopBar) -> placeholderUpdate s e
|
|
--(Just BRTaskList) -> updateTaskList s e
|
|
(Just BRTaskList) -> placeholderUpdate s e
|
|
(Just BRNewTask) -> placeholderUpdate s e
|
|
|
|
placeholderUpdate :: UIState -> B.BrickEvent RName () -> B.EventM RName (B.Next UIState)
|
|
placeholderUpdate s (B.VtyEvent (VTY.EvKey VTY.KEsc [])) = B.halt s
|
|
placeholderUpdate s (B.VtyEvent (VTY.EvKey (VTY.KChar '\t') [])) = B.continue $ bigFocusNext s
|
|
placeholderUpdate s (B.VtyEvent (VTY.EvKey VTY.KBackTab [])) = B.continue $ bigFocusPrev s
|
|
placeholderUpdate s _ = B.continue s
|
|
-}
|
|
|
|
{- Starting the app -}
|
|
|
|
myApp :: B.App UIState () RName
|
|
myApp = B.App
|
|
{ B.appDraw = drawUIState
|
|
, B.appChooseCursor = B.showFirstCursor
|
|
, B.appHandleEvent = updateUIState
|
|
, B.appStartEvent = pure
|
|
, B.appAttrMap = const (B.themeToAttrMap defaultTheme)
|
|
}
|
|
|
|
startUIState :: Options -> UIState
|
|
startUIState o = UIState
|
|
{ options = o
|
|
, focus = B.focusRing [BRTaskList, BRNewTask, BRTopBar]
|
|
, errorPopup = Nothing
|
|
, tasks = taskList RTaskList V.empty
|
|
, taskEdit = Nothing
|
|
}
|