Use own task format
This commit is contained in:
parent
ea87567455
commit
6fd0814057
12 changed files with 260 additions and 749 deletions
|
|
@ -1,135 +0,0 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
-- | A collection of types necessary for the UI.
|
||||
--
|
||||
-- These were put in a separate module to avoid an import cycle.
|
||||
|
||||
module TaskMachine.UI.Colortest where
|
||||
|
||||
import Control.Monad
|
||||
import Data.List
|
||||
|
||||
import qualified Brick as B
|
||||
import qualified Graphics.Vty as VTY
|
||||
|
||||
colors :: [(String, VTY.Color)]
|
||||
colors =
|
||||
[ ("black", VTY.black)
|
||||
, ("red", VTY.red)
|
||||
, ("green", VTY.green)
|
||||
, ("yellow", VTY.yellow)
|
||||
, ("blue", VTY.blue)
|
||||
, ("magenta", VTY.magenta)
|
||||
, ("cyan", VTY.cyan)
|
||||
, ("white", VTY.white)
|
||||
, ("brightBlack", VTY.brightBlack)
|
||||
, ("brightRed", VTY.brightRed)
|
||||
, ("brightGreen", VTY.brightGreen)
|
||||
, ("brightYellow", VTY.brightYellow)
|
||||
, ("brightBlue", VTY.brightBlue)
|
||||
, ("brightMagenta", VTY.brightMagenta)
|
||||
, ("brightCyan", VTY.brightCyan)
|
||||
, ("brightWhite", VTY.brightWhite)
|
||||
]
|
||||
|
||||
styles :: [(String, VTY.Style)]
|
||||
styles =
|
||||
[ ("standout", VTY.standout)
|
||||
, ("underline", VTY.underline)
|
||||
-- , ("reverseVideo", VTY.reverseVideo)
|
||||
-- , ("blink", VTY.blink)
|
||||
-- , ("dim", VTY.dim)
|
||||
, ("bold", VTY.bold)
|
||||
]
|
||||
|
||||
toName :: String -> String -> String -> B.AttrName
|
||||
toName a b c = B.attrName a <> B.attrName b <> B.attrName c
|
||||
|
||||
useStyles :: [VTY.Style] -> VTY.Attr -> VTY.Attr
|
||||
useStyles = foldr ((.) . flip VTY.withStyle) id
|
||||
|
||||
attrMap :: B.AttrMap
|
||||
attrMap = B.attrMap VTY.defAttr $ do
|
||||
(fgName, fgColor) <- colors
|
||||
(bgName, bgColor) <- colors
|
||||
styleList <- subsequences styles
|
||||
let styleName = concatMap fst styleList
|
||||
name = toName styleName bgName fgName
|
||||
fgAttr = VTY.withForeColor VTY.defAttr fgColor
|
||||
bgAttr = VTY.withBackColor fgAttr bgColor
|
||||
attr = useStyles (map snd styleList) bgAttr
|
||||
pure (name, attr)
|
||||
|
||||
cw :: String -> B.Widget n
|
||||
cw style = B.vBox $ B.str (' ':style) : do
|
||||
(bgName, _) <- colors
|
||||
pure $ B.hBox $ do
|
||||
(fgName, _) <- colors
|
||||
let name = toName style bgName fgName
|
||||
pure $ B.withAttr name $ B.str "Hi"
|
||||
|
||||
testWidget :: B.Widget n
|
||||
testWidget = B.vBox
|
||||
[ B.hBox [cw "", cw "standout"]
|
||||
, B.hBox [cw "", cw "underline"]
|
||||
-- , B.hBox [cw "", cw "reverseVideo"]
|
||||
-- , B.hBox [cw "", cw "blink"]
|
||||
-- , B.hBox [cw "", cw "dim"]
|
||||
, B.hBox [cw "", cw "bold"]
|
||||
]
|
||||
|
||||
--fgAttrs :: [(B.AttrName, VTY.Attr)]
|
||||
--fgAttrs = map toFGAttr colors
|
||||
-- where
|
||||
-- toFGAttr :: (String, VTY.Color) -> (B.AttrName, VTY.Attr)
|
||||
-- toFGAttr (s, c) = (toFGName s, VTY.withForeColor VTY.currentAttr c)
|
||||
--
|
||||
--bgAttrs :: [(B.AttrName, VTY.Attr)]
|
||||
--bgAttrs = map toBGAttr colors
|
||||
-- where
|
||||
-- toBGAttr :: (String, VTY.Color) -> (B.AttrName, VTY.Attr)
|
||||
-- toBGAttr (s, c) = (toBGName s, VTY.withBackColor VTY.currentAttr c)
|
||||
--
|
||||
--styleAttrs :: [(B.AttrName, VTY.Attr)]
|
||||
--styleAttrs = map toStyleAttr styles
|
||||
-- where
|
||||
-- toStyleAttr :: (String, VTY.Style) -> (B.AttrName, VTY.Attr)
|
||||
-- toStyleAttr (s, st) = (toStyleName s, VTY.withStyle VTY.currentAttr st)
|
||||
--
|
||||
--attrMap :: B.AttrMap
|
||||
--attrMap = B.attrMap VTY.defAttr $ concat [fgAttrs, bgAttrs, styleAttrs]
|
||||
--
|
||||
--colorWidget :: B.Widget n
|
||||
--colorWidget = B.vBox $ do
|
||||
-- (bgName, _) <- colors
|
||||
-- let name = toBGName bgName
|
||||
-- pure $ B.withAttr name $ B.hBox $ do
|
||||
-- (fgName, _) <- colors
|
||||
-- let name = toFGName fgName
|
||||
-- pure $ B.withAttr name $ B.str "Hi"
|
||||
--
|
||||
--testWidget :: B.Widget n
|
||||
--testWidget = B.vBox $ do
|
||||
-- (styleName, _) <- styles
|
||||
-- let label = B.str styleName
|
||||
-- name = toStyleName styleName
|
||||
-- widget = B.withAttr name colorWidget
|
||||
-- pure $ B.vBox [B.str "", label, widget]
|
||||
---- sStyles <- subsequences styles
|
||||
---- let label = B.str . concat . intercalate ", " . map fst $ sStyles
|
||||
---- styleMod = foldr (.) id $ map (flip VTY.withStyle . snd) sStyles
|
||||
---- attr = styleMod VTY.defAttr
|
||||
---- widget = B.withAttr colorWidget
|
||||
---- pure $ B.vBox [B.str "", label, widget]
|
||||
|
||||
colorTestMain :: IO ()
|
||||
colorTestMain = void $ B.defaultMain app ()
|
||||
where
|
||||
app :: B.App () () ()
|
||||
app = B.App
|
||||
{ B.appDraw = const [testWidget]
|
||||
, B.appChooseCursor = B.neverShowCursor
|
||||
, B.appHandleEvent = B.resizeOrQuit
|
||||
, B.appStartEvent = pure
|
||||
, B.appAttrMap = const attrMap
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
module TaskMachine.UI.NewTask where
|
||||
|
||||
import qualified Brick as B
|
||||
|
||||
import TaskMachine.UI.Types
|
||||
|
||||
placeholderNewTask :: B.Widget RName
|
||||
placeholderNewTask = B.str "New: " B.<+> B.vLimit 1 (B.fill '_')
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module TaskMachine.UI.TaskList where
|
||||
|
||||
import Data.Void
|
||||
|
||||
import qualified Brick as B
|
||||
import qualified Brick.Focus as B
|
||||
import qualified Brick.Widgets.Edit as B
|
||||
import qualified Brick.Widgets.List as B
|
||||
import qualified Data.Text.Zipper as T
|
||||
import qualified Graphics.Vty as VTY
|
||||
import Text.Megaparsec
|
||||
|
||||
import TaskMachine.LTask
|
||||
import TaskMachine.Todotxt
|
||||
import TaskMachine.UI.Types
|
||||
|
||||
{-
|
||||
widgetPriority :: B.AttrName -> Maybe Priority -> B.Widget n
|
||||
widgetPriority _ Nothing = B.emptyWidget
|
||||
widgetPriority highlight (Just prio) =
|
||||
let attrName = highlight <> "priority" <> B.attrName [priorityToChar prio]
|
||||
text = formatPriority prio ++ " "
|
||||
in B.withAttr attrName $ B.str text
|
||||
|
||||
widgetDescription :: B.AttrName -> String -> B.Widget n
|
||||
widgetDescription highlight desc =
|
||||
let attrName = highlight <> "description"
|
||||
in B.withAttr attrName $ B.str desc
|
||||
|
||||
renderLTask :: Bool -> LTask -> B.Widget RName
|
||||
renderLTask highlight (LTask _ Task{..}) =
|
||||
let attrHighlight = if highlight then "highlight" else "normal"
|
||||
wCompleted = B.str $ if taskCompleted then "x " else " "
|
||||
wPriority = widgetPriority attrHighlight taskPriority
|
||||
wDescription = widgetDescription attrHighlight taskDescription
|
||||
in B.hBox [wCompleted, wPriority, wDescription]
|
||||
-}
|
||||
|
||||
--type Editor = B.Editor String RName
|
||||
--type TaskList = B.List RName LTask
|
||||
|
||||
{- Rendering -}
|
||||
|
||||
renderLTask :: Maybe (B.Editor String RName) -> Bool -> LTask -> B.Widget RName
|
||||
renderLTask _ False (LTask _ t) = B.str $ formatTask t
|
||||
renderLTask Nothing True (LTask _ t) = B.str $ formatTask t
|
||||
renderLTask (Just edit) True _ = B.renderEditor (B.str . unlines) True edit
|
||||
|
||||
renderTaskList :: UIState -> B.Widget RName
|
||||
renderTaskList s =
|
||||
let inFocus = B.focusGetCurrent (focus s) == Just BRTaskList
|
||||
in B.renderList (renderLTask (taskEdit s)) inFocus (taskList s)
|
||||
|
||||
{- Editing tasks -}
|
||||
|
||||
toEditText :: Task -> String
|
||||
toEditText Task{taskPriority=Nothing, taskDescription=d} = d
|
||||
toEditText Task{taskPriority=Just p, taskDescription=d} = formatPriority p ++ " " ++ d
|
||||
|
||||
pEditText :: Parser (Maybe Priority, String)
|
||||
pEditText = (,) <$> maybeParse (andSpace pPriority) <*> untilEndOfLine
|
||||
|
||||
parseEditText :: String -> Either (ParseError Char Void) (Maybe Priority, String)
|
||||
parseEditText = parse pEditText "edited task"
|
||||
|
||||
{- Updating state -}
|
||||
|
||||
startEdit :: UIState -> UIState
|
||||
startEdit s =
|
||||
case B.listSelectedElement (taskList s) of
|
||||
Nothing -> s
|
||||
Just (_, LTask _ t) ->
|
||||
let edit = B.editor RTaskEdit (Just 1) (toEditText t)
|
||||
in s{taskEdit=Just edit}
|
||||
|
||||
finishEdit :: UIState -> UIState
|
||||
finishEdit s@UIState{taskEdit=Just edit} =
|
||||
case B.getEditContents edit of
|
||||
[line] -> case parseEditText line of
|
||||
Right (prio, desc) ->
|
||||
let changeTask (LTask n t) = LTask n t{taskPriority=prio, taskDescription=desc}
|
||||
newList = B.listModify changeTask (taskList s)
|
||||
in s{taskList=newList, taskEdit=Nothing}
|
||||
|
||||
Left parseError -> s{errorPopup=Just $ popup "Parse error" (parseErrorTextPretty parseError)}
|
||||
_ -> s{errorPopup=Just $ popup "Empty editor" "Enter a task description."}
|
||||
finishEdit s = s
|
||||
|
||||
updateEditor :: B.Editor String RName -> VTY.Event -> B.EventM RName (B.Editor String RName)
|
||||
updateEditor edit (VTY.EvKey VTY.KHome []) = pure $ B.applyEdit T.gotoBOL edit
|
||||
updateEditor edit (VTY.EvKey VTY.KEnd []) = pure $ B.applyEdit T.gotoEOL edit
|
||||
updateEditor edit e = B.handleEditorEvent e edit
|
||||
|
||||
|
||||
updateTaskList :: UIState -> B.BrickEvent RName () -> B.EventM RName (B.Next UIState)
|
||||
-- Exit application
|
||||
updateTaskList s@UIState{taskEdit=Nothing} (B.VtyEvent (VTY.EvKey VTY.KEsc [])) = B.halt s
|
||||
|
||||
{-
|
||||
-- Test stuff
|
||||
updateTaskList s e = do
|
||||
let changeTask (LTask n t) = LTask n t{taskDescription=show e}
|
||||
newList = B.listModify changeTask (taskList s)
|
||||
B.continue s{taskList=newList}
|
||||
-}
|
||||
|
||||
-- Scroll focus
|
||||
updateTaskList s (B.VtyEvent (VTY.EvKey VTY.KBackTab [])) = B.continue $ bigFocusPrev s
|
||||
updateTaskList s (B.VtyEvent (VTY.EvKey (VTY.KChar '\t') [])) = B.continue $ bigFocusNext s
|
||||
-- Start editing the current task
|
||||
updateTaskList s@UIState{taskEdit=Nothing} (B.VtyEvent (VTY.EvKey (VTY.KChar 'e') [])) = B.continue $ startEdit s
|
||||
-- Update the task list
|
||||
updateTaskList s@UIState{taskEdit=Nothing} (B.VtyEvent e) = do
|
||||
newList <- B.handleListEventVi B.handleListEvent e (taskList s)
|
||||
B.continue s{taskList=newList}
|
||||
-- Exit the editor (losing all changes)
|
||||
updateTaskList s@UIState{taskEdit=Just _} (B.VtyEvent (VTY.EvKey VTY.KEsc [])) = B.continue $ s{taskEdit=Nothing}
|
||||
-- Exit the editor (keeping all changes)
|
||||
updateTaskList s@UIState{taskEdit=Just _} (B.VtyEvent (VTY.EvKey VTY.KEnter [])) = B.continue $ finishEdit s
|
||||
-- Update the editor
|
||||
updateTaskList s@UIState{taskEdit=Just edit} (B.VtyEvent e) = do
|
||||
newTaskEdit <- updateEditor edit e
|
||||
B.continue s{taskEdit=Just newTaskEdit}
|
||||
-- Catch everything else
|
||||
updateTaskList s _ = B.halt s
|
||||
--updateTaskList list (Just editor) (B.VtyEvent e) = (,) <$> const list <*> B.handleEditorEvent e editor
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
module TaskMachine.UI.TopBar where
|
||||
|
||||
import qualified Brick as B
|
||||
|
||||
placeholderTopBar :: B.Widget n
|
||||
placeholderTopBar = B.str "Prune | Reload | Search: " B.<+> B.vLimit 1 (B.fill '_')
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
-- | A collection of types necessary for the UI.
|
||||
--
|
||||
-- These were put in a separate module to avoid an import cycle.
|
||||
|
||||
module TaskMachine.UI.Types
|
||||
( RName(..)
|
||||
, BigRing(..)
|
||||
, SmallRing(..)
|
||||
-- * Popups
|
||||
, Popup
|
||||
, popup
|
||||
, renderPopup
|
||||
, handlePopupEvent
|
||||
-- * UI state
|
||||
, UIState(..)
|
||||
, startUIState
|
||||
, bigFocusNext, bigFocusPrev
|
||||
, smallFocusNext, smallFocusPrev
|
||||
, defaultTheme
|
||||
) where
|
||||
|
||||
import qualified Brick as B
|
||||
import qualified Brick.Focus as B
|
||||
import qualified Brick.Themes as B
|
||||
import qualified Brick.Widgets.Dialog 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 TaskMachine.LTask
|
||||
|
||||
-- | Resource names
|
||||
data RName
|
||||
= RSearchEdit
|
||||
| RTaskList
|
||||
| RTaskEdit
|
||||
| RNewEdit
|
||||
deriving (Eq, Show, Ord)
|
||||
|
||||
data BigRing
|
||||
= BRTopBar
|
||||
| BRTaskList
|
||||
| BRNewTask
|
||||
deriving (Eq)
|
||||
|
||||
data SmallRing
|
||||
= SRPrune
|
||||
| SRReload
|
||||
| SRSearch
|
||||
deriving (Eq)
|
||||
|
||||
{- Popup -}
|
||||
|
||||
data Popup = Popup (B.Dialog ()) (B.Widget RName)
|
||||
|
||||
popup :: String -> String -> Popup
|
||||
popup title content =
|
||||
let dialog = B.dialog (Just title) (Just (0,[("OK",())])) 70 -- with a min terminal width of 80
|
||||
widget = B.str content
|
||||
in Popup dialog widget
|
||||
|
||||
renderPopup :: Popup -> B.Widget RName
|
||||
renderPopup (Popup dialog widget) = B.renderDialog dialog widget
|
||||
|
||||
handlePopupEvent :: VTY.Event -> Popup -> B.EventM RName Popup
|
||||
handlePopupEvent e (Popup dialog widget) = Popup <$> B.handleDialogEvent e dialog <*> pure widget
|
||||
|
||||
{- UI state -}
|
||||
|
||||
-- | The state of the program and UI
|
||||
data UIState = UIState
|
||||
{ focus :: B.FocusRing BigRing
|
||||
-- ^ 'B.FocusRing' for tab navigation
|
||||
, focusTopBar :: B.FocusRing SmallRing
|
||||
-- ^ 'B.FocusRing' for the top bar, for ← and → arrow key navigation
|
||||
, errorPopup :: Maybe Popup
|
||||
|
||||
-- TOP BAR
|
||||
, searchEdit :: B.Editor String RName
|
||||
-- ^ Content of the search field
|
||||
|
||||
-- TASK LIST
|
||||
, taskList :: B.List RName LTask
|
||||
-- ^ List to display tasks
|
||||
, invisibleTasks :: V.Vector LTask
|
||||
-- ^ All tasks that aren't displayed in the taskList due to search filters
|
||||
, taskEdit :: Maybe (B.Editor String RName)
|
||||
-- ^ Task currently being edited
|
||||
|
||||
-- NEW TASK
|
||||
, newEdit :: B.Editor String RName
|
||||
-- ^ "New task" text field at the bottom
|
||||
}
|
||||
|
||||
-- | Create a starting UI state
|
||||
startUIState :: V.Vector LTask -> UIState
|
||||
startUIState ltasks = UIState
|
||||
{ focus = B.focusRing [BRTaskList, BRNewTask, BRTopBar]
|
||||
, focusTopBar = B.focusRing [SRPrune, SRReload, SRSearch]
|
||||
, errorPopup = Nothing
|
||||
, searchEdit = B.editor RSearchEdit (Just 1) ""
|
||||
, taskList = B.list RTaskList ltasks 1
|
||||
, invisibleTasks = V.empty
|
||||
, taskEdit = Nothing
|
||||
, newEdit = B.editor RNewEdit (Just 1) ""
|
||||
}
|
||||
|
||||
bigFocusNext :: UIState -> UIState
|
||||
bigFocusNext s = s{focus=B.focusNext (focus s)}
|
||||
|
||||
bigFocusPrev :: UIState -> UIState
|
||||
bigFocusPrev s = s{focus=B.focusPrev (focus s)}
|
||||
|
||||
smallFocusNext :: UIState -> UIState
|
||||
smallFocusNext s = s{focusTopBar=B.focusNext (focusTopBar s)}
|
||||
|
||||
smallFocusPrev :: UIState -> UIState
|
||||
smallFocusPrev s = s{focusTopBar=B.focusPrev (focusTopBar s)}
|
||||
|
||||
defaultTheme :: B.Theme
|
||||
defaultTheme = B.newTheme VTY.defAttr
|
||||
[ (B.dialogAttr, none)
|
||||
, (B.buttonAttr, none)
|
||||
, (B.buttonSelectedAttr, bg' VTY.blue)
|
||||
, (B.editAttr, none)
|
||||
, (B.editFocusedAttr, bg' VTY.blue)
|
||||
, (B.listAttr, none)
|
||||
, (B.listSelectedAttr, st' VTY.bold)
|
||||
, (B.listSelectedFocusedAttr, bg VTY.blue $ st' VTY.bold)
|
||||
, ("normal" , none)
|
||||
, ("normal" <> "priority", fg VTY.cyan $ st' VTY.bold)
|
||||
, ("normal" <> "priority" <> "A", fg VTY.red $ st' VTY.bold)
|
||||
, ("normal" <> "priority" <> "B", fg VTY.yellow $ st' VTY.bold)
|
||||
, ("normal" <> "priority" <> "C", fg VTY.green $ st' VTY.bold)
|
||||
, ("highlight", bg' VTY.blue)
|
||||
, ("highlight" <> "priority", bg VTY.blue $ fg VTY.cyan $ st' VTY.bold)
|
||||
, ("highlight" <> "priority" <> "A", bg VTY.blue $ fg VTY.red $ st' VTY.bold)
|
||||
, ("highlight" <> "priority" <> "B", bg VTY.blue $ fg VTY.yellow $ st' VTY.bold)
|
||||
, ("highlight" <> "priority" <> "C", bg VTY.blue $ fg VTY.green $ st' VTY.bold)
|
||||
]
|
||||
where
|
||||
fg = flip VTY.withForeColor
|
||||
bg = flip VTY.withBackColor
|
||||
--st = flip VTY.withStyle
|
||||
--fg' = VTY.withForeColor none
|
||||
bg' = VTY.withBackColor none
|
||||
st' = VTY.withStyle none
|
||||
none = VTY.defAttr
|
||||
Loading…
Add table
Add a link
Reference in a new issue