packages feed

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 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