diff --git a/package.yaml b/package.yaml index 41e392b..fa9cbe4 100644 --- a/package.yaml +++ b/package.yaml @@ -32,7 +32,7 @@ dependencies: #- sqlite-simple #- stm #- text - #- text-zipper +- text-zipper - time #- transformers #- unix diff --git a/src/TaskMachine/UI.hs b/src/TaskMachine/UI.hs index 9fddcc5..0cb729e 100644 --- a/src/TaskMachine/UI.hs +++ b/src/TaskMachine/UI.hs @@ -7,11 +7,15 @@ module TaskMachine.UI 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 @@ -24,8 +28,11 @@ 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, renderTaskList True (tasks s), placeholderNewTask] +drawBaseLayer s = B.vBox [placeholderTopBar, drawTaskList True s, placeholderNewTask] drawUIState :: UIState -> [B.Widget RName] drawUIState s@UIState{errorPopup=Just p} = [renderPopupOk p, drawBaseLayer s] @@ -52,16 +59,55 @@ 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 -> Result) -> UIState -> VTY.Event -> Result +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 -selectBehavior s e = closeBehavior rootBehavior 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 @@ -107,4 +153,5 @@ startUIState o = UIState , focus = B.focusRing [BRTaskList, BRNewTask, BRTopBar] , errorPopup = Nothing , tasks = taskList RTaskList V.empty + , taskEdit = Nothing } diff --git a/src/TaskMachine/UI/TaskList.hs b/src/TaskMachine/UI/TaskList.hs index ec74a2a..d1bb3aa 100644 --- a/src/TaskMachine/UI/TaskList.hs +++ b/src/TaskMachine/UI/TaskList.hs @@ -2,6 +2,7 @@ module TaskMachine.UI.TaskList ( TaskList , taskList , renderTaskList + , updateTaskList , taskListElements , taskListFilter , taskListSelectedElement @@ -11,12 +12,12 @@ module TaskMachine.UI.TaskList --import Data.Void import qualified Brick as B +import qualified Brick.Widgets.Edit as B import qualified Brick.Widgets.List as B import qualified Data.Vector as V +import qualified Graphics.Vty as VTY --import qualified Brick.Focus as B ---import qualified Brick.Widgets.Edit as B --import qualified Data.Text.Zipper as T ---import qualified Graphics.Vty as VTY --import Text.Megaparsec import TaskMachine.LTask @@ -37,9 +38,17 @@ newList name ltasks = B.list name ltasks 1 taskList :: n -> V.Vector LTask -> TaskList n taskList name ltasks = TaskList{visibleTasks=newList name ltasks, invisibleTasks=V.empty} --- TODO: render while editing -renderTaskList :: (Ord n, Show n) => Bool -> TaskList n -> B.Widget n -renderTaskList focus tl = B.renderList (const $ renderTask . toTask) focus (visibleTasks tl) +renderLTask :: (Ord n, Show n) => Maybe (B.Editor String n) -> Bool -> LTask -> B.Widget n +renderLTask (Just e) True _ = B.renderEditor (B.str . unlines) True e +renderLTask _ _ lt = renderTask (toTask lt) + +renderTaskList :: (Ord n, Show n) => Maybe (B.Editor String n) -> Bool -> TaskList n -> B.Widget n +renderTaskList edit focus tl = B.renderList (renderLTask edit) focus (visibleTasks tl) + +updateTaskList :: (Ord n) => VTY.Event -> TaskList n -> B.EventM n (TaskList n) +updateTaskList e tl = do + updatedList <- B.handleListEventVi B.handleListEvent e (visibleTasks tl) + pure tl{visibleTasks=updatedList} {- Managing tasks -} diff --git a/src/TaskMachine/UI/Types.hs b/src/TaskMachine/UI/Types.hs index a73063b..10aa2e5 100644 --- a/src/TaskMachine/UI/Types.hs +++ b/src/TaskMachine/UI/Types.hs @@ -87,6 +87,7 @@ data UIState = UIState -- tasks , tasks :: TaskList RName + , taskEdit :: Maybe (B.Editor String RName) } type NewState = B.EventM RName (B.Next UIState) diff --git a/todo.txt b/todo.txt index ac537ee..a8b7474 100644 --- a/todo.txt +++ b/todo.txt @@ -4,4 +4,7 @@ - c2018-09-18 Offer to clean up file when loading (adding creation/completion dates) - c2018-09-18 Purge - move completed tasks to a separate file - c2018-09-18 Sort tasks by completion, priority, due date, description +- c2018-09-28 Load file in initial app action +- c2018-09-28 Move cursor to beginning of task description when editing tasks +- c2018-09-28 Syntax highlighting while editing tasks x2018-09-27 c2018-09-18 Quit using Esc or q