mirror of
https://github.com/jtdaugherty/brick.git
synced 2024-11-29 21:46:11 +03:00
Get rid of scrolling wrappers, replace with viewport abstraction and primitives
This commit is contained in:
parent
1a6cf1a660
commit
5aac657d2f
@ -24,7 +24,6 @@ library
|
|||||||
Brick.List
|
Brick.List
|
||||||
Brick.Main
|
Brick.Main
|
||||||
Brick.Render
|
Brick.Render
|
||||||
Brick.Scroll
|
|
||||||
Brick.Util
|
Brick.Util
|
||||||
other-modules:
|
other-modules:
|
||||||
Brick.Render.Internal
|
Brick.Render.Internal
|
||||||
|
@ -45,11 +45,11 @@ drawUI st = [a]
|
|||||||
vCenter $
|
vCenter $
|
||||||
(hCenter $ borderWithLabel bs bsName $
|
(hCenter $ borderWithLabel bs bsName $
|
||||||
(hLimit 25 (
|
(hLimit 25 (
|
||||||
(vLimit 1 $ useAttr (cyan `on` blue) $ withLens stEditor drawEditor)
|
(vLimit 1 $ useAttr (cyan `on` blue) $ drawEditor (st^.stEditor))
|
||||||
<<=
|
<<=
|
||||||
hBorder bs
|
hBorder bs
|
||||||
=>>
|
=>>
|
||||||
(vLimit 10 $ withLens stList drawList)
|
(vLimit 10 $ drawList (st^.stList))
|
||||||
)))
|
)))
|
||||||
<<=
|
<<=
|
||||||
(vLimit 1 $ vPad ' ')
|
(vLimit 1 $ vPad ' ')
|
||||||
|
@ -1,18 +1,23 @@
|
|||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
module Brick.Core
|
module Brick.Core
|
||||||
( Location(..)
|
( Location(Location)
|
||||||
|
, loc
|
||||||
, CursorName(..)
|
, CursorName(..)
|
||||||
, CursorLocation(..)
|
, CursorLocation(..)
|
||||||
, HandleEvent(..)
|
, HandleEvent(..)
|
||||||
, SetSize(..)
|
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
import Control.Lens
|
||||||
import Data.Monoid (Monoid(..))
|
import Data.Monoid (Monoid(..))
|
||||||
import Graphics.Vty (Event, DisplayRegion)
|
import Graphics.Vty (Event, DisplayRegion)
|
||||||
|
|
||||||
newtype Location = Location (Int, Int)
|
data Location = Location { _loc :: (Int, Int)
|
||||||
|
}
|
||||||
deriving Show
|
deriving Show
|
||||||
|
|
||||||
|
makeLenses ''Location
|
||||||
|
|
||||||
origin :: Location
|
origin :: Location
|
||||||
origin = Location (0, 0)
|
origin = Location (0, 0)
|
||||||
|
|
||||||
@ -31,6 +36,3 @@ data CursorLocation =
|
|||||||
|
|
||||||
class HandleEvent a where
|
class HandleEvent a where
|
||||||
handleEvent :: Event -> a -> a
|
handleEvent :: Event -> a -> a
|
||||||
|
|
||||||
class SetSize a where
|
|
||||||
setSize :: DisplayRegion -> a -> a
|
|
||||||
|
@ -5,20 +5,17 @@ module Brick.Edit
|
|||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Data.Default
|
|
||||||
import Data.Monoid ((<>))
|
import Data.Monoid ((<>))
|
||||||
import Graphics.Vty (Event(..), Key(..), Modifier(..))
|
import Graphics.Vty (Event(..), Key(..), Modifier(..))
|
||||||
|
|
||||||
import Brick.Core (Location(..), CursorName(..), HandleEvent(..), SetSize(..))
|
import Brick.Core (Location(..), CursorName(..), HandleEvent(..))
|
||||||
import Brick.Render
|
import Brick.Render
|
||||||
import Brick.Scroll (HScroll, hScroll, scrollToView)
|
|
||||||
import Brick.Util (clamp)
|
import Brick.Util (clamp)
|
||||||
|
|
||||||
data Editor =
|
data Editor =
|
||||||
Editor { editStr :: !String
|
Editor { editStr :: !String
|
||||||
, editCursorPos :: !Int
|
, editCursorPos :: !Int
|
||||||
, editorCursorName :: !CursorName
|
, editorCursorName :: !CursorName
|
||||||
, editorScroll :: !HScroll
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instance HandleEvent Editor where
|
instance HandleEvent Editor where
|
||||||
@ -37,8 +34,7 @@ instance HandleEvent Editor where
|
|||||||
editSetCursorPos :: Int -> Editor -> Editor
|
editSetCursorPos :: Int -> Editor -> Editor
|
||||||
editSetCursorPos pos e =
|
editSetCursorPos pos e =
|
||||||
let newCP = clamp 0 (length $ editStr e) pos
|
let newCP = clamp 0 (length $ editStr e) pos
|
||||||
in e { editorScroll = scrollToView (newCP, 1) (editorScroll e)
|
in e { editCursorPos = newCP
|
||||||
, editCursorPos = newCP
|
|
||||||
}
|
}
|
||||||
|
|
||||||
moveLeft :: Editor -> Editor
|
moveLeft :: Editor -> Editor
|
||||||
@ -70,7 +66,6 @@ insertChar :: Char -> Editor -> Editor
|
|||||||
insertChar c theEdit =
|
insertChar c theEdit =
|
||||||
theEdit { editStr = s
|
theEdit { editStr = s
|
||||||
, editCursorPos = newCursorPos
|
, editCursorPos = newCursorPos
|
||||||
, editorScroll = scrollToView (newCursorPos, 1) (editorScroll theEdit)
|
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
s = take n oldStr ++ [c] ++ drop n oldStr
|
s = take n oldStr ++ [c] ++ drop n oldStr
|
||||||
@ -78,17 +73,22 @@ insertChar c theEdit =
|
|||||||
newCursorPos = n + 1
|
newCursorPos = n + 1
|
||||||
oldStr = editStr theEdit
|
oldStr = editStr theEdit
|
||||||
|
|
||||||
instance SetSize Editor where
|
|
||||||
setSize sz e =
|
|
||||||
let updatedScroll = setSize sz $ editorScroll e
|
|
||||||
in e { editorScroll = scrollToView (editCursorPos e, 1) updatedScroll
|
|
||||||
}
|
|
||||||
|
|
||||||
editor :: CursorName -> String -> Editor
|
editor :: CursorName -> String -> Editor
|
||||||
editor cName s = Editor s (length s) cName def
|
editor cName s = Editor s (length s) cName
|
||||||
|
|
||||||
drawEditor :: Render Editor
|
drawEditor :: Editor -> Render Editor
|
||||||
drawEditor =
|
drawEditor e =
|
||||||
saveSize setSize $ hScroll editorScroll $ usingState $ \e ->
|
let cursorLoc = Location (cp, 0)
|
||||||
let cursorLoc = Location (editCursorPos e, 0)
|
cp = editCursorPos e
|
||||||
in showCursor (editorCursorName e) cursorLoc $ txt (editStr e) <<+ hPad ' '
|
s = editStr e
|
||||||
|
beforeCursor = take cp s
|
||||||
|
onCursor' = take 1 $ drop cp s
|
||||||
|
onCursor = if null onCursor' then " " else onCursor'
|
||||||
|
afterCursor = drop (cp + 1) s
|
||||||
|
in viewport "edit" Horizontal $
|
||||||
|
showCursor (editorCursorName e) cursorLoc $
|
||||||
|
hBox [ ( txt beforeCursor, High )
|
||||||
|
, ( visible $ txt onCursor, High )
|
||||||
|
, ( txt afterCursor, High )
|
||||||
|
, ( hPad ' ', Low )
|
||||||
|
]
|
||||||
|
@ -11,24 +11,19 @@ module Brick.List
|
|||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Control.Applicative ((<$>), (<|>))
|
import Control.Applicative ((<$>))
|
||||||
import Data.Default
|
import Data.Maybe (fromMaybe)
|
||||||
import Data.Maybe (fromMaybe, catMaybes)
|
import Graphics.Vty (Event(..), Key(..))
|
||||||
import Graphics.Vty (Event(..), Key(..), DisplayRegion)
|
|
||||||
import qualified Data.Map as M
|
|
||||||
|
|
||||||
import Brick.Core (HandleEvent(..), SetSize(..))
|
import Brick.Core (HandleEvent(..))
|
||||||
import Brick.Merge (maintainSel)
|
import Brick.Merge (maintainSel)
|
||||||
import Brick.Render
|
import Brick.Render
|
||||||
import Brick.Scroll (VScroll, vScroll, scrollToView)
|
|
||||||
import Brick.Util (clamp, for)
|
import Brick.Util (clamp, for)
|
||||||
|
|
||||||
data List e =
|
data List e =
|
||||||
List { listElements :: ![e]
|
List { listElements :: ![e]
|
||||||
, listElementDraw :: Bool -> e -> Render (List e)
|
, listElementDraw :: Bool -> e -> Render (List e)
|
||||||
, listSelected :: !(Maybe Int)
|
, listSelected :: !(Maybe Int)
|
||||||
, listScroll :: !VScroll
|
|
||||||
, listElementHeights :: M.Map Int Int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instance HandleEvent (List e) where
|
instance HandleEvent (List e) where
|
||||||
@ -39,37 +34,25 @@ instance HandleEvent (List e) where
|
|||||||
EvKey KDown [] -> moveDown
|
EvKey KDown [] -> moveDown
|
||||||
_ -> id
|
_ -> id
|
||||||
|
|
||||||
instance SetSize (List e) where
|
|
||||||
setSize sz l =
|
|
||||||
let updatedScroll = setSize sz $ listScroll l
|
|
||||||
in makeSelectedVisible $ l { listScroll = updatedScroll }
|
|
||||||
|
|
||||||
list :: (Bool -> e -> Render (List e)) -> [e] -> List e
|
list :: (Bool -> e -> Render (List e)) -> [e] -> List e
|
||||||
list draw es =
|
list draw es =
|
||||||
let selIndex = if null es then Nothing else Just 0
|
let selIndex = if null es then Nothing else Just 0
|
||||||
in List es draw selIndex def M.empty
|
in List es draw selIndex
|
||||||
|
|
||||||
listSetElementSize :: Int -> DisplayRegion -> List e -> List e
|
drawList :: List e -> Render (List e)
|
||||||
listSetElementSize i sz l =
|
drawList l = theList
|
||||||
l { listElementHeights = M.insert i (snd sz) (listElementHeights l)
|
|
||||||
}
|
|
||||||
|
|
||||||
drawList :: Render (List e)
|
|
||||||
drawList = theList
|
|
||||||
where
|
where
|
||||||
theList = saveSize setSize $
|
theList = viewport "list" Vertical $ body
|
||||||
vScroll listScroll $
|
|
||||||
ensure makeSelectedVisible body
|
|
||||||
|
|
||||||
body = usingState $ \l -> do
|
body = (vBox drawn <<= vPad ' ') <<+ hPad ' '
|
||||||
let es = listElements l
|
es = listElements l
|
||||||
drawn = for (zip [0..] es) $ \(i, e) ->
|
drawn = for (zip [0..] es) $ \(i, e) ->
|
||||||
let isSelected = Just i == listSelected l
|
let isSelected = Just i == listSelected l
|
||||||
elemRender = listElementDraw l isSelected e
|
elemRender = listElementDraw l isSelected e
|
||||||
in ( saveSize (listSetElementSize i) elemRender
|
makeVisible = if isSelected then visible else id
|
||||||
|
in ( makeVisible elemRender
|
||||||
, High
|
, High
|
||||||
)
|
)
|
||||||
(vBox drawn <<= vPad ' ') <<+ hPad ' '
|
|
||||||
|
|
||||||
listInsert :: Int -> e -> List e -> List e
|
listInsert :: Int -> e -> List e -> List e
|
||||||
listInsert pos e l =
|
listInsert pos e l =
|
||||||
@ -81,7 +64,7 @@ listInsert pos e l =
|
|||||||
then s + 1
|
then s + 1
|
||||||
else s
|
else s
|
||||||
(front, back) = splitAt safePos es
|
(front, back) = splitAt safePos es
|
||||||
in makeSelectedVisible $ l { listSelected = Just newSel
|
in l { listSelected = Just newSel
|
||||||
, listElements = front ++ (e : back)
|
, listElements = front ++ (e : back)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +79,7 @@ listRemove pos l | null es = l
|
|||||||
else s
|
else s
|
||||||
(front, back) = splitAt pos es
|
(front, back) = splitAt pos es
|
||||||
es' = front ++ tail back
|
es' = front ++ tail back
|
||||||
in makeSelectedVisible $ l { listSelected = if null es'
|
in l { listSelected = if null es'
|
||||||
then Nothing
|
then Nothing
|
||||||
else Just newSel
|
else Just newSel
|
||||||
, listElements = es'
|
, listElements = es'
|
||||||
@ -114,7 +97,7 @@ listReplace es' l | es' == es = l
|
|||||||
(_, True) -> Nothing
|
(_, True) -> Nothing
|
||||||
(True, False) -> Just 0
|
(True, False) -> Just 0
|
||||||
(False, False) -> Just (maintainSel es es' sel)
|
(False, False) -> Just (maintainSel es es' sel)
|
||||||
in makeSelectedVisible $ l { listSelected = newSel
|
in l { listSelected = newSel
|
||||||
, listElements = es'
|
, listElements = es'
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
@ -129,13 +112,13 @@ moveDown = moveBy 1
|
|||||||
moveBy :: Int -> List e -> List e
|
moveBy :: Int -> List e -> List e
|
||||||
moveBy amt l =
|
moveBy amt l =
|
||||||
let newSel = clamp 0 (length (listElements l) - 1) <$> (amt +) <$> listSelected l
|
let newSel = clamp 0 (length (listElements l) - 1) <$> (amt +) <$> listSelected l
|
||||||
in makeSelectedVisible $ l { listSelected = newSel }
|
in l { listSelected = newSel }
|
||||||
|
|
||||||
moveTo :: Int -> List e -> List e
|
moveTo :: Int -> List e -> List e
|
||||||
moveTo pos l =
|
moveTo pos l =
|
||||||
let len = length (listElements l)
|
let len = length (listElements l)
|
||||||
newSel = clamp 0 (len - 1) $ if pos < 0 then (len - pos) else pos
|
newSel = clamp 0 (len - 1) $ if pos < 0 then (len - pos) else pos
|
||||||
in makeSelectedVisible $ l { listSelected = if len > 0
|
in l { listSelected = if len > 0
|
||||||
then Just newSel
|
then Just newSel
|
||||||
else Nothing
|
else Nothing
|
||||||
}
|
}
|
||||||
@ -144,14 +127,3 @@ listSelectedElement :: List e -> Maybe (Int, e)
|
|||||||
listSelectedElement l = do
|
listSelectedElement l = do
|
||||||
sel <- listSelected l
|
sel <- listSelected l
|
||||||
return (sel, listElements l !! sel)
|
return (sel, listElements l !! sel)
|
||||||
|
|
||||||
makeSelectedVisible :: List e -> List e
|
|
||||||
makeSelectedVisible l =
|
|
||||||
let Just scrollTo = (listSelected l) <|> (Just 0)
|
|
||||||
heights = listElementHeights l
|
|
||||||
scrollTop = sum $ catMaybes $ (\k -> M.lookup k heights) <$> [0..scrollTo-1]
|
|
||||||
scrollBottom = case M.lookup scrollTo heights of
|
|
||||||
Nothing -> 1
|
|
||||||
Just k -> k
|
|
||||||
in l { listScroll = scrollToView (scrollTop, scrollBottom) (listScroll l)
|
|
||||||
}
|
|
||||||
|
@ -20,6 +20,7 @@ import Control.Monad (when, forever)
|
|||||||
import Control.Concurrent (forkIO, Chan, newChan, readChan, writeChan)
|
import Control.Concurrent (forkIO, Chan, newChan, readChan, writeChan)
|
||||||
import Data.Default
|
import Data.Default
|
||||||
import Data.Maybe (listToMaybe)
|
import Data.Maybe (listToMaybe)
|
||||||
|
import qualified Data.Map as M
|
||||||
import Graphics.Vty
|
import Graphics.Vty
|
||||||
( Vty
|
( Vty
|
||||||
, Picture(..)
|
, Picture(..)
|
||||||
@ -35,7 +36,7 @@ import Graphics.Vty
|
|||||||
import System.Exit (exitSuccess)
|
import System.Exit (exitSuccess)
|
||||||
|
|
||||||
import Brick.Render (Render)
|
import Brick.Render (Render)
|
||||||
import Brick.Render.Internal (renderFinal)
|
import Brick.Render.Internal (renderFinal, RenderState(..))
|
||||||
import Brick.Core (Location(..), CursorLocation(..))
|
import Brick.Core (Location(..), CursorLocation(..))
|
||||||
|
|
||||||
data App a e =
|
data App a e =
|
||||||
@ -61,11 +62,12 @@ simpleMain ls =
|
|||||||
in defaultMain app ()
|
in defaultMain app ()
|
||||||
|
|
||||||
defaultMainWithVty :: IO Vty -> App a Event -> a -> IO ()
|
defaultMainWithVty :: IO Vty -> App a Event -> a -> IO ()
|
||||||
defaultMainWithVty buildVty app initialState = do
|
defaultMainWithVty buildVty app initialAppState = do
|
||||||
|
let initialRS = RS M.empty
|
||||||
chan <- newChan
|
chan <- newChan
|
||||||
withVty buildVty $ \vty -> do
|
withVty buildVty $ \vty -> do
|
||||||
forkIO $ supplyVtyEvents vty id chan
|
forkIO $ supplyVtyEvents vty id chan
|
||||||
runVty vty chan app initialState
|
runVty vty chan app initialAppState initialRS
|
||||||
|
|
||||||
isResizeEvent :: Event -> Bool
|
isResizeEvent :: Event -> Bool
|
||||||
isResizeEvent (EvResize _ _) = True
|
isResizeEvent (EvResize _ _) = True
|
||||||
@ -80,28 +82,29 @@ supplyVtyEvents vty mkEvent chan =
|
|||||||
when (isResizeEvent e) $ writeChan chan $ mkEvent e
|
when (isResizeEvent e) $ writeChan chan $ mkEvent e
|
||||||
writeChan chan $ mkEvent e
|
writeChan chan $ mkEvent e
|
||||||
|
|
||||||
runVty :: Vty -> Chan e -> App a e -> a -> IO ()
|
runVty :: Vty -> Chan e -> App a e -> a -> RenderState -> IO ()
|
||||||
runVty vty chan app appState = do
|
runVty vty chan app appState rs = do
|
||||||
state' <- renderApp vty app appState
|
newRS <- renderApp vty app appState rs
|
||||||
e <- readChan chan
|
e <- readChan chan
|
||||||
appHandleEvent app e state' >>= runVty vty chan app
|
newAppState <- appHandleEvent app e appState
|
||||||
|
runVty vty chan app newAppState newRS
|
||||||
|
|
||||||
withVty :: IO Vty -> (Vty -> IO a) -> IO a
|
withVty :: IO Vty -> (Vty -> IO a) -> IO a
|
||||||
withVty buildVty useVty = do
|
withVty buildVty useVty = do
|
||||||
vty <- buildVty
|
vty <- buildVty
|
||||||
useVty vty `finally` shutdown vty
|
useVty vty `finally` shutdown vty
|
||||||
|
|
||||||
renderApp :: Vty -> App a e -> a -> IO a
|
renderApp :: Vty -> App a e -> a -> RenderState -> IO RenderState
|
||||||
renderApp vty app appState = do
|
renderApp vty app appState rs = do
|
||||||
sz <- displayBounds $ outputIface vty
|
sz <- displayBounds $ outputIface vty
|
||||||
let (newAppState, pic, theCursor) = renderFinal (appDraw app appState) sz (appChooseCursor app appState) appState
|
let (newRS, pic, theCursor) = renderFinal (appDraw app appState) sz (appChooseCursor app appState) rs
|
||||||
picWithCursor = case theCursor of
|
picWithCursor = case theCursor of
|
||||||
Nothing -> pic { picCursor = NoCursor }
|
Nothing -> pic { picCursor = NoCursor }
|
||||||
Just (CursorLocation (Location (w, h)) _) -> pic { picCursor = Cursor w h }
|
Just (CursorLocation (Location (w, h)) _) -> pic { picCursor = Cursor w h }
|
||||||
|
|
||||||
update vty picWithCursor
|
update vty picWithCursor
|
||||||
|
|
||||||
return newAppState
|
return newRS
|
||||||
|
|
||||||
neverShowCursor :: a -> [CursorLocation] -> Maybe CursorLocation
|
neverShowCursor :: a -> [CursorLocation] -> Maybe CursorLocation
|
||||||
neverShowCursor = const $ const Nothing
|
neverShowCursor = const $ const Nothing
|
||||||
|
@ -3,6 +3,7 @@ module Brick.Render
|
|||||||
, Priority(..)
|
, Priority(..)
|
||||||
, (=>>), (<<=), (<=>)
|
, (=>>), (<<=), (<=>)
|
||||||
, (+>>), (<<+), (<+>)
|
, (+>>), (<<+), (<+>)
|
||||||
|
, ViewportType(..)
|
||||||
|
|
||||||
, txt
|
, txt
|
||||||
, hPad
|
, hPad
|
||||||
@ -21,12 +22,10 @@ module Brick.Render
|
|||||||
, cropTopBy
|
, cropTopBy
|
||||||
, cropBottomBy
|
, cropBottomBy
|
||||||
, showCursor
|
, showCursor
|
||||||
, saveSize
|
|
||||||
, hRelease
|
, hRelease
|
||||||
, vRelease
|
, vRelease
|
||||||
, withLens
|
, viewport
|
||||||
, usingState
|
, visible
|
||||||
, ensure
|
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
@ -8,10 +8,14 @@ module Brick.Render.Internal
|
|||||||
, image
|
, image
|
||||||
, cursors
|
, cursors
|
||||||
|
|
||||||
|
, RenderState(..)
|
||||||
|
|
||||||
, Priority(..)
|
, Priority(..)
|
||||||
, renderFinal
|
, renderFinal
|
||||||
, Render
|
, Render
|
||||||
|
|
||||||
|
, ViewportType(..)
|
||||||
|
|
||||||
, txt
|
, txt
|
||||||
, hPad
|
, hPad
|
||||||
, vPad
|
, vPad
|
||||||
@ -29,33 +33,52 @@ module Brick.Render.Internal
|
|||||||
, cropTopBy
|
, cropTopBy
|
||||||
, cropBottomBy
|
, cropBottomBy
|
||||||
, showCursor
|
, showCursor
|
||||||
, saveSize
|
|
||||||
, hRelease
|
, hRelease
|
||||||
, vRelease
|
, vRelease
|
||||||
, withLens
|
, viewport
|
||||||
, usingState
|
, visible
|
||||||
, ensure
|
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Control.Applicative
|
import Control.Applicative
|
||||||
import Control.Lens (makeLenses, (^.), (.=), (.~), (&), (%~), to, _2)
|
import Control.Lens (makeLenses, (^.), (.~), (&), (%~), to, _1, _2)
|
||||||
|
import Control.Monad (when)
|
||||||
import Control.Monad.Trans.State.Lazy
|
import Control.Monad.Trans.State.Lazy
|
||||||
import Control.Monad.Trans.Reader
|
import Control.Monad.Trans.Reader
|
||||||
import Control.Monad.Trans.Class (lift)
|
import Control.Monad.Trans.Class (lift)
|
||||||
import Data.Default
|
import Data.Default
|
||||||
|
import Data.Monoid ((<>), mempty)
|
||||||
|
import qualified Data.Map as M
|
||||||
import qualified Data.Function as DF
|
import qualified Data.Function as DF
|
||||||
import Data.List (sortBy)
|
import Data.List (sortBy)
|
||||||
import Control.Lens (Lens')
|
import Control.Lens (Lens')
|
||||||
import Data.String (IsString(..))
|
import Data.String (IsString(..))
|
||||||
import qualified Graphics.Vty as V
|
import qualified Graphics.Vty as V
|
||||||
|
|
||||||
import Brick.Core (Location(..), CursorLocation(..), CursorName(..))
|
import Brick.Core (Location(..), loc, CursorLocation(..), CursorName(..))
|
||||||
import Brick.Util (clOffset, for)
|
import Brick.Util (clOffset, for)
|
||||||
|
|
||||||
|
import qualified Debug.Trace as D
|
||||||
|
|
||||||
|
data VisibilityRequest =
|
||||||
|
VR { _vrPosition :: Location
|
||||||
|
, _vrSize :: V.DisplayRegion
|
||||||
|
}
|
||||||
|
deriving Show
|
||||||
|
|
||||||
|
data ViewportType = Vertical | Horizontal deriving Show
|
||||||
|
|
||||||
|
data Viewport =
|
||||||
|
VP { _vpLeft :: Int
|
||||||
|
, _vpTop :: Int
|
||||||
|
, _vpSize :: V.DisplayRegion
|
||||||
|
}
|
||||||
|
deriving Show
|
||||||
|
|
||||||
data Result =
|
data Result =
|
||||||
Result { _image :: V.Image
|
Result { _image :: V.Image
|
||||||
, _cursors :: [CursorLocation]
|
, _cursors :: [CursorLocation]
|
||||||
|
, _visibilityRequests :: [VisibilityRequest]
|
||||||
}
|
}
|
||||||
deriving Show
|
deriving Show
|
||||||
|
|
||||||
@ -65,33 +88,45 @@ data Context =
|
|||||||
, _h :: Int
|
, _h :: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
makeLenses ''Result
|
|
||||||
makeLenses ''Context
|
|
||||||
|
|
||||||
data Priority = High | Low
|
data Priority = High | Low
|
||||||
deriving (Show, Eq)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
type Render a = ReaderT Context (State a) Result
|
type Render a = ReaderT Context (State RenderState) Result
|
||||||
|
|
||||||
|
data RenderState =
|
||||||
|
RS { _viewportMap :: M.Map String Viewport
|
||||||
|
}
|
||||||
|
|
||||||
|
makeLenses ''Result
|
||||||
|
makeLenses ''Context
|
||||||
|
makeLenses ''VisibilityRequest
|
||||||
|
makeLenses ''Viewport
|
||||||
|
makeLenses ''RenderState
|
||||||
|
|
||||||
instance IsString (Render a) where
|
instance IsString (Render a) where
|
||||||
fromString = txt
|
fromString = txt
|
||||||
|
|
||||||
instance Default Result where
|
instance Default Result where
|
||||||
def = Result V.emptyImage []
|
def = Result V.emptyImage [] []
|
||||||
|
|
||||||
renderFinal :: [Render a]
|
renderFinal :: [Render a]
|
||||||
-> V.DisplayRegion
|
-> V.DisplayRegion
|
||||||
-> ([CursorLocation] -> Maybe CursorLocation)
|
-> ([CursorLocation] -> Maybe CursorLocation)
|
||||||
-> a
|
-> RenderState
|
||||||
-> (a, V.Picture, Maybe CursorLocation)
|
-> (RenderState, V.Picture, Maybe CursorLocation)
|
||||||
renderFinal layerRenders sz chooseCursor st = (newState, pic, theCursor)
|
renderFinal layerRenders sz chooseCursor rs = (newRS, pic, theCursor)
|
||||||
where
|
where
|
||||||
(layerResults, newState) = flip runState st $ sequence $ (\p -> runReaderT p ctx) <$> layerRenders
|
(layerResults, newRS) = flip runState rs $ sequence $ (\p -> runReaderT p ctx) <$> layerRenders
|
||||||
ctx = Context V.defAttr (fst sz) (snd sz)
|
ctx = Context V.defAttr (fst sz) (snd sz)
|
||||||
pic = V.picForLayers $ uncurry V.resize sz <$> (^.image) <$> layerResults
|
pic = V.picForLayers $ uncurry V.resize sz <$> (^.image) <$> layerResults
|
||||||
layerCursors = (^.cursors) <$> layerResults
|
layerCursors = (^.cursors) <$> layerResults
|
||||||
theCursor = chooseCursor $ concat layerCursors
|
theCursor = chooseCursor $ concat layerCursors
|
||||||
|
|
||||||
|
addVisibilityOffset :: Location -> Result -> Result
|
||||||
|
addVisibilityOffset off r =
|
||||||
|
let addOffset vrs = (& vrPosition %~ (off <>)) <$> vrs
|
||||||
|
in r & visibilityRequests %~ addOffset
|
||||||
|
|
||||||
addCursorOffset :: Location -> Result -> Result
|
addCursorOffset :: Location -> Result -> Result
|
||||||
addCursorOffset off r =
|
addCursorOffset off r =
|
||||||
let onlyVisible = filter isVisible
|
let onlyVisible = filter isVisible
|
||||||
@ -155,12 +190,16 @@ hBox pairs = do
|
|||||||
allResults = snd <$> rendered
|
allResults = snd <$> rendered
|
||||||
allImages = (^.image) <$> allResults
|
allImages = (^.image) <$> allResults
|
||||||
allWidths = V.imageWidth <$> allImages
|
allWidths = V.imageWidth <$> allImages
|
||||||
|
allTranslatedVRs = for (zip [0..] allResults) $ \(i, result) ->
|
||||||
|
let off = Location (offWidth, 0)
|
||||||
|
offWidth = sum $ take i allWidths
|
||||||
|
in (addVisibilityOffset off result)^.visibilityRequests
|
||||||
allTranslatedCursors = for (zip [0..] allResults) $ \(i, result) ->
|
allTranslatedCursors = for (zip [0..] allResults) $ \(i, result) ->
|
||||||
let off = Location (offWidth, 0)
|
let off = Location (offWidth, 0)
|
||||||
offWidth = sum $ take i allWidths
|
offWidth = sum $ take i allWidths
|
||||||
in (addCursorOffset off result)^.cursors
|
in (addCursorOffset off result)^.cursors
|
||||||
|
|
||||||
return $ Result (V.horizCat allImages) (concat allTranslatedCursors)
|
return $ Result (V.horizCat allImages) (concat allTranslatedCursors) (concat allTranslatedVRs)
|
||||||
|
|
||||||
vBox :: [(Render a, Priority)] -> Render a
|
vBox :: [(Render a, Priority)] -> Render a
|
||||||
vBox pairs = do
|
vBox pairs = do
|
||||||
@ -189,18 +228,22 @@ vBox pairs = do
|
|||||||
allResults = snd <$> rendered
|
allResults = snd <$> rendered
|
||||||
allImages = (^.image) <$> allResults
|
allImages = (^.image) <$> allResults
|
||||||
allHeights = V.imageHeight <$> allImages
|
allHeights = V.imageHeight <$> allImages
|
||||||
|
allTranslatedVRs = for (zip [0..] allResults) $ \(i, result) ->
|
||||||
|
let off = Location (0, offHeight)
|
||||||
|
offHeight = sum $ take i allHeights
|
||||||
|
in (addVisibilityOffset off result)^.visibilityRequests
|
||||||
allTranslatedCursors = for (zip [0..] allResults) $ \(i, result) ->
|
allTranslatedCursors = for (zip [0..] allResults) $ \(i, result) ->
|
||||||
let off = Location (0, offHeight)
|
let off = Location (0, offHeight)
|
||||||
offHeight = sum $ take i allHeights
|
offHeight = sum $ take i allHeights
|
||||||
in (addCursorOffset off result)^.cursors
|
in (addCursorOffset off result)^.cursors
|
||||||
|
|
||||||
return $ Result (V.vertCat allImages) (concat allTranslatedCursors)
|
return $ Result (V.vertCat allImages) (concat allTranslatedCursors) (concat allTranslatedVRs)
|
||||||
|
|
||||||
-- xxx crop cursors
|
-- xxx crop cursors and VRs
|
||||||
hLimit :: Int -> Render a -> Render a
|
hLimit :: Int -> Render a -> Render a
|
||||||
hLimit w' = withReaderT (& w .~ w')
|
hLimit w' = withReaderT (& w .~ w')
|
||||||
|
|
||||||
-- xxx crop cursors
|
-- xxx crop cursors and VRs
|
||||||
vLimit :: Int -> Render a -> Render a
|
vLimit :: Int -> Render a -> Render a
|
||||||
vLimit h' = withReaderT (& h .~ h')
|
vLimit h' = withReaderT (& h .~ h')
|
||||||
|
|
||||||
@ -219,6 +262,7 @@ translate (Location (tw,th)) p = do
|
|||||||
result <- p
|
result <- p
|
||||||
c <- ask
|
c <- ask
|
||||||
return $ addCursorOffset (Location (tw, th)) $
|
return $ addCursorOffset (Location (tw, th)) $
|
||||||
|
addVisibilityOffset (Location (tw, th)) $
|
||||||
result & image %~ (V.crop (c^.w) (c^.h) . V.translate tw th)
|
result & image %~ (V.crop (c^.w) (c^.h) . V.translate tw th)
|
||||||
|
|
||||||
cropLeftBy :: Int -> Render a -> Render a
|
cropLeftBy :: Int -> Render a -> Render a
|
||||||
@ -226,14 +270,16 @@ cropLeftBy cols p = do
|
|||||||
result <- p
|
result <- p
|
||||||
let amt = V.imageWidth (result^.image) - cols
|
let amt = V.imageWidth (result^.image) - cols
|
||||||
cropped img = if amt < 0 then V.emptyImage else V.cropLeft amt img
|
cropped img = if amt < 0 then V.emptyImage else V.cropLeft amt img
|
||||||
return $ addCursorOffset (Location (-1 * cols, 0)) $ result & image %~ cropped
|
return $ addCursorOffset (Location (-1 * cols, 0)) $
|
||||||
|
addVisibilityOffset (Location (-1 * cols, 0)) $
|
||||||
|
result & image %~ cropped
|
||||||
|
|
||||||
cropRightBy :: Int -> Render a -> Render a
|
cropRightBy :: Int -> Render a -> Render a
|
||||||
cropRightBy cols p = do
|
cropRightBy cols p = do
|
||||||
result <- p
|
result <- p
|
||||||
let amt = V.imageWidth (result^.image) - cols
|
let amt = V.imageWidth (result^.image) - cols
|
||||||
cropped img = if amt < 0 then V.emptyImage else V.cropRight amt img
|
cropped img = if amt < 0 then V.emptyImage else V.cropRight amt img
|
||||||
-- xxx cursors
|
-- xxx cursors / VRs
|
||||||
return $ result & image %~ cropped
|
return $ result & image %~ cropped
|
||||||
|
|
||||||
cropTopBy :: Int -> Render a -> Render a
|
cropTopBy :: Int -> Render a -> Render a
|
||||||
@ -241,28 +287,22 @@ cropTopBy rows p = do
|
|||||||
result <- p
|
result <- p
|
||||||
let amt = V.imageHeight (result^.image) - rows
|
let amt = V.imageHeight (result^.image) - rows
|
||||||
cropped img = if amt < 0 then V.emptyImage else V.cropTop amt img
|
cropped img = if amt < 0 then V.emptyImage else V.cropTop amt img
|
||||||
return $ addCursorOffset (Location (0, -1 * rows)) $ result & image %~ cropped
|
return $ addCursorOffset (Location (0, -1 * rows)) $
|
||||||
|
addVisibilityOffset (Location (0, -1 * rows)) $
|
||||||
|
result & image %~ cropped
|
||||||
|
|
||||||
cropBottomBy :: Int -> Render a -> Render a
|
cropBottomBy :: Int -> Render a -> Render a
|
||||||
cropBottomBy rows p = do
|
cropBottomBy rows p = do
|
||||||
result <- p
|
result <- p
|
||||||
let amt = V.imageHeight (result^.image) - rows
|
let amt = V.imageHeight (result^.image) - rows
|
||||||
cropped img = if amt < 0 then V.emptyImage else V.cropBottom amt img
|
cropped img = if amt < 0 then V.emptyImage else V.cropBottom amt img
|
||||||
-- xxx crop cursors
|
-- xxx crop cursors / VRs
|
||||||
return $ result & image %~ cropped
|
return $ result & image %~ cropped
|
||||||
|
|
||||||
showCursor :: CursorName -> Location -> Render a -> Render a
|
showCursor :: CursorName -> Location -> Render a -> Render a
|
||||||
showCursor n loc p = do
|
showCursor n cloc p = do
|
||||||
result <- p
|
result <- p
|
||||||
return $ result & cursors %~ (CursorLocation loc (Just n):)
|
return $ result & cursors %~ (CursorLocation cloc (Just n):)
|
||||||
|
|
||||||
saveSize :: (V.DisplayRegion -> a -> a) -> Render a -> Render a
|
|
||||||
saveSize sizeSetter p = do
|
|
||||||
result <- p
|
|
||||||
let img = result^.image
|
|
||||||
imgSz = (V.imageWidth img, V.imageHeight img)
|
|
||||||
lift $ modify (sizeSetter imgSz)
|
|
||||||
return result
|
|
||||||
|
|
||||||
hRelease :: Render a -> Render a
|
hRelease :: Render a -> Render a
|
||||||
hRelease = withReaderT (& w .~ unrestricted) --- NB
|
hRelease = withReaderT (& w .~ unrestricted) --- NB
|
||||||
@ -270,20 +310,79 @@ hRelease = withReaderT (& w .~ unrestricted) --- NB
|
|||||||
vRelease :: Render a -> Render a
|
vRelease :: Render a -> Render a
|
||||||
vRelease = withReaderT (& h .~ unrestricted) --- NB
|
vRelease = withReaderT (& h .~ unrestricted) --- NB
|
||||||
|
|
||||||
withLens :: (Lens' a b) -> Render b -> Render a
|
viewport :: String -> ViewportType -> Render a -> Render a
|
||||||
withLens target p = do
|
viewport vpname typ p = do
|
||||||
outerState <- lift get
|
-- First, update the viewport size.
|
||||||
let oldInnerState = outerState^.target
|
|
||||||
c <- ask
|
c <- ask
|
||||||
let (result, newInnerState) = runState (runReaderT p c) oldInnerState
|
let newVp = VP 0 0 newSize
|
||||||
target .= newInnerState
|
newSize = (c^.w, c^.h)
|
||||||
return result
|
doInsert (Just vp) = Just $ vp & vpSize .~ newSize
|
||||||
|
doInsert Nothing = Just newVp
|
||||||
|
|
||||||
usingState :: (a -> Render a) -> Render a
|
lift $ modify (& viewportMap %~ (M.alter doInsert vpname))
|
||||||
usingState f = (lift get) >>= (\a -> f a)
|
|
||||||
|
|
||||||
ensure :: (a -> a) -> Render a -> Render a
|
-- Then render the sub-rendering with the rendering layout
|
||||||
ensure f p = do
|
-- constraint released
|
||||||
|
let release = case typ of
|
||||||
|
Vertical -> vRelease
|
||||||
|
Horizontal -> hRelease
|
||||||
|
|
||||||
|
initialResult <- release p
|
||||||
|
|
||||||
|
-- If the sub-rendering requested visibility, update the scroll
|
||||||
|
-- state accordingly
|
||||||
|
when (not $ null $ initialResult^.visibilityRequests) $ do
|
||||||
|
Just vp <- lift $ gets $ (^.viewportMap.to (M.lookup vpname))
|
||||||
|
-- XXX for now, just permit one request but we could permit
|
||||||
|
-- many by computing the bounding rectangle over the submitted
|
||||||
|
-- requests.
|
||||||
|
let [rq] = initialResult^.visibilityRequests
|
||||||
|
updatedVp = scrollToView typ rq vp
|
||||||
|
lift $ D.trace (show (vpname, rq, newVp)) $ modify (& viewportMap %~ (M.insert vpname updatedVp))
|
||||||
|
|
||||||
|
-- Get the viewport state now that it has been updated.
|
||||||
|
Just vp <- lift $ gets (M.lookup vpname . (^.viewportMap))
|
||||||
|
|
||||||
|
-- Then perform a translation of the sub-rendering to fit into the
|
||||||
|
-- viewport
|
||||||
|
translated <- translate (Location (-1 * vp^.vpLeft, -1 * vp^.vpTop)) $ return initialResult
|
||||||
|
|
||||||
|
-- Return the translated result with the visibility requests
|
||||||
|
-- discarded
|
||||||
|
return $ translated & visibilityRequests .~ mempty
|
||||||
|
|
||||||
|
scrollToView :: ViewportType -> VisibilityRequest -> Viewport -> Viewport
|
||||||
|
scrollToView typ rq vp = vp & theStart .~ newStart
|
||||||
|
where
|
||||||
|
theStart :: Lens' Viewport Int
|
||||||
|
theStart = case typ of
|
||||||
|
Horizontal -> vpLeft
|
||||||
|
Vertical -> vpTop
|
||||||
|
theSize = case typ of
|
||||||
|
Horizontal -> vpSize._1
|
||||||
|
Vertical -> vpSize._2
|
||||||
|
reqStart = case typ of
|
||||||
|
Horizontal -> rq^.vrPosition.loc._1
|
||||||
|
Vertical -> rq^.vrPosition.loc._2
|
||||||
|
reqSize = case typ of
|
||||||
|
Horizontal -> rq^.vrSize._1
|
||||||
|
Vertical -> rq^.vrSize._2
|
||||||
|
|
||||||
|
curStart = vp^.theStart
|
||||||
|
curEnd = curStart + vp^.theSize
|
||||||
|
|
||||||
|
reqEnd = reqStart + reqSize
|
||||||
|
newStart :: Int
|
||||||
|
newStart = if reqStart < curStart
|
||||||
|
then reqStart
|
||||||
|
else if reqStart > curEnd || reqEnd > curEnd
|
||||||
|
then reqEnd - vp^.theSize
|
||||||
|
else curStart
|
||||||
|
|
||||||
|
visible :: Render a -> Render a
|
||||||
|
visible p = do
|
||||||
result <- p
|
result <- p
|
||||||
lift $ modify f
|
let imageSize = ( result^.image.to V.imageWidth
|
||||||
return result
|
, result^.image.to V.imageHeight
|
||||||
|
)
|
||||||
|
return $ result & visibilityRequests %~ (VR (Location (0, 0)) imageSize :)
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
module Brick.Scroll
|
|
||||||
( Scrollable(..)
|
|
||||||
|
|
||||||
, VScroll
|
|
||||||
, vScroll
|
|
||||||
|
|
||||||
, HScroll
|
|
||||||
, hScroll
|
|
||||||
|
|
||||||
, scrollToView
|
|
||||||
)
|
|
||||||
where
|
|
||||||
|
|
||||||
import Data.Default
|
|
||||||
|
|
||||||
import Brick.Core (SetSize(..), Location(..))
|
|
||||||
import Brick.Render
|
|
||||||
|
|
||||||
data HScroll =
|
|
||||||
HScroll { scrollLeft :: !Int
|
|
||||||
, scrollWidth :: !Int
|
|
||||||
}
|
|
||||||
|
|
||||||
data VScroll =
|
|
||||||
VScroll { scrollTop :: !Int
|
|
||||||
, scrollHeight :: !Int
|
|
||||||
}
|
|
||||||
|
|
||||||
instance SetSize VScroll where
|
|
||||||
setSize (_, h) vs = vs { scrollHeight = h }
|
|
||||||
|
|
||||||
instance SetSize HScroll where
|
|
||||||
setSize (w, _) hs = hs { scrollWidth = w }
|
|
||||||
|
|
||||||
instance Default HScroll where
|
|
||||||
def = HScroll 0 0
|
|
||||||
|
|
||||||
instance Default VScroll where
|
|
||||||
def = VScroll 0 0
|
|
||||||
|
|
||||||
class Scrollable a where
|
|
||||||
setScrollStart :: Int -> a -> a
|
|
||||||
scrollStart :: a -> Int
|
|
||||||
scrollSize :: a -> Int
|
|
||||||
|
|
||||||
instance Scrollable HScroll where
|
|
||||||
setScrollStart col hs = hs { scrollLeft = col }
|
|
||||||
scrollStart = scrollLeft
|
|
||||||
scrollSize = scrollWidth
|
|
||||||
|
|
||||||
instance Scrollable VScroll where
|
|
||||||
setScrollStart row vs = vs { scrollTop = row }
|
|
||||||
scrollStart = scrollTop
|
|
||||||
scrollSize = scrollHeight
|
|
||||||
|
|
||||||
vScroll :: (a -> VScroll) -> Render a -> Render a
|
|
||||||
vScroll f p = do
|
|
||||||
result <- vRelease p
|
|
||||||
usingState $ \s ->
|
|
||||||
let vs = f s
|
|
||||||
in translate (Location (0, -1 * scrollStart vs)) $ return result
|
|
||||||
|
|
||||||
hScroll :: (a -> HScroll) -> Render a -> Render a
|
|
||||||
hScroll f p = do
|
|
||||||
result <- hRelease p
|
|
||||||
usingState $ \s ->
|
|
||||||
let hs = f s
|
|
||||||
in translate (Location (-1 * scrollStart hs, 0)) $ return result
|
|
||||||
|
|
||||||
scrollToView :: (Scrollable a) => (Int, Int) -> a -> a
|
|
||||||
scrollToView (reqStart, reqSize) s =
|
|
||||||
setScrollStart newStart s
|
|
||||||
where
|
|
||||||
curEnd = curStart + scrollSize s
|
|
||||||
curStart = scrollStart s
|
|
||||||
reqEnd = reqStart + reqSize
|
|
||||||
newStart = if reqStart < curStart
|
|
||||||
then reqStart
|
|
||||||
else if reqStart > curEnd || reqEnd > curEnd
|
|
||||||
then reqEnd - scrollSize s
|
|
||||||
else curStart
|
|
Loading…
Reference in New Issue
Block a user