brick 0.14 → 0.15
raw patch · 12 files changed
+301/−100 lines, 12 filesdep ~text-zipperPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: text-zipper
API changes (from Hackage documentation)
- Brick.Types: column :: TerminalLocation a => a -> Int
- Brick.Types: columnL :: TerminalLocation a => Lens' a Int
- Brick.Types: row :: TerminalLocation a => a -> Int
- Brick.Types: rowL :: TerminalLocation a => Lens' a Int
+ Brick.Types: [extentName] :: Extent n -> n
+ Brick.Types: [extentOffset] :: Extent n -> Location
+ Brick.Types: [extentSize] :: Extent n -> (Int, Int)
+ Brick.Types: [extentUpperLeft] :: Extent n -> Location
+ Brick.Types: locationColumn :: TerminalLocation a => a -> Int
+ Brick.Types: locationColumnL :: TerminalLocation a => Lens' a Int
+ Brick.Types: locationRow :: TerminalLocation a => a -> Int
+ Brick.Types: locationRowL :: TerminalLocation a => Lens' a Int
- Brick.Types: Extent :: n -> Location -> (Int, Int) -> Extent n
+ Brick.Types: Extent :: n -> Location -> (Int, Int) -> Location -> Extent n
- Brick.Types: cursorLocationL :: forall n_ax96. Lens' (CursorLocation n_ax96) Location
+ Brick.Types: cursorLocationL :: forall n_ax9K. Lens' (CursorLocation n_ax9K) Location
- Brick.Types: cursorLocationNameL :: forall n_ax96 n_axMU. Lens (CursorLocation n_ax96) (CursorLocation n_axMU) (Maybe n_ax96) (Maybe n_axMU)
+ Brick.Types: cursorLocationNameL :: forall n_ax9K n_axNy. Lens (CursorLocation n_ax9K) (CursorLocation n_axNy) (Maybe n_ax9K) (Maybe n_axNy)
- Brick.Types: cursorsL :: forall n_ax61. Lens' (Result n_ax61) [CursorLocation n_ax61]
+ Brick.Types: cursorsL :: forall n_ax6F. Lens' (Result n_ax6F) [CursorLocation n_ax6F]
- Brick.Types: extentsL :: forall n_ax61. Lens' (Result n_ax61) [Extent n_ax61]
+ Brick.Types: extentsL :: forall n_ax6F. Lens' (Result n_ax6F) [Extent n_ax6F]
- Brick.Types: imageL :: forall n_ax61. Lens' (Result n_ax61) Image
+ Brick.Types: imageL :: forall n_ax6F. Lens' (Result n_ax6F) Image
- Brick.Types: visibilityRequestsL :: forall n_ax61. Lens' (Result n_ax61) [VisibilityRequest]
+ Brick.Types: visibilityRequestsL :: forall n_ax6F. Lens' (Result n_ax6F) [VisibilityRequest]
- Brick.Widgets.Dialog: dialogButtonsL :: forall a_a1iTW a_a1iUw. Lens (Dialog a_a1iTW) (Dialog a_a1iUw) [(String, a_a1iTW)] [(String, a_a1iUw)]
+ Brick.Widgets.Dialog: dialogButtonsL :: forall a_a1kfA a_a1kga. Lens (Dialog a_a1kfA) (Dialog a_a1kga) [(String, a_a1kfA)] [(String, a_a1kga)]
- Brick.Widgets.Dialog: dialogSelectedIndexL :: forall a_a1iTW. Lens' (Dialog a_a1iTW) (Maybe Int)
+ Brick.Widgets.Dialog: dialogSelectedIndexL :: forall a_a1kfA. Lens' (Dialog a_a1kfA) (Maybe Int)
- Brick.Widgets.Dialog: dialogTitleL :: forall a_a1iTW. Lens' (Dialog a_a1iTW) (Maybe String)
+ Brick.Widgets.Dialog: dialogTitleL :: forall a_a1kfA. Lens' (Dialog a_a1kfA) (Maybe String)
- Brick.Widgets.Dialog: dialogWidthL :: forall a_a1iTW. Lens' (Dialog a_a1iTW) Int
+ Brick.Widgets.Dialog: dialogWidthL :: forall a_a1kfA. Lens' (Dialog a_a1kfA) Int
- Brick.Widgets.Edit: editContentsL :: forall t_a1kVn n_a1kVo. Lens' (Editor t_a1kVn n_a1kVo) (TextZipper t_a1kVn)
+ Brick.Widgets.Edit: editContentsL :: forall t_a1mh1 n_a1mh2. Lens' (Editor t_a1mh1 n_a1mh2) (TextZipper t_a1mh1)
- Brick.Widgets.Edit: editDrawContentsL :: forall t_a1kVn n_a1kVo. Lens' (Editor t_a1kVn n_a1kVo) ([t_a1kVn] -> Widget n_a1kVo)
+ Brick.Widgets.Edit: editDrawContentsL :: forall t_a1mh1 n_a1mh2. Lens' (Editor t_a1mh1 n_a1mh2) ([t_a1mh1] -> Widget n_a1mh2)
- Brick.Widgets.List: listElementsL :: forall n_a1nyU e_a1nyV e_a1nIc. Lens (List n_a1nyU e_a1nyV) (List n_a1nyU e_a1nIc) (Vector e_a1nyV) (Vector e_a1nIc)
+ Brick.Widgets.List: listElementsL :: forall n_a1oYI e_a1oYJ e_a1p80. Lens (List n_a1oYI e_a1oYJ) (List n_a1oYI e_a1p80) (Vector e_a1oYJ) (Vector e_a1p80)
- Brick.Widgets.List: listItemHeightL :: forall n_a1nyU e_a1nyV. Lens' (List n_a1nyU e_a1nyV) Int
+ Brick.Widgets.List: listItemHeightL :: forall n_a1oYI e_a1oYJ. Lens' (List n_a1oYI e_a1oYJ) Int
- Brick.Widgets.List: listNameL :: forall n_a1nyU e_a1nyV n_a1nId. Lens (List n_a1nyU e_a1nyV) (List n_a1nId e_a1nyV) n_a1nyU n_a1nId
+ Brick.Widgets.List: listNameL :: forall n_a1oYI e_a1oYJ n_a1p81. Lens (List n_a1oYI e_a1oYJ) (List n_a1p81 e_a1oYJ) n_a1oYI n_a1p81
- Brick.Widgets.List: listSelectedL :: forall n_a1nyU e_a1nyV. Lens' (List n_a1nyU e_a1nyV) (Maybe Int)
+ Brick.Widgets.List: listSelectedL :: forall n_a1oYI e_a1oYJ. Lens' (List n_a1oYI e_a1oYJ) (Maybe Int)
Files
- CHANGELOG.md +25/−0
- brick.cabal +3/−2
- docs/guide.rst +90/−41
- programs/LayerDemo.hs +17/−9
- programs/MouseDemo.hs +56/−6
- src/Brick/Main.hs +11/−9
- src/Brick/Types.hs +4/−4
- src/Brick/Types/Internal.hs +15/−11
- src/Brick/Widgets/Center.hs +2/−2
- src/Brick/Widgets/Core.hs +18/−14
- src/Brick/Widgets/Edit.hs +4/−1
- src/Brick/Widgets/Internal.hs +56/−1
CHANGELOG.md view
@@ -2,6 +2,31 @@ Brick changelog --------------- +0.15+----++Demo changes:+* MouseDemo: add an editor and use mouse events to move the cursor+* MouseDemo: Enhance MouseDemo to show interaction between 'clickable'+ and viewports (thanks Kevin Quick)++New features:+* Editors now report mouse click events++API changes:+* Rename TerminalLocation row/column fields to avoid commonplace name+ clashes; rename row/column to locationRow/locationColumn (fixes #96)++Bug fixes:+* Core: make cropToContext also crop extents (fixes #101)+* viewport: if the sub-widget is not rendered, also cull all extents and+ cursor locations++Documentation changes:+* User Guide updates: minor fixes, updates to content on custom widgets,+ wide character support, and examples (thanks skapazzo@inventati.org,+ Kevin Quick)+ 0.14 ----
brick.cabal view
@@ -1,5 +1,5 @@ name: brick-version: 0.14+version: 0.15 synopsis: A declarative terminal user interface library description: Write terminal applications painlessly with 'brick'! You write an@@ -164,7 +164,8 @@ data-default, text, microlens >= 0.3.0.0,- microlens-th+ microlens-th,+ text-zipper executable brick-layer-demo if !flag(demos)
docs/guide.rst view
@@ -423,7 +423,7 @@ cursor position. For example, this widget requests a cursor placement on the first-"``o``" in "``foo``" assocated with the cursor name "``myCursor``":+"``o``" in "``foo``" associated with the cursor name "``myCursor``": .. code:: haskell @@ -726,6 +726,37 @@ * ``Brick.Widgets.Core.withDefAttr`` * ``Brick.Widgets.Core.overrideAttr`` +Wide Character Support and the TextWidth class+==============================================++Brick supports rendering wide characters in all widgets, and the brick+editor supports entering and editing wide characters. Wide characters+are those such as many Asian characters and emoji that need more than+a single terminal column to be displayed. Brick relies on Vty's use of+the `utf8proc`_ library to determine the column width of each character+rendered.++As a result of supporting wide characters, it is important to know that+computing the length of a string to determine its screen width will+*only* work for single-column characters. So, for example, if you want+to support wide characters in your application, this will not work:++.. code:: haskell++ let width = Data.Text.length t++because if the string contains any wide characters, their widths+will not be counted properly. In order to get this right, use the+``TextWidth`` type class to compute the width:++.. code:: haskell++ let width = Brick.Widgets.Core.textWidth t++The ``TextWidth`` type class uses Vty's character width routine (and+thus ``utf8proc``) to compute the correct width. If you need to compute+the width of a single character, use ``Graphics.Text.wcwidth``.+ Extents ======= @@ -783,9 +814,11 @@ do vty <- Brick.Main.getVtyHandle- let output = outputIface vty- when (supportsMode output BracketedPaste) $- liftIO $ setMode output BracketedPaste True+ case vty of+ Nothing -> return ()+ Just v -> let output = outputIface v+ in when (supportsMode output BracketedPaste) $+ liftIO $ setMode output BracketedPaste True Once enabled, paste mode will generate Vty ``EvPaste`` events. These events will give you the entire pasted content as a ``ByteString`` which@@ -804,9 +837,11 @@ do vty <- Brick.Main.getVtyHandle- let output = outputIface vty- when (supportsMode output Mouse) $- liftIO $ setMode output Mouse True+ case vty of+ Nothing -> return ()+ Just v -> let output = outputIface vt+ in when (supportsMode output Mouse) $+ liftIO $ setMode output Mouse True Bear in mind that some terminals do not support mouse interaction, so use Vty's ``getModeStatus`` to find out whether your terminal will@@ -1107,31 +1142,41 @@ -- Invalidate the entire cache (useful on a resize): Brick.Main.invalidateCache -Implementing Your Own Widgets-=============================+Implementing Custom Widgets+=========================== -``brick`` exposes all of the internals you need to implement your own-widgets. Those internals, together with ``Graphics.Vty``, can be used to-create widgets from the ground up. We start by writing a constructor-function:+``brick`` exposes all of the internals you need to implement your+own widgets. Those internals, together with ``Graphics.Vty``, can be+used to create widgets from the ground up. You'll need to implement+your own widget if you can't write what you need in terms of existing+combinators. For example, an ordinary widget like .. code:: haskell - myWidget :: ... -> Widget n- myWidget ... =- Widget Fixed Fixed $ do- ...+ myWidget :: Widget n+ myWidget = str "Above" <=> str "Below" -We specify the horizontal and vertical growth policies of the widget-as ``Fixed`` in this example, although they should be specified-appropriately (see `How Widgets and Rendering Work`_). Lastly we specify-the *rendering function*, a function of type+can be expressed with ``<=>`` and ``str`` and needs no custom behavior.+But suppose we want to write a widget that renders some string followed+by the number of columns in the space available to the widget. We can't+do this without writing a custom widget because we need access to the+rendering context. We can write such a widget as follows: .. code:: haskell - render :: RenderM n Result+ customWidget :: String -> Widget n+ customWidget s =+ Widget Fixed Fixed $ do+ ctx <- getContext+ render $ str (s <> " " <> show (ctx^.availWidthL)) -which is a function returning a ``Brick.Types.Result``:+The ``Widget`` constructor takes the horizontal and vertical growth+policies as described in `How Widgets and Rendering Work`_. Here we just+provide ``Fixed`` for both because the widget will not change behavior+if we give it more space. We then get the rendering context and append+the context's available columns to the provided string. Lastly we call+``render`` to render the widget we made with ``str``. The ``render``+function returns a ``Brick.Types.Result`` value: .. code:: haskell @@ -1139,11 +1184,14 @@ Result { image :: Graphics.Vty.Image , cursors :: [Brick.Types.CursorLocation n] , visibilityRequests :: [Brick.Types.VisibilityRequest]+ , extents :: [Extent n] } -The ``RenderM`` monad gives us access to the rendering context (see `How-Widgets and Rendering Work`_) via the ``Brick.Types.getContext``-function. The context type is:+The rendering function runs in the ``RenderM`` monad, which gives us+access to the rendering context (see `How Widgets and Rendering Work`_)+via the ``Brick.Types.getContext`` function as shown above. The context+tells us about the dimensions of the rendering area and the current+attribute state of the renderer, among other things: .. code:: haskell @@ -1157,16 +1205,16 @@ and has lens fields exported as described in `Conventions`_. -The job of the rendering function is to return a rendering result which,-at a minimum, means producing a ``vty`` ``Image``. In addition, if you-so choose, you can also return one or more cursor positions in the-``cursors`` field of the ``Result`` as well as visibility requests (see-`Viewports`_) in the ``visibilityRequests`` field. Returned visibility-requests and cursor positions should be relative to the upper-left-corner of your widget, ``Location (0, 0)``. When your widget is placed-in others, such as boxes, the ``Result`` data you returned will be-offset (as described in `Rendering Sub-Widgets`_) to result in correct-coordinates once the entire interface has been rendered.+As shown here, the job of the rendering function is to return a+rendering result which means producing a ``vty`` ``Image``. In addition,+if you so choose, you can also return one or more cursor positions in+the ``cursors`` field of the ``Result`` as well as visibility requests+(see `Viewports`_) in the ``visibilityRequests`` field. Returned+visibility requests and cursor positions should be relative to the+upper-left corner of your widget, ``Location (0, 0)``. When your widget+is placed in others, such as boxes, the ``Result`` data you returned+will be offset (as described in `Rendering Sub-Widgets`_) to result in+correct coordinates once the entire interface has been rendered. Using the Rendering Context ---------------------------@@ -1195,11 +1243,11 @@ Rendering Sub-Widgets --------------------- -If your custom widget wraps another, then in addition to rendering the-wrapped widget and augmenting its returned ``Result`` *it must also-translate the resulting cursor locations and visibility requests*.-This is vital to maintaining the correctness of cursor locations and-visbility locations as widget layout proceeds. To do so, use the+If your custom widget wraps another, then in addition to rendering+the wrapped widget and augmenting its returned ``Result`` *it must+also translate the resulting cursor locations, visibility requests,+and extents*. This is vital to maintaining the correctness of+rendering metadata as widget layout proceeds. To do so, use the ``Brick.Widgets.Core.addResultOffset`` function to offset the elements of a ``Result`` by a specified amount. The amount depends on the nature of the offset introduced by your wrapper widget's logic.@@ -1231,3 +1279,4 @@ .. _Hackage: http://hackage.haskell.org/ .. _microlens: http://hackage.haskell.org/package/microlens .. _bracketed paste mode: https://cirw.in/blog/bracketed-paste+.. _utf8proc: http://julialang.org/utf8proc/
programs/LayerDemo.hs view
@@ -9,7 +9,7 @@ import qualified Graphics.Vty as V import qualified Brick.Types as T-import Brick.Types (rowL, columnL, Widget)+import Brick.Types (locationRowL, locationColumnL, Widget) import qualified Brick.Main as M import qualified Brick.Widgets.Border as B import qualified Brick.Widgets.Center as C@@ -44,15 +44,23 @@ B.border $ str "Bottom layer\n(Ctrl-arrow keys move)" appEvent :: St -> T.BrickEvent () e -> T.EventM () (T.Next St)-appEvent st (T.VtyEvent (V.EvKey V.KDown [])) = M.continue $ st & topLayerLocation.rowL %~ (+ 1)-appEvent st (T.VtyEvent (V.EvKey V.KUp [])) = M.continue $ st & topLayerLocation.rowL %~ (subtract 1)-appEvent st (T.VtyEvent (V.EvKey V.KRight [])) = M.continue $ st & topLayerLocation.columnL %~ (+ 1)-appEvent st (T.VtyEvent (V.EvKey V.KLeft [])) = M.continue $ st & topLayerLocation.columnL %~ (subtract 1)+appEvent st (T.VtyEvent (V.EvKey V.KDown [])) =+ M.continue $ st & topLayerLocation.locationRowL %~ (+ 1)+appEvent st (T.VtyEvent (V.EvKey V.KUp [])) =+ M.continue $ st & topLayerLocation.locationRowL %~ (subtract 1)+appEvent st (T.VtyEvent (V.EvKey V.KRight [])) =+ M.continue $ st & topLayerLocation.locationColumnL %~ (+ 1)+appEvent st (T.VtyEvent (V.EvKey V.KLeft [])) =+ M.continue $ st & topLayerLocation.locationColumnL %~ (subtract 1) -appEvent st (T.VtyEvent (V.EvKey V.KDown [V.MCtrl])) = M.continue $ st & bottomLayerLocation.rowL %~ (+ 1)-appEvent st (T.VtyEvent (V.EvKey V.KUp [V.MCtrl])) = M.continue $ st & bottomLayerLocation.rowL %~ (subtract 1)-appEvent st (T.VtyEvent (V.EvKey V.KRight [V.MCtrl])) = M.continue $ st & bottomLayerLocation.columnL %~ (+ 1)-appEvent st (T.VtyEvent (V.EvKey V.KLeft [V.MCtrl])) = M.continue $ st & bottomLayerLocation.columnL %~ (subtract 1)+appEvent st (T.VtyEvent (V.EvKey V.KDown [V.MCtrl])) =+ M.continue $ st & bottomLayerLocation.locationRowL %~ (+ 1)+appEvent st (T.VtyEvent (V.EvKey V.KUp [V.MCtrl])) =+ M.continue $ st & bottomLayerLocation.locationRowL %~ (subtract 1)+appEvent st (T.VtyEvent (V.EvKey V.KRight [V.MCtrl])) =+ M.continue $ st & bottomLayerLocation.locationColumnL %~ (+ 1)+appEvent st (T.VtyEvent (V.EvKey V.KLeft [V.MCtrl])) =+ M.continue $ st & bottomLayerLocation.locationColumnL %~ (subtract 1) appEvent st (T.VtyEvent (V.EvKey V.KEsc [])) = M.halt st appEvent st _ = M.continue st
programs/MouseDemo.hs view
@@ -3,7 +3,7 @@ module Main where import Control.Applicative ((<$>))-import Lens.Micro ((^.), (&), (.~))+import Lens.Micro ((^.), (&), (.~), (%~)) import Lens.Micro.TH (makeLenses) import Control.Monad (void) import Data.Monoid ((<>))@@ -12,17 +12,23 @@ import qualified Brick.Types as T import Brick.AttrMap import Brick.Util-import Brick.Types (Widget)+import Brick.Types (Widget, ViewportType(Vertical)) import qualified Brick.Main as M+import qualified Brick.Widgets.Edit as E import qualified Brick.Widgets.Center as C import qualified Brick.Widgets.Border as B import Brick.Widgets.Core+import Data.Text.Zipper (moveCursor)+import Data.Tuple (swap) -data Name = Info | Button1 | Button2 | Button3 deriving (Show, Ord, Eq)+data Name = Info | Button1 | Button2 | Button3 | Prose | TextBox+ deriving (Show, Ord, Eq) data St = St { _clicked :: [T.Extent Name] , _lastReportedClick :: Maybe (Name, T.Location)+ , _prose :: String+ , _edit :: E.Editor String Name } makeLenses ''St@@ -30,12 +36,17 @@ drawUi :: St -> [Widget Name] drawUi st = [ buttonLayer st+ , proseLayer st , infoLayer st ] buttonLayer :: St -> Widget Name buttonLayer st =- C.centerLayer $ hBox $ padAll 1 <$> buttons+ C.vCenterLayer $+ C.hCenterLayer (padBottom (T.Pad 1) $ str "Click a button:") <=>+ C.hCenterLayer (hBox $ padLeftRight 1 <$> buttons) <=>+ C.hCenterLayer (padTopBottom 1 $ str "Or enter text and then click in this editor:") <=>+ C.hCenterLayer (vLimit 3 $ hLimit 50 $ E.renderEditor True (st^.edit)) where buttons = mkButton <$> buttonData buttonData = [ (Button1, "Button 1", "button1")@@ -51,6 +62,18 @@ padLeftRight (if wasClicked then 2 else 3) $ str (if wasClicked then "<" <> label <> ">" else label) +proseLayer :: St -> Widget Name+proseLayer st =+ B.border $+ C.hCenterLayer $+ vLimit 8 $+ -- n.b. if clickable and viewport are inverted here, click event+ -- coordinates will only identify the viewable range, not the actual+ -- editor widget coordinates.+ viewport Prose Vertical $+ clickable Prose $+ vBox $ map str $ lines (st^.prose)+ infoLayer :: St -> Widget Name infoLayer st = T.Widget T.Fixed T.Fixed $ do c <- T.getContext@@ -63,10 +86,16 @@ C.hCenter (str ("Last reported click: " <> msg)) appEvent :: St -> T.BrickEvent Name e -> T.EventM Name (T.Next St)-appEvent st (T.MouseDown n _ _ loc) = M.continue $ st & lastReportedClick .~ Just (n, loc)+appEvent st (T.MouseDown n _ _ loc) = do+ let T.Location pos = loc+ M.continue $ st & lastReportedClick .~ Just (n, loc)+ & edit %~ E.applyEdit (if n == TextBox then moveCursor (swap pos) else id) appEvent st (T.MouseUp _ _ _) = M.continue $ st & lastReportedClick .~ Nothing appEvent st (T.VtyEvent (V.EvMouseUp _ _ _)) = M.continue $ st & lastReportedClick .~ Nothing+appEvent st (T.VtyEvent (V.EvKey V.KUp [V.MCtrl])) = M.vScrollBy (M.viewportScroll Prose) (-1) >> M.continue st+appEvent st (T.VtyEvent (V.EvKey V.KDown [V.MCtrl])) = M.vScrollBy (M.viewportScroll Prose) 1 >> M.continue st appEvent st (T.VtyEvent (V.EvKey V.KEsc [])) = M.halt st+appEvent st (T.VtyEvent ev) = M.continue =<< T.handleEventLensed st edit E.handleEditorEvent ev appEvent st _ = M.continue st aMap :: AttrMap@@ -75,6 +104,7 @@ , ("button1", V.white `on` V.cyan) , ("button2", V.white `on` V.green) , ("button3", V.white `on` V.blue)+ , (E.editFocusedAttr, V.black `on` V.yellow) ] app :: M.App St e Name@@ -83,7 +113,7 @@ , M.appStartEvent = return , M.appHandleEvent = appEvent , M.appAttrMap = const aMap- , M.appChooseCursor = M.neverShowCursor+ , M.appChooseCursor = M.showFirstCursor } main :: IO ()@@ -94,3 +124,23 @@ return v void $ M.customMain buildVty Nothing app $ St [] Nothing+ "Press Ctrl-up and Ctrl-down arrow keys to scroll, ESC to quit.\n\+ \Observe the click coordinates identify the\n\+ \underlying widget coordinates.\n\+ \\n\+ \Lorem ipsum dolor sit amet,\n\+ \consectetur adipiscing elit,\n\+ \sed do eiusmod tempor incididunt ut labore\n\+ \et dolore magna aliqua.\n\+ \ \n\+ \Ut enim ad minim veniam\n\+ \quis nostrud exercitation ullamco laboris\n\+ \nisi ut aliquip ex ea commodo consequat.\n\+ \\n\+ \Duis aute irure dolor in reprehenderit\n\+ \in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n\+ \\n\+ \Excepteur sint occaecat cupidatat not proident,\n\+ \sunt in culpa qui officia deserunt mollit\n\+ \anim id est laborum.\n"+ (E.editor TextBox (str . unlines) Nothing "")
src/Brick/Main.hs view
@@ -238,30 +238,30 @@ VtyEvent (EvMouseDown c r button mods) -> do let matching = findClickedExtents_ (c, r) exts case matching of- (Extent n (Location (ec, er)) _:_) ->+ (Extent n (Location (ec, er)) _ (Location (oC, oR)):_) -> -- If the clicked extent was registered as -- clickable, send a click event. Otherwise, just -- send the raw mouse event case n `elem` firstRS^.clickableNamesL of True -> do let localCoords = Location (lc, lr)- lc = c - ec- lr = r - er+ lc = c - ec + oC+ lr = r - er + oR return (MouseDown n button mods localCoords, firstRS, exts) False -> return (e, firstRS, exts) _ -> return (e, firstRS, exts) VtyEvent (EvMouseUp c r button) -> do let matching = findClickedExtents_ (c, r) exts case matching of- (Extent n (Location (ec, er)) _:_) ->+ (Extent n (Location (ec, er)) _ (Location (oC, oR)):_) -> -- If the clicked extent was registered as -- clickable, send a click event. Otherwise, just -- send the raw mouse event case n `elem` firstRS^.clickableNamesL of True -> do let localCoords = Location (lc, lr)- lc = c - ec- lr = r - er+ lc = c - ec + oC+ lr = r - er + oR return (MouseUp n button localCoords, firstRS, exts) False -> return (e, firstRS, exts) _ -> return (e, firstRS, exts)@@ -294,7 +294,7 @@ -- | Did the specified mouse coordinates (column, row) intersect the -- specified extent? clickedExtent :: (Int, Int) -> Extent n -> Bool-clickedExtent (c, r) (Extent _ (Location (lc, lr)) (w, h)) =+clickedExtent (c, r) (Extent _ (Location (lc, lr)) (w, h) _) = c >= lc && c < (lc + w) && r >= lr && r < (lr + h) @@ -303,7 +303,7 @@ lookupExtent :: (Eq n) => n -> EventM n (Maybe (Extent n)) lookupExtent n = EventM $ asks (listToMaybe . filter f . latestExtents) where- f (Extent n' _ _) = n == n'+ f (Extent n' _ _ _) = n == n' -- | Given a mouse click location, return the extents intersected by the -- click. The returned extents are sorted such that the first extent in@@ -346,7 +346,9 @@ rs picWithCursor = case theCursor of Nothing -> pic { picCursor = NoCursor }- Just cloc -> pic { picCursor = AbsoluteCursor (cloc^.columnL) (cloc^.rowL) }+ Just cloc -> pic { picCursor = AbsoluteCursor (cloc^.locationColumnL)+ (cloc^.locationRowL)+ } update vty picWithCursor
src/Brick/Types.hs view
@@ -154,10 +154,10 @@ attrL = to (\c -> attrMapLookup (c^.ctxAttrNameL) (c^.ctxAttrMapL)) instance TerminalLocation (CursorLocation n) where- columnL = cursorLocationL._1- column = column . cursorLocation- rowL = cursorLocationL._2- row = row . cursorLocation+ locationColumnL = cursorLocationL._1+ locationColumn = locationColumn . cursorLocation+ locationRowL = cursorLocationL._2+ locationRow = locationRow . cursorLocation -- | Given an attribute name, obtain the attribute for the attribute -- name by consulting the context's attribute map.
src/Brick/Types/Internal.hs view
@@ -103,9 +103,12 @@ , cacheInvalidateRequests :: [CacheInvalidateRequest n] } --- | An extent of a named area indicating the location of its upper-left--- corner and its size (width, height).-data Extent n = Extent n Location (Int, Int)+-- | An extent of a named area: its size, location, and origin.+data Extent n = Extent { extentName :: n+ , extentUpperLeft :: Location+ , extentSize :: (Int, Int)+ , extentOffset :: Location+ } deriving (Show) data EventRO n = EventRO { eventViewportMap :: M.Map n Viewport@@ -143,17 +146,18 @@ -- | The class of types that behave like terminal locations. class TerminalLocation a where -- | Get the column out of the value- columnL :: Lens' a Int- column :: a -> Int+ locationColumnL :: Lens' a Int+ locationColumn :: a -> Int+ -- | Get the row out of the value- rowL :: Lens' a Int- row :: a -> Int+ locationRowL :: Lens' a Int+ locationRow :: a -> Int instance TerminalLocation Location where- columnL = _1- column (Location t) = fst t- rowL = _2- row (Location t) = snd t+ locationColumnL = _1+ locationColumn (Location t) = fst t+ locationRowL = _2+ locationRow (Location t) = snd t -- | The origin (upper-left corner). origin :: Location
src/Brick/Widgets/Center.hs view
@@ -153,7 +153,7 @@ c <- getContext let centerW = c^.availWidthL `div` 2 centerH = c^.availHeightL `div` 2- off = Location ( centerW - l^.columnL- , centerH - l^.rowL+ off = Location ( centerW - l^.locationColumnL+ , centerH - l^.locationRowL ) render $ translateBy off p
src/Brick/Widgets/Core.hs view
@@ -144,13 +144,15 @@ -- something and then translate it or otherwise offset it from its -- original origin. addResultOffset :: Location -> Result n -> Result n-addResultOffset off = addCursorOffset off . addVisibilityOffset off . addExtentOffset off+addResultOffset off = addCursorOffset off .+ addVisibilityOffset off .+ addExtentOffset off addVisibilityOffset :: Location -> Result n -> Result n addVisibilityOffset off r = r & visibilityRequestsL.each.vrPositionL %~ (off <>) addExtentOffset :: Location -> Result n -> Result n-addExtentOffset off r = r & extentsL.each %~ (\(Extent n l sz) -> Extent n (off <> l) sz)+addExtentOffset off r = r & extentsL.each %~ (\(Extent n l sz o) -> Extent n (off <> l) sz o) -- | Render the specified widget and record its rendering extent using -- the specified name (see also 'lookupExtent').@@ -158,7 +160,7 @@ reportExtent n p = Widget (hSize p) (vSize p) $ do result <- render p- let ext = Extent n (Location (0, 0)) sz+ let ext = Extent n (Location (0, 0)) sz (Location (0, 0)) sz = ( result^.imageL.to V.imageWidth , result^.imageL.to V.imageHeight )@@ -174,7 +176,7 @@ addCursorOffset :: Location -> Result n -> Result n addCursorOffset off r = let onlyVisible = filter isVisible- isVisible l = l^.columnL >= 0 && l^.rowL >= 0+ isVisible l = l^.locationColumnL >= 0 && l^.locationRowL >= 0 in r & cursorsL %~ (\cs -> onlyVisible $ (`clOffset` off) <$> cs) unrestricted :: Int@@ -550,16 +552,16 @@ raw img = Widget Fixed Fixed $ return $ def & imageL .~ img -- | Translate the specified widget by the specified offset amount.--- Defers to the translated width for growth policy.+-- Defers to the translated widget for growth policy. translateBy :: Location -> Widget n -> Widget n translateBy off p = Widget (hSize p) (vSize p) $ do result <- render p return $ addResultOffset off- $ result & imageL %~ (V.translate (off^.columnL) (off^.rowL))+ $ result & imageL %~ (V.translate (off^.locationColumnL) (off^.locationRowL)) -- | Crop the specified widget on the left by the specified number of--- columns. Defers to the translated width for growth policy.+-- columns. Defers to the cropped widget for growth policy. cropLeftBy :: Int -> Widget n -> Widget n cropLeftBy cols p = Widget (hSize p) (vSize p) $ do@@ -570,7 +572,7 @@ $ result & imageL %~ cropped -- | Crop the specified widget on the right by the specified number of--- columns. Defers to the translated width for growth policy.+-- columns. Defers to the cropped widget for growth policy. cropRightBy :: Int -> Widget n -> Widget n cropRightBy cols p = Widget (hSize p) (vSize p) $ do@@ -580,7 +582,7 @@ return $ result & imageL %~ cropped -- | Crop the specified widget on the top by the specified number of--- rows. Defers to the translated width for growth policy.+-- rows. Defers to the cropped widget for growth policy. cropTopBy :: Int -> Widget n -> Widget n cropTopBy rows p = Widget (hSize p) (vSize p) $ do@@ -591,7 +593,7 @@ $ result & imageL %~ cropped -- | Crop the specified widget on the bottom by the specified number of--- rows. Defers to the translated width for growth policy.+-- rows. Defers to the cropped widget for growth policy. cropBottomBy :: Int -> Widget n -> Widget n cropBottomBy rows p = Widget (hSize p) (vSize p) $ do@@ -772,6 +774,8 @@ case translatedSize of (0, 0) -> return $ translated & imageL .~ (V.charFill (c^.attrL) ' ' (c^.availWidthL) (c^.availHeightL)) & visibilityRequestsL .~ mempty+ & extentsL .~ mempty+ & cursorsL .~ mempty _ -> render $ cropToContext $ padBottom Max $ padRight Max@@ -820,9 +824,9 @@ where curStart = vp^.vpTop curEnd = curStart + vp^.vpSize._2- reqStart = rq^.vrPositionL.rowL+ reqStart = rq^.vrPositionL.locationRowL - reqEnd = rq^.vrPositionL.rowL + rq^.vrSizeL._2+ reqEnd = rq^.vrPositionL.locationRowL + rq^.vrSizeL._2 newVStart :: Int newVStart = if reqStart < vStartEndVisible then reqStart@@ -834,9 +838,9 @@ where curStart = vp^.vpLeft curEnd = curStart + vp^.vpSize._1- reqStart = rq^.vrPositionL.columnL+ reqStart = rq^.vrPositionL.locationColumnL - reqEnd = rq^.vrPositionL.columnL + rq^.vrSizeL._1+ reqEnd = rq^.vrPositionL.locationColumnL + rq^.vrSizeL._1 newHStart :: Int newHStart = if reqStart < hStartEndVisible then reqStart
src/Brick/Widgets/Edit.hs view
@@ -146,7 +146,9 @@ getEditContents :: Monoid t => Editor t n -> [t] getEditContents e = Z.getText $ e^.editContentsL --- | Turn an editor state value into a widget+-- | Turn an editor state value into a widget. This uses the editor's+-- name for its scrollable viewport handle and the name is also used to+-- report mouse events. renderEditor :: (Ord n, Show n, Monoid t, TextWidth t, Z.GenericTextZipper t) => Bool -- ^ Whether the editor has focus. It will report a cursor@@ -167,6 +169,7 @@ in withAttr (if foc then editFocusedAttr else editAttr) $ limit $ viewport (e^.editorNameL) Both $+ clickable (e^.editorNameL) $ (if foc then showCursor (e^.editorNameL) cursorLoc else id) $ visibleRegion cursorLoc (atCharWidth, 1) $ e^.editDrawContentsL $
src/Brick/Widgets/Internal.hs view
@@ -14,6 +14,7 @@ import Control.Monad.Trans.State.Lazy import Control.Monad.Trans.Reader import Data.Default+import Data.Maybe (catMaybes) import qualified Graphics.Vty as V import Brick.Types@@ -49,4 +50,58 @@ cropResultToContext :: Result n -> RenderM n (Result n) cropResultToContext result = do c <- getContext- return $ result & imageL %~ (V.crop (max 0 $ c^.availWidthL) (max 0 $ c^.availHeightL))+ return $ result & imageL %~ cropImage c+ & cursorsL %~ cropCursors c+ & extentsL %~ cropExtents c++cropImage :: Context -> V.Image -> V.Image+cropImage c = V.crop (max 0 $ c^.availWidthL) (max 0 $ c^.availHeightL)++cropCursors :: Context -> [CursorLocation n] -> [CursorLocation n]+cropCursors ctx cs = catMaybes $ cropCursor <$> cs+ where+ -- A cursor location is removed if it is not within the region+ -- described by the context.+ cropCursor c | outOfContext c = Nothing+ | otherwise = Just c+ outOfContext c =+ or [ c^.cursorLocationL.locationRowL < 0+ , c^.cursorLocationL.locationColumnL < 0+ , c^.cursorLocationL.locationRowL >= ctx^.availHeightL+ , c^.cursorLocationL.locationColumnL >= ctx^.availWidthL+ ]++cropExtents :: Context -> [Extent n] -> [Extent n]+cropExtents ctx es = catMaybes $ cropExtent <$> es+ where+ -- An extent is cropped in places where it is not within the+ -- region described by the context.+ --+ -- If its entirety is outside the context region, it is dropped.+ --+ -- Otherwise its size and upper left corner are adjusted so that+ -- they are contained within the context region.+ cropExtent (Extent n (Location (c, r)) (w, h) (Location (oC, oR))) =+ -- First, clamp the upper-left corner to at least (0, 0).+ let c' = max c 0+ r' = max r 0+ -- Compute deltas for the offset since if the upper-left+ -- corner moved, so should the offset.+ dc = c' - c+ dr = r' - r+ -- Then, determine the new lower-right corner based on+ -- the clamped corner.+ endCol = c' + w+ endRow = r' + h+ -- Then clamp the lower-right corner based on the+ -- context+ endCol' = min (ctx^.availWidthL) endCol+ endRow' = min (ctx^.availHeightL) endRow+ -- Then compute the new width and height from the+ -- clamped lower-right corner.+ w' = endCol' - c'+ h' = endRow' - r'+ e = Extent n (Location (c', r')) (w', h') (Location (oC + dc, oR + dr))+ in if w' < 0 || h' < 0+ then Nothing+ else Just e