ui: solidified register screen, added themes
- register screen: - smarter width-sensitive layout, with multi-commodity amounts on one line - items are sorted in date order - jumps to the latest item by default, with consistent scroll position - more prerendering, might speed up movement/paging slightly - themes! --theme to select, --help to list (current themes: default, terminal, greenterm) - border tweaks - dropped side borders, added side padding
This commit is contained in:
		
							parent
							
								
									3a7a5d6035
								
							
						
					
					
						commit
						e7aa150e52
					
				| @ -5,6 +5,7 @@ Re-export the modules of the hledger-ui program. | ||||
| module Hledger.UI ( | ||||
|                      module Hledger.UI.Main, | ||||
|                      module Hledger.UI.Options, | ||||
|                      module Hledger.UI.Theme, | ||||
|                      tests_Hledger_UI | ||||
|               ) | ||||
| where | ||||
| @ -12,6 +13,7 @@ import Test.HUnit | ||||
| 
 | ||||
| import Hledger.UI.Main | ||||
| import Hledger.UI.Options | ||||
| import Hledger.UI.Theme | ||||
| 
 | ||||
| tests_Hledger_UI :: Test | ||||
| tests_Hledger_UI = TestList | ||||
|  | ||||
| @ -14,16 +14,17 @@ import Data.List | ||||
| -- import Data.Monoid              --  | ||||
| import Data.Time.Calendar (Day) | ||||
| import qualified Data.Vector as V | ||||
| import qualified Graphics.Vty as Vty | ||||
| import Graphics.Vty as Vty | ||||
| import Brick | ||||
| import Brick.Widgets.List | ||||
| import Brick.Widgets.Border | ||||
| import Brick.Widgets.Center | ||||
| -- import Brick.Widgets.Border | ||||
| -- import Brick.Widgets.Center | ||||
| 
 | ||||
| import Hledger | ||||
| import Hledger.Cli hiding (progname,prognameandversion,green) | ||||
| -- import Hledger.Cli.Options (defaultBalanceLineFormat) | ||||
| import Hledger.UI.Options | ||||
| -- import Hledger.UI.Theme | ||||
| import Hledger.UI.UITypes | ||||
| import Hledger.UI.UIUtils | ||||
| import qualified Hledger.UI.RegisterScreen2 as RS2 (screen) | ||||
| @ -47,7 +48,10 @@ initAccountsScreen d args st@AppState{aopts=opts, ajournal=j, aScreen=s@Accounts | ||||
|              --{query_=unwords' $ locArgs l} | ||||
|         ropts = (reportopts_ cliopts) | ||||
|                 {no_elide_=True} | ||||
|                 {query_=unwords' args} | ||||
|                 { | ||||
|                   query_=unwords' args, | ||||
|                   balancetype_=HistoricalBalance -- XXX balanceReport doesn't respect this yet | ||||
|                 } | ||||
|         cliopts = cliopts_ opts | ||||
| initAccountsScreen _ _ _ = error "init function called with wrong screen type, should not happen" | ||||
| 
 | ||||
| @ -59,15 +63,7 @@ drawAccountsScreen st@AppState{aScreen=AccountsScreen{asState=is}} = [ui] | ||||
|                   Nothing -> "-" | ||||
|                   Just i -> show (i + 1)) | ||||
|       total = str $ show $ length $ is^.listElementsL | ||||
|       box = borderWithLabel label $ | ||||
|             -- hLimit 25 $ | ||||
|             -- vLimit 15 $ | ||||
|             renderList is (drawAccountsItem fmt) | ||||
|       ui = box | ||||
|       _ui = vCenter $ vBox [ hCenter box | ||||
|                             , str " " | ||||
|                             , hCenter $ str "Press Esc to exit." | ||||
|                             ] | ||||
| 
 | ||||
|       items = listElements is | ||||
|       flat = flat_ $ reportopts_ $ cliopts_ $ aopts st | ||||
|       acctcolwidth = maximum $ | ||||
| @ -82,16 +78,17 @@ drawAccountsScreen st@AppState{aScreen=AccountsScreen{asState=is}} = [ui] | ||||
|              , FormatField False (Just 40) Nothing TotalField | ||||
|              ] | ||||
| 
 | ||||
|       ui = defaultLayout label $ renderList is (drawAccountsItem fmt) | ||||
| 
 | ||||
| drawAccountsScreen _ = error "draw function called with wrong screen type, should not happen" | ||||
| 
 | ||||
| drawAccountsItem :: StringFormat -> Bool -> BalanceReportItem -> Widget | ||||
| drawAccountsItem fmt sel item = | ||||
|     let selStr i = if sel | ||||
|                    then withAttr customAttr (str $ showitem i) | ||||
|                    else str $ showitem i | ||||
|         showitem = intercalate "\n" . balanceReportItemAsText defreportopts fmt | ||||
|     in | ||||
|      selStr item | ||||
| drawAccountsItem fmt _sel item = | ||||
|   Widget Greedy Fixed $ do | ||||
|     -- c <- getContext | ||||
|     let | ||||
|       showitem = intercalate "\n" . balanceReportItemAsText defreportopts fmt | ||||
|     render $ str $ showitem item | ||||
| 
 | ||||
| handleAccountsScreen :: AppState -> Vty.Event -> EventM (Next AppState) | ||||
| handleAccountsScreen st@AppState{aScreen=scr@AccountsScreen{asState=is}} e = do | ||||
| @ -104,8 +101,9 @@ handleAccountsScreen st@AppState{aScreen=scr@AccountsScreen{asState=is}} e = do | ||||
|         Vty.EvKey (Vty.KChar 'q') [] -> halt st | ||||
|         Vty.EvKey (Vty.KLeft) []     -> continue $ popScreen st | ||||
|         Vty.EvKey (Vty.KRight) []    -> do | ||||
|           (w,h) <- getViewportSize "accounts" | ||||
|           continue $ screenEnter d args RS2.screen{rs2Size=(w,h)} st | ||||
|           let st' = screenEnter d args RS2.screen st | ||||
|           vScrollToBeginning $ viewportScroll "register" | ||||
|           continue st' | ||||
|           where | ||||
|             args = case listSelectedElement is of | ||||
|                     Just (_, ((acct, _, _), _)) -> ["acct:"++accountNameToAccountRegex acct] | ||||
|  | ||||
| @ -12,10 +12,11 @@ module Hledger.UI.Main where | ||||
| -- import Control.Applicative | ||||
| -- import Control.Lens ((^.)) | ||||
| import Control.Monad | ||||
| -- import Control.Monad.IO.Class (liftIO) | ||||
| -- import Data.Default | ||||
| -- import Data.Monoid              --  | ||||
| -- import Data.List | ||||
| -- import Data.Maybe | ||||
| import Data.Maybe | ||||
| -- import Data.Time.Calendar | ||||
| -- import Safe | ||||
| import System.Exit | ||||
| @ -27,19 +28,14 @@ import Hledger | ||||
| import Hledger.Cli hiding (progname,prognameandversion,green) | ||||
| import Hledger.UI.Options | ||||
| import Hledger.UI.UITypes | ||||
| import Hledger.UI.UIUtils | ||||
| -- import Hledger.UI.UIUtils | ||||
| import Hledger.UI.Theme | ||||
| import Hledger.UI.AccountsScreen as AS | ||||
| -- import Hledger.UI.RegisterScreen as RS | ||||
| import Hledger.UI.RegisterScreen2 as RS2 | ||||
| -- import Hledger.UI.RegisterScreen2 as RS2 | ||||
| 
 | ||||
| ---------------------------------------------------------------------- | ||||
| 
 | ||||
| -- | The available screens. | ||||
| appScreens = [ | ||||
|    AS.screen | ||||
|   ,RS2.screen | ||||
|   ] | ||||
| 
 | ||||
| main :: IO () | ||||
| main = do | ||||
|   opts <- getHledgerUIOpts | ||||
| @ -65,8 +61,10 @@ runBrickUi opts j = do | ||||
|   d <- getCurrentDay | ||||
| 
 | ||||
|   let | ||||
|     theme = maybe defaultTheme (fromMaybe defaultTheme . getTheme) $ | ||||
|             maybestringopt "theme" $ rawopts_ $ cliopts_ opts | ||||
|     args = words' $ query_ $ reportopts_ $ cliopts_ opts | ||||
|     scr = head appScreens | ||||
|     scr = AS.screen | ||||
|     st = (sInitFn scr) d args | ||||
|          AppState{ | ||||
|             aopts=opts | ||||
| @ -80,10 +78,11 @@ runBrickUi opts j = do | ||||
|     app = App { | ||||
|         appLiftVtyEvent = id | ||||
|       , appStartEvent   = return | ||||
|       , appAttrMap      = const customAttrMap | ||||
|       , appAttrMap      = const theme | ||||
|       , appChooseCursor = showFirstCursor | ||||
|       , appHandleEvent  = \st ev -> (sHandleFn $ aScreen st) st ev | ||||
|       , appDraw         = \st -> (sDrawFn $ aScreen st) st | ||||
|       } | ||||
| 
 | ||||
|   void $ defaultMain app st | ||||
| 
 | ||||
|  | ||||
| @ -5,10 +5,12 @@ | ||||
| 
 | ||||
| module Hledger.UI.Options | ||||
| where | ||||
| import Data.List (intercalate) | ||||
| import System.Console.CmdArgs | ||||
| import System.Console.CmdArgs.Explicit | ||||
| 
 | ||||
| import Hledger.Cli hiding (progname,version,prognameandversion) | ||||
| import Hledger.UI.Theme (themeNames) | ||||
| 
 | ||||
| progname, version :: String | ||||
| progname = "hledger-ui" | ||||
| @ -25,6 +27,7 @@ uiflags = [ | ||||
|   ,flagNone ["flat"] (\opts -> setboolopt "flat" opts) "show full account names, unindented" | ||||
|   ,flagReq ["drop"] (\s opts -> Right $ setopt "drop" s opts) "N" "with --flat, omit this many leading account name components" | ||||
|   ,flagReq  ["format"] (\s opts -> Right $ setopt "format" s opts) "FORMATSTR" "use this custom line format" | ||||
|   ,flagReq  ["theme"] (\s opts -> Right $ setopt "theme" s opts) "THEME" ("use this custom display theme ("++intercalate ", " themeNames++")") | ||||
|   ,flagNone ["no-elide"] (\opts -> setboolopt "no-elide" opts) "no eliding at all, stronger than --empty" | ||||
|   -- ,flagNone ["no-total"] (\opts -> setboolopt "no-total" opts) "don't show the final total" | ||||
|  ] | ||||
| @ -66,6 +69,10 @@ toUIOpts rawopts = do | ||||
| checkUIOpts :: UIOpts -> IO UIOpts | ||||
| checkUIOpts opts = do | ||||
|   checkCliOpts $ cliopts_ opts | ||||
|   case maybestringopt "theme" $ rawopts_ $ cliopts_ opts of | ||||
|     Just t | not $ elem t themeNames -> | ||||
|       optserror $ "invalid theme name: "++t | ||||
|     _ -> return () | ||||
|   return opts | ||||
| 
 | ||||
| getHledgerUIOpts :: IO UIOpts | ||||
|  | ||||
| @ -7,6 +7,7 @@ module Hledger.UI.RegisterScreen | ||||
| where | ||||
| 
 | ||||
| import Control.Lens ((^.)) | ||||
| -- import Control.Monad.IO.Class (liftIO) | ||||
| import Data.List | ||||
| import Data.Time.Calendar (Day) | ||||
| import qualified Data.Vector as V | ||||
| @ -42,7 +43,7 @@ initRegisterScreen d args st@AppState{aopts=opts, ajournal=j, aScreen=s@Register | ||||
|              -- query_="cur:\\$"} -- XXX limit to one commodity to ensure one-line items | ||||
|              --{query_=unwords' $ locArgs l} | ||||
|         ropts = (reportopts_ cliopts) | ||||
|                 {query_=unwords' args} | ||||
|                 { query_=unwords' args } | ||||
|         cliopts = cliopts_ opts | ||||
| initRegisterScreen _ _ _ = error "init function called with wrong screen type, should not happen" | ||||
| 
 | ||||
| @ -72,12 +73,17 @@ drawRegisterScreen _ = error "draw function called with wrong screen type, shoul | ||||
| 
 | ||||
| drawRegisterItem :: Bool -> PostingsReportItem -> Widget | ||||
| drawRegisterItem sel item = | ||||
| 
 | ||||
|   -- (w,_) <- getViewportSize "register" -- getCurrentViewportSize | ||||
|   -- st@AppState{aopts=opts} <- getAppState | ||||
|   -- let opts' = opts{width_=Just $ show w} | ||||
| 
 | ||||
|   let selStr i = if sel | ||||
|                  then withAttr customAttr (str $ showitem i) | ||||
|                  then {- withAttr selectedAttr -} str $ showitem i | ||||
|                  else str $ showitem i | ||||
|       showitem (_,_,_,p,b) = | ||||
|         intercalate ", " $ map strip $ lines $  | ||||
|         postingsReportItemAsText defcliopts $ | ||||
|         postingsReportItemAsText defcliopts{width_=Just "160"} $ -- XXX | ||||
|         mkpostingsReportItem True True PrimaryDate Nothing p b | ||||
|       -- fmt = BottomAligned [ | ||||
|       --     FormatField False (Just 20) Nothing TotalField | ||||
| @ -89,7 +95,7 @@ drawRegisterItem sel item = | ||||
|    selStr item | ||||
| 
 | ||||
| handleRegisterScreen :: AppState -> Vty.Event -> EventM (Next AppState) | ||||
| handleRegisterScreen st@AppState{aScreen=s@RegisterScreen{rsState=is}} e = | ||||
| handleRegisterScreen st@AppState{aopts=_opts,aScreen=s@RegisterScreen{rsState=is}} e = do | ||||
|   case e of | ||||
|     Vty.EvKey Vty.KEsc []        -> halt st | ||||
|     Vty.EvKey (Vty.KChar 'q') [] -> halt st | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| -- The register screen, showing account postings, like the CLI register command. | ||||
| 
 | ||||
| {-# LANGUAGE OverloadedStrings #-} | ||||
| {-# LANGUAGE OverloadedStrings, FlexibleContexts #-} | ||||
| 
 | ||||
| module Hledger.UI.RegisterScreen2 | ||||
|  (screen) | ||||
| @ -8,111 +8,147 @@ where | ||||
| 
 | ||||
| import Control.Lens ((^.)) | ||||
| -- import Control.Monad.IO.Class (liftIO) | ||||
| import Data.List | ||||
| -- import Data.List | ||||
| import Data.List.Split (splitOn) | ||||
| -- import Data.Maybe | ||||
| import Data.Time.Calendar (Day) | ||||
| import qualified Data.Vector as V | ||||
| import qualified Graphics.Vty as Vty | ||||
| import Graphics.Vty as Vty | ||||
| import Brick | ||||
| import Brick.Widgets.List | ||||
| import Brick.Widgets.Border | ||||
| import Brick.Widgets.Center | ||||
| -- import Brick.Widgets.Border | ||||
| -- import Brick.Widgets.Border.Style | ||||
| -- import Brick.Widgets.Center | ||||
| -- import Text.Printf | ||||
| 
 | ||||
| import Hledger | ||||
| import Hledger.Cli hiding (progname,prognameandversion,green) | ||||
| import Hledger.UI.Options | ||||
| -- import Hledger.UI.Theme | ||||
| import Hledger.UI.UITypes | ||||
| import Hledger.UI.UIUtils | ||||
| 
 | ||||
| screen = RegisterScreen2{ | ||||
|    rs2State  = list "register" V.empty 1 | ||||
|   ,rs2Size   = (0,0) | ||||
|   ,sInitFn    = initRegisterScreen2 | ||||
|   ,sDrawFn    = drawRegisterScreen2 | ||||
|   ,sHandleFn = handleRegisterScreen2 | ||||
|   } | ||||
| 
 | ||||
| initRegisterScreen2 :: Day -> [String] -> AppState -> AppState | ||||
| initRegisterScreen2 d args st@AppState{aopts=opts, ajournal=j, aScreen=s@RegisterScreen2{rs2Size=size}} = | ||||
|   st{aScreen=s{rs2State=is'}} | ||||
| initRegisterScreen2 d args st@AppState{aopts=opts, ajournal=j, aScreen=s@RegisterScreen2{}} = | ||||
|   st{aScreen=s{rs2State=l}} | ||||
|   where | ||||
|     is' = | ||||
|       -- listMoveTo (length items) $ | ||||
|       list (Name "register") (V.fromList items') 1 | ||||
|     -- gather arguments and queries | ||||
|     ropts = (reportopts_ $ cliopts_ opts) | ||||
|             { | ||||
|               query_=unwords' args, | ||||
|               balancetype_=HistoricalBalance | ||||
|             } | ||||
|     -- XXX temp | ||||
|     curacct = drop 5 $ head args -- should be "acct:..."  | ||||
|     thisacctq = Acct $ curacct -- XXX why is this excluding subs: accountNameToAccountRegex curacct | ||||
|     q = queryFromOpts d ropts | ||||
|          -- query_="cur:\\$"} -- XXX limit to one commodity to ensure one-line items | ||||
|          --{query_=unwords' $ locArgs l} | ||||
| 
 | ||||
|     -- XXX temporary hack: include saved viewport size in list elements | ||||
|     -- for element draw function | ||||
|     items' = zip (repeat size) items | ||||
|     (_label,items) = accountTransactionsReport ropts j thisacctq q | ||||
|       where | ||||
|         -- XXX temp | ||||
|         curacct = drop 5 $ head args -- should be "acct:..."  | ||||
|         thisacctq = Acct $ curacct -- XXX why is this excluding subs: accountNameToAccountRegex curacct | ||||
|     -- run a transactions report, most recent last | ||||
|     (_label,items') = accountTransactionsReport ropts j thisacctq q | ||||
|     items = reverse items' | ||||
| 
 | ||||
|     -- pre-render all items; these will be the List elements. This helps calculate column widths. | ||||
|     displayitem (_, t, _issplit, otheracctsstr, change, bal) = | ||||
|       (showDate $ tdate t | ||||
|       ,tdescription t | ||||
|       ,case splitOn ", " otheracctsstr of | ||||
|         [s] -> s | ||||
|         _   -> "<split>" | ||||
|       ,showMixedAmountOneLineWithoutPrice change | ||||
|       ,showMixedAmountOneLineWithoutPrice bal | ||||
|       ) | ||||
|     displayitems = map displayitem items | ||||
| 
 | ||||
|     -- build the List, moving the selection to the end | ||||
|     l = listMoveTo (length items) $ | ||||
|         list (Name "register") (V.fromList displayitems) 1 | ||||
| 
 | ||||
|         -- (listName someList) | ||||
| 
 | ||||
|         q = queryFromOpts d ropts | ||||
|              -- query_="cur:\\$"} -- XXX limit to one commodity to ensure one-line items | ||||
|              --{query_=unwords' $ locArgs l} | ||||
|         ropts = (reportopts_ cliopts) | ||||
|                 {query_=unwords' args} | ||||
|         cliopts = cliopts_ opts | ||||
| initRegisterScreen2 _ _ _ = error "init function called with wrong screen type, should not happen" | ||||
| 
 | ||||
| drawRegisterScreen2 :: AppState -> [Widget] | ||||
| drawRegisterScreen2 AppState{aopts=_opts, aScreen=RegisterScreen2{rs2State=is}} = [ui] | ||||
| drawRegisterScreen2 AppState{aopts=_opts, aScreen=RegisterScreen2{rs2State=l}} = [ui] | ||||
|   where | ||||
|     label = str "Transaction " | ||||
|             <+> cur | ||||
|             <+> str " of " | ||||
|             <+> total | ||||
|             <+> str " to/from this account" -- " <+> str query <+> "and subaccounts" | ||||
|     cur = str $ case is^.(listSelectedL) of | ||||
|     cur = str $ case l^.listSelectedL of | ||||
|                  Nothing -> "-" | ||||
|                  Just i -> show (i + 1) | ||||
|     total = str $ show $ length $ is^.(listElementsL) | ||||
|     total = str $ show $ length displayitems | ||||
|     displayitems = V.toList $ l^.listElementsL | ||||
| 
 | ||||
|     -- query = query_ $ reportopts_ $ cliopts_ opts | ||||
|     box = borderWithLabel label $ | ||||
|           -- hLimit 25 $ | ||||
|           -- vLimit 15 $ | ||||
|           renderList is drawRegisterItem | ||||
|     ui = box | ||||
|     _ui = vCenter $ vBox [ hCenter box | ||||
|                           , str " " | ||||
|                           , hCenter $ str "Press Esc to exit." | ||||
|                           ] | ||||
| 
 | ||||
|     ui = Widget Greedy Greedy $ do | ||||
| 
 | ||||
|       -- calculate column widths, based on current available width | ||||
|       c <- getContext | ||||
|       let | ||||
|         totalwidth = c^.availWidthL - 2 -- XXX trimmed.. for the margin ? | ||||
| 
 | ||||
|         -- the date column is fixed width | ||||
|         datewidth = 10 | ||||
| 
 | ||||
|         -- multi-commodity amounts rendered on one line can be | ||||
|         -- arbitrarily wide.  Give the two amounts as much space as | ||||
|         -- they need, while reserving a minimum of space for other | ||||
|         -- columns and whitespace.  If they don't get all they need, | ||||
|         -- allocate it to them proportionally to their maximum widths. | ||||
|         maxamtswidth = max 0 (totalwidth - 21) | ||||
|         changewidth' = maximum' $ map (length . fourth5) displayitems | ||||
|         balwidth' = maximum' $ map (length . fifth5) displayitems | ||||
|         changewidthproportion = (changewidth' + balwidth') `div` changewidth' | ||||
|         maxchangewidth = maxamtswidth `div` changewidthproportion | ||||
|         maxbalwidth = maxamtswidth - maxchangewidth | ||||
|         changewidth = min maxchangewidth changewidth'  | ||||
|         balwidth = min maxbalwidth balwidth' | ||||
| 
 | ||||
|         -- assign the remaining space to the description and accounts columns | ||||
|         maxdescacctswidth = totalwidth - 17 - changewidth - balwidth | ||||
|         -- allocating proportionally. | ||||
|         -- descwidth' = maximum' $ map (length . second5) displayitems | ||||
|         -- acctswidth' = maximum' $ map (length . third5) displayitems | ||||
|         -- descwidthproportion = (descwidth' + acctswidth') `div` descwidth' | ||||
|         -- maxdescwidth = min (maxdescacctswidth - 7) (maxdescacctswidth `div` descwidthproportion) | ||||
|         -- maxacctswidth = maxdescacctswidth - maxdescwidth | ||||
|         -- descwidth = min maxdescwidth descwidth'  | ||||
|         -- acctswidth = min maxacctswidth acctswidth' | ||||
|         -- allocating equally. | ||||
|         descwidth = maxdescacctswidth `div` 2 | ||||
|         acctswidth = maxdescacctswidth - descwidth | ||||
| 
 | ||||
|         colwidths = (datewidth,descwidth,acctswidth,changewidth,balwidth) | ||||
| 
 | ||||
|       render $ defaultLayout label $ renderList l (drawRegisterItem colwidths) | ||||
| 
 | ||||
| drawRegisterScreen2 _ = error "draw function called with wrong screen type, should not happen" | ||||
| 
 | ||||
| drawRegisterItem :: Bool -> ((Int,Int), AccountTransactionsReportItem) -> Widget | ||||
| drawRegisterItem sel ((w,_h),item) = | ||||
| 
 | ||||
|   -- (w,_) <- getViewportSize "register" -- getCurrentViewportSize | ||||
|   -- st@AppState{aopts=opts} <- getAppState | ||||
|   -- let opts' = opts{width_=Just $ show w} | ||||
| 
 | ||||
|   let selStr i = if sel | ||||
|                  then withAttr customAttr (str $ showitem i) | ||||
|                  else str $ showitem i | ||||
|       showitem (_origt,t,split,acctsstr,postedamt,totalamt) = | ||||
|         -- make a fake posting to render | ||||
|         let p = nullposting{ | ||||
|                   pdate=Just $ tdate t | ||||
|                  ,paccount=if split then intercalate ", " acctnames ++" (split)" else acctsstr | ||||
|                     -- XXX elideAccountName doesn't elide combined split names well | ||||
|                  ,pamount=postedamt | ||||
|                  ,ptransaction=Just t | ||||
|                  } | ||||
|             acctnames = nub $ sort $ splitOn ", " acctsstr -- XXX | ||||
|         in | ||||
|          intercalate ", " $ map strip $ lines $  | ||||
|          postingsReportItemAsText defcliopts{width_=Just (show w)} $ | ||||
|          mkpostingsReportItem True True PrimaryDate Nothing p totalamt | ||||
|       -- fmt = BottomAligned [ | ||||
|       --     FormatField False (Just 20) Nothing TotalField | ||||
|       --   , FormatLiteral "  " | ||||
|       --   , FormatField True (Just 2) Nothing DepthSpacerField | ||||
|       --   , FormatField True Nothing Nothing AccountField | ||||
|       --   ] | ||||
|   in | ||||
|    selStr item | ||||
| drawRegisterItem :: (Int,Int,Int,Int,Int) -> Bool -> (String,String,String,String,String) -> Widget | ||||
| drawRegisterItem (datewidth,descwidth,acctswidth,changewidth,balwidth) _sel (date,desc,accts,change,bal) = | ||||
|   Widget Greedy Fixed $ do | ||||
|     render $ | ||||
|       str (padright datewidth $ elideRight datewidth date) <+> | ||||
|       str " " <+> | ||||
|       str (padright descwidth $ elideRight descwidth desc) <+> | ||||
|       str "  " <+> | ||||
|       str (padright acctswidth $ elideLeft acctswidth $ accts) <+> | ||||
|       str "  " <+> | ||||
|       str (padleft changewidth $ elideLeft changewidth change) <+> | ||||
|       str "  " <+> | ||||
|       str (padleft balwidth $ elideLeft balwidth bal) | ||||
| 
 | ||||
| handleRegisterScreen2 :: AppState -> Vty.Event -> EventM (Next AppState) | ||||
| handleRegisterScreen2 st@AppState{aopts=_opts,aScreen=s@RegisterScreen2{rs2State=is}} e = do | ||||
|  | ||||
| @ -1,34 +1,99 @@ | ||||
| ---------------------------------------------------------------------- | ||||
| -- Theme        | ||||
| -- the all-important theming engine! | ||||
| -- | The all-important theming engine! | ||||
| -- | ||||
| -- Cf | ||||
| -- https://hackage.haskell.org/package/vty/docs/Graphics-Vty-Attributes.html | ||||
| -- http://hackage.haskell.org/package/brick/docs/Brick-AttrMap.html | ||||
| -- http://hackage.haskell.org/package/brick-0.1/docs/Brick-Util.html | ||||
| -- http://hackage.haskell.org/package/brick-0.1/docs/Brick-Widgets-Core.html#g:5 | ||||
| -- http://hackage.haskell.org/package/brick-0.1/docs/Brick-Widgets-Border.html | ||||
| 
 | ||||
| -- theme = Restrained | ||||
| -- -- theme = Colorful | ||||
| -- -- theme = Blood | ||||
| 
 | ||||
| -- data UITheme = Restrained | Colorful | Blood | ||||
| {-# LANGUAGE OverloadedStrings #-} | ||||
| 
 | ||||
| -- (defaultattr, | ||||
| --  currentlineattr, | ||||
| --  statusattr | ||||
| --  ) = case theme of | ||||
| --        Restrained -> (defAttr | ||||
| --                     ,defAttr `withStyle` bold | ||||
| --                     ,defAttr `withStyle` reverseVideo | ||||
| --                     ) | ||||
| --        Colorful   -> (defAttr `withStyle` reverseVideo | ||||
| --                     ,defAttr `withForeColor` white `withBackColor` red | ||||
| --                     ,defAttr `withForeColor` black `withBackColor` green | ||||
| --                     ) | ||||
| --        Blood      -> (defAttr `withStyle` reverseVideo | ||||
| --                     ,defAttr `withForeColor` white `withBackColor` red | ||||
| --                     ,defAttr `withStyle` reverseVideo | ||||
| --                     ) | ||||
| module Hledger.UI.Theme ( | ||||
|    defaultTheme | ||||
|   ,getTheme | ||||
|   ,themes | ||||
|   ,themeNames | ||||
|  ) where | ||||
| 
 | ||||
| -- -- halfbrightattr = defAttr `withStyle` dim | ||||
| -- -- reverseattr = defAttr `withStyle` reverseVideo | ||||
| -- -- redattr = defAttr `withForeColor` red | ||||
| -- -- greenattr = defAttr `withForeColor` green | ||||
| -- -- reverseredattr = defAttr `withStyle` reverseVideo `withForeColor` red | ||||
| -- -- reversegreenattr= defAttr `withStyle` reverseVideo `withForeColor` green | ||||
| import qualified Data.Map as M | ||||
| import Data.Maybe | ||||
| import Data.Monoid | ||||
| import Graphics.Vty | ||||
| import Brick | ||||
| import Brick.Widgets.Border | ||||
| import Brick.Widgets.List | ||||
| 
 | ||||
| defaultTheme :: AttrMap | ||||
| defaultTheme = fromMaybe (snd $ head themesList) $ getTheme "white" | ||||
|   -- the theme named here should exist; | ||||
|   -- otherwise it will take the first one from the list, | ||||
|   -- which must be non-empty. | ||||
| 
 | ||||
| -- | Look up the named theme, if it exists. | ||||
| getTheme :: String -> Maybe AttrMap | ||||
| getTheme name = M.lookup name themes | ||||
| 
 | ||||
| -- | A selection of named themes specifying terminal colours and styles. | ||||
| -- One of these is active at a time. | ||||
| -- | ||||
| -- A hledger-ui theme is a vty/brick AttrMap.  Each theme specifies a | ||||
| -- default style (Attr), plus extra styles which are applied when | ||||
| -- their (hierarchical) name matches the widget rendering context.  Eg | ||||
| -- when rendering a widget named "b" which is inside a widget named | ||||
| -- "a", the following styles will be applied if they exist: the | ||||
| -- default style, then a style named "a", and finally a style named | ||||
| -- "a" <> "b". | ||||
| -- | ||||
| themes :: M.Map String AttrMap | ||||
| themes = M.fromList themesList | ||||
| 
 | ||||
| themeNames :: [String] | ||||
| themeNames = map fst themesList | ||||
| 
 | ||||
| (&) = withStyle | ||||
| 
 | ||||
| themesList :: [(String, AttrMap)] | ||||
| themesList = [ | ||||
|   ("default", attrMap | ||||
|             (black `on` white & bold) [ -- default style for this theme | ||||
|               (borderAttr       , white `on` black), | ||||
|               -- ("normal"                , black `on` white), | ||||
|               ("list"                  , black `on` white),      -- regular list items | ||||
|               ("list" <> "selected"    , white `on` blue & bold) -- selected list items | ||||
|               -- ("list" <> "selected"     , black `on` brightYellow), | ||||
|               -- ("list" <> "accounts"  , white `on` brightGreen), | ||||
|               -- ("list" <> "amount"       , black `on` white & bold) | ||||
|               ]), | ||||
| 
 | ||||
|   ("terminal", attrMap | ||||
|             defAttr [  -- use the current terminal's default style | ||||
|               (borderAttr       , white `on` black), | ||||
|               -- ("normal"         , defAttr), | ||||
|               (listAttr         , defAttr), | ||||
|               (listSelectedAttr , defAttr & reverseVideo & bold) | ||||
|               -- ("status"         , defAttr & reverseVideo) | ||||
|               ]), | ||||
| 
 | ||||
|   ("greenterm", attrMap | ||||
|             (green `on` black) [ | ||||
|               -- (listAttr                  , green `on` black), | ||||
|               (listSelectedAttr          , black `on` green & bold) | ||||
|               ]) | ||||
|   -- ("colorful", attrMap | ||||
|   --           defAttr [ | ||||
|   --             (listAttr         , defAttr & reverseVideo), | ||||
|   --             (listSelectedAttr , defAttr `withForeColor` white `withBackColor` red) | ||||
|   --             -- ("status"         , defAttr `withForeColor` black `withBackColor` green) | ||||
|   --             ]) | ||||
| 
 | ||||
|   ] | ||||
| 
 | ||||
| -- halfbrightattr = defAttr & dim | ||||
| -- reverseattr = defAttr & reverseVideo | ||||
| -- redattr = defAttr `withForeColor` red | ||||
| -- greenattr = defAttr `withForeColor` green | ||||
| -- reverseredattr = defAttr & reverseVideo `withForeColor` red | ||||
| -- reversegreenattr= defAttr & reverseVideo `withForeColor` green | ||||
| 
 | ||||
|  | ||||
| @ -37,8 +37,7 @@ data Screen = | ||||
|     ,sDrawFn :: AppState -> [Widget] | ||||
|     } | ||||
|   | RegisterScreen2 { | ||||
|      rs2Size :: (Int,Int) -- ^ XXX prev screen's viewport size on entering this screen | ||||
|     ,rs2State :: List ((Int,Int), AccountTransactionsReportItem) | ||||
|      rs2State :: List (String,String,String,String,String) | ||||
|     ,sInitFn :: Day -> [String] -> AppState -> AppState | ||||
|     ,sHandleFn :: AppState -> V.Event -> EventM (Next AppState) | ||||
|     ,sDrawFn :: AppState -> [Widget] | ||||
|  | ||||
| @ -4,23 +4,27 @@ module Hledger.UI.UIUtils ( | ||||
|   pushScreen | ||||
|  ,popScreen | ||||
|  ,screenEnter | ||||
|  ,attrMap | ||||
|  ,customAttrMap | ||||
|  ,customAttr | ||||
|  ,getViewportSize | ||||
|  ,margin | ||||
|  ,withBorderAttr | ||||
|  ,topBottomBorderWithLabel | ||||
|  ,defaultLayout | ||||
|  ) where | ||||
| 
 | ||||
| import Control.Lens ((^.)) | ||||
| -- import Control.Monad | ||||
| import Control.Monad.IO.Class | ||||
| -- import Control.Monad.IO.Class | ||||
| -- import Data.Default | ||||
| import Data.Monoid              --  | ||||
| -- import Data.Monoid              --  | ||||
| import Data.Time.Calendar (Day) | ||||
| import qualified Graphics.Vty as V | ||||
| import Brick | ||||
| import Brick.Widgets.List | ||||
| -- import Brick.Widgets.List | ||||
| import Brick.Widgets.Border | ||||
| import Brick.Widgets.Border.Style | ||||
| import Graphics.Vty as Vty | ||||
| 
 | ||||
| import Hledger.UI.UITypes | ||||
| import Hledger.Utils (applyN) | ||||
| 
 | ||||
| pushScreen :: Screen -> AppState -> AppState | ||||
| pushScreen scr st = st{aPrevScreens=(aScreen st:aPrevScreens st) | ||||
| @ -43,15 +47,6 @@ screenEnter d args scr st = (sInitFn scr) d args $ | ||||
|                             pushScreen scr | ||||
|                             st | ||||
| 
 | ||||
| customAttrMap :: AttrMap | ||||
| customAttrMap = attrMap V.defAttr | ||||
|     [ (listAttr,            V.white `on` V.blue) | ||||
|     , (listSelectedAttr,    V.black `on` V.white) | ||||
|     -- , (customAttr,            fg V.cyan) | ||||
|     ] | ||||
| 
 | ||||
| customAttr :: AttrName | ||||
| 
 | ||||
| -- | In the EventM monad, get the named current viewport's width and height, | ||||
| -- or (0,0) if the named viewport is not found. | ||||
| getViewportSize :: Name -> EventM (Int,Int) | ||||
| @ -60,6 +55,55 @@ getViewportSize name = do | ||||
|   let (w,h) = case mvp of | ||||
|         Just vp -> vp ^. vpSize | ||||
|         Nothing -> (0,0) | ||||
|   -- liftIO $ putStrLn $ show (w,h) | ||||
|   return (w,h) | ||||
| 
 | ||||
| customAttr = listSelectedAttr <> "custom" | ||||
| defaultLayout label = | ||||
|   topBottomBorderWithLabel label . | ||||
|   margin 1 0 Nothing | ||||
|   -- margin 1 0 (Just white) | ||||
| 
 | ||||
| topBottomBorderWithLabel label = \wrapped -> | ||||
|   Widget Greedy Greedy $ do | ||||
|     c <- getContext | ||||
|     let (_w,h) = (c^.availWidthL, c^.availHeightL) | ||||
|         h' = h - 2 | ||||
|         wrapped' = vLimit (h') wrapped | ||||
|         debugmsg = | ||||
|           "" | ||||
|           -- "  debug: "++show (_w,h') | ||||
|     render $ | ||||
|       hBorderWithLabel (label <+> str debugmsg) | ||||
|       <=> | ||||
|       wrapped' | ||||
|       <=> | ||||
|       hBorder | ||||
| 
 | ||||
| -- | Wrap a widget in a margin with the given horizontal and vertical | ||||
| -- thickness, using the current background colour or the specified | ||||
| -- colour. XXX May disrupt border style of inner widgets. | ||||
| margin :: Int -> Int -> Maybe Color -> Widget -> Widget | ||||
| margin h v mcolour = \w -> | ||||
|   Widget Greedy Greedy $ do | ||||
|     c <- getContext | ||||
|     let w' = vLimit (c^.availHeightL - v*2) $ hLimit (c^.availWidthL - h*2) w | ||||
|         attr = maybe currentAttr (\c -> c `on` c) mcolour | ||||
|     render $ | ||||
|       withBorderAttr attr $ | ||||
|       withBorderStyle (borderStyleFromChar ' ') $ | ||||
|       applyN v (hBorder <=>) $ | ||||
|       applyN h (vBorder <+>) $ | ||||
|       applyN v (<=> hBorder) $ | ||||
|       applyN h (<+> vBorder) $ | ||||
|       w' | ||||
| 
 | ||||
|    -- withBorderAttr attr . | ||||
|    -- withBorderStyle (borderStyleFromChar ' ') . | ||||
|    -- applyN n border | ||||
| 
 | ||||
| withBorderAttr attr = updateAttrMap (applyAttrMappings [(borderAttr, attr)]) | ||||
| 
 | ||||
| -- _ui = vCenter $ vBox [ hCenter box | ||||
| --                       , str " " | ||||
| --                       , hCenter $ str "Press Esc to exit." | ||||
| --                       ] | ||||
|  | ||||
| @ -8,17 +8,31 @@ hledger-ui currently allows browsing the balance, register and print | ||||
| reports, with drill-down and scrolling. | ||||
| 
 | ||||
| 
 | ||||
| Backlog: | ||||
| show journal entries | ||||
| # HACKING | ||||
| 
 | ||||
| ## Backlog: | ||||
| ``` | ||||
| merge to master | ||||
|  brick release | ||||
| 
 | ||||
| make it more useful | ||||
|  register | ||||
|   simplify/remove unhelpful account names ? | ||||
|  show journal entries | ||||
|   transaction dialog / journal screen | ||||
|  bs/is/cf-ish reports | ||||
|  save custom reports | ||||
| 
 | ||||
| fix -H | ||||
| fix --drop | ||||
| track current account better | ||||
|  show it in register title | ||||
| track current query better | ||||
| search | ||||
| filter | ||||
| depth adjustment | ||||
| search in page | ||||
| adjust query | ||||
| adjust depth | ||||
| use color, selectable themes | ||||
| switch to next brick release | ||||
|  reg: use full width | ||||
|  reg2: find subaccounts' transactions better | ||||
|  keep cursor at bottom of screen if jumping to end | ||||
| add | ||||
| @ -28,3 +42,5 @@ reload | ||||
|  on screen change | ||||
|  on redraw | ||||
|  on file change | ||||
| 
 | ||||
| ``` | ||||
| @ -56,6 +56,7 @@ executable hledger-ui | ||||
|     , base >= 3 && < 5 | ||||
|     , brick | ||||
|     , cmdargs >= 0.8 | ||||
|     , containers | ||||
|     , data-default | ||||
|     , HUnit | ||||
|     , lens >= 4.12.3 && < 4.13 | ||||
| @ -74,6 +75,7 @@ executable hledger-ui | ||||
|       Hledger.UI | ||||
|       Hledger.UI.Main | ||||
|       Hledger.UI.Options | ||||
|       Hledger.UI.Theme | ||||
|       Hledger.UI.UITypes | ||||
|       Hledger.UI.UIUtils | ||||
|       Hledger.UI.AccountsScreen | ||||
|  | ||||
| @ -66,6 +66,7 @@ executables: | ||||
|       - hledger-lib == 0.26.98 | ||||
|       - base >= 3 && < 5 | ||||
|       - cmdargs >= 0.8 | ||||
|       - containers | ||||
|       - HUnit | ||||
|       - safe >= 0.2 | ||||
|       - split >= 0.1 && < 0.3 | ||||
|  | ||||
| @ -113,6 +113,10 @@ tests_postingsReportAsText = [ | ||||
| -- | ||||
| -- date and description are shown for the first posting of a transaction only. | ||||
| -- | ||||
| -- Returns a string which can be multi-line, eg if the running balance | ||||
| -- has multiple commodities. Does not yet support formatting control | ||||
| -- like balance reports. | ||||
| -- | ||||
| postingsReportItemAsText :: CliOpts -> PostingsReportItem -> String | ||||
| postingsReportItemAsText opts (mdate, menddate, mdesc, p, b) = | ||||
|   intercalate "\n" $ | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user