Core: make cropToContext also crop extents (fixes #101)

This change also:

* adds record fields to the Extent type
* adds a local coordinate offset to the Extent type for tracking the
  offset that the Extent's upper-left corner has into the original
  coordinate space of the Extent
* updates mouse event generation to account for the local offset when
  computing widget-local coordinates
This commit is contained in:
Jonathan Daugherty 2016-12-03 20:05:51 -08:00
parent 26fe2d349d
commit b0357289f0
4 changed files with 75 additions and 15 deletions

View File

@ -238,30 +238,30 @@ runVty vty chan app appState rs = do
VtyEvent (EvMouseDown c r button mods) -> do VtyEvent (EvMouseDown c r button mods) -> do
let matching = findClickedExtents_ (c, r) exts let matching = findClickedExtents_ (c, r) exts
case matching of case matching of
(Extent n (Location (ec, er)) _:_) -> (Extent n (Location (ec, er)) _ (Location (oC, oR)):_) ->
-- If the clicked extent was registered as -- If the clicked extent was registered as
-- clickable, send a click event. Otherwise, just -- clickable, send a click event. Otherwise, just
-- send the raw mouse event -- send the raw mouse event
case n `elem` firstRS^.clickableNamesL of case n `elem` firstRS^.clickableNamesL of
True -> do True -> do
let localCoords = Location (lc, lr) let localCoords = Location (lc, lr)
lc = c - ec lc = c - ec + oC
lr = r - er lr = r - er + oR
return (MouseDown n button mods localCoords, firstRS, exts) return (MouseDown n button mods localCoords, firstRS, exts)
False -> return (e, firstRS, exts) False -> return (e, firstRS, exts)
_ -> return (e, firstRS, exts) _ -> return (e, firstRS, exts)
VtyEvent (EvMouseUp c r button) -> do VtyEvent (EvMouseUp c r button) -> do
let matching = findClickedExtents_ (c, r) exts let matching = findClickedExtents_ (c, r) exts
case matching of case matching of
(Extent n (Location (ec, er)) _:_) -> (Extent n (Location (ec, er)) _ (Location (oC, oR)):_) ->
-- If the clicked extent was registered as -- If the clicked extent was registered as
-- clickable, send a click event. Otherwise, just -- clickable, send a click event. Otherwise, just
-- send the raw mouse event -- send the raw mouse event
case n `elem` firstRS^.clickableNamesL of case n `elem` firstRS^.clickableNamesL of
True -> do True -> do
let localCoords = Location (lc, lr) let localCoords = Location (lc, lr)
lc = c - ec lc = c - ec + oC
lr = r - er lr = r - er + oR
return (MouseUp n button localCoords, firstRS, exts) return (MouseUp n button localCoords, firstRS, exts)
False -> return (e, firstRS, exts) False -> return (e, firstRS, exts)
_ -> return (e, firstRS, exts) _ -> return (e, firstRS, exts)
@ -294,7 +294,7 @@ lookupViewport n = EventM $ asks (M.lookup n . eventViewportMap)
-- | Did the specified mouse coordinates (column, row) intersect the -- | Did the specified mouse coordinates (column, row) intersect the
-- specified extent? -- specified extent?
clickedExtent :: (Int, Int) -> Extent n -> Bool 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) && c >= lc && c < (lc + w) &&
r >= lr && r < (lr + h) r >= lr && r < (lr + h)
@ -303,7 +303,7 @@ clickedExtent (c, r) (Extent _ (Location (lc, lr)) (w, h)) =
lookupExtent :: (Eq n) => n -> EventM n (Maybe (Extent n)) lookupExtent :: (Eq n) => n -> EventM n (Maybe (Extent n))
lookupExtent n = EventM $ asks (listToMaybe . filter f . latestExtents) lookupExtent n = EventM $ asks (listToMaybe . filter f . latestExtents)
where where
f (Extent n' _ _) = n == n' f (Extent n' _ _ _) = n == n'
-- | Given a mouse click location, return the extents intersected by the -- | Given a mouse click location, return the extents intersected by the
-- click. The returned extents are sorted such that the first extent in -- click. The returned extents are sorted such that the first extent in

View File

@ -103,9 +103,12 @@ data EventState n = ES { esScrollRequests :: [(n, ScrollRequest)]
, cacheInvalidateRequests :: [CacheInvalidateRequest n] , cacheInvalidateRequests :: [CacheInvalidateRequest n]
} }
-- | An extent of a named area indicating the location of its upper-left -- | An extent of a named area: its size, location, and origin.
-- corner and its size (width, height). data Extent n = Extent { extentName :: n
data Extent n = Extent n Location (Int, Int) , extentUpperLeft :: Location
, extentSize :: (Int, Int)
, extentOffset :: Location
}
deriving (Show) deriving (Show)
data EventRO n = EventRO { eventViewportMap :: M.Map n Viewport data EventRO n = EventRO { eventViewportMap :: M.Map n Viewport

View File

@ -144,13 +144,15 @@ emptyWidget = raw V.emptyImage
-- something and then translate it or otherwise offset it from its -- something and then translate it or otherwise offset it from its
-- original origin. -- original origin.
addResultOffset :: Location -> Result n -> Result n 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 :: Location -> Result n -> Result n
addVisibilityOffset off r = r & visibilityRequestsL.each.vrPositionL %~ (off <>) addVisibilityOffset off r = r & visibilityRequestsL.each.vrPositionL %~ (off <>)
addExtentOffset :: Location -> Result n -> Result n 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 -- | Render the specified widget and record its rendering extent using
-- the specified name (see also 'lookupExtent'). -- the specified name (see also 'lookupExtent').
@ -158,7 +160,7 @@ reportExtent :: n -> Widget n -> Widget n
reportExtent n p = reportExtent n p =
Widget (hSize p) (vSize p) $ do Widget (hSize p) (vSize p) $ do
result <- render p 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 sz = ( result^.imageL.to V.imageWidth
, result^.imageL.to V.imageHeight , result^.imageL.to V.imageHeight
) )

View File

@ -14,6 +14,7 @@ import Lens.Micro ((^.), (&), (%~))
import Control.Monad.Trans.State.Lazy import Control.Monad.Trans.State.Lazy
import Control.Monad.Trans.Reader import Control.Monad.Trans.Reader
import Data.Default import Data.Default
import Data.Maybe (catMaybes)
import qualified Graphics.Vty as V import qualified Graphics.Vty as V
import Brick.Types import Brick.Types
@ -49,4 +50,58 @@ cropToContext p =
cropResultToContext :: Result n -> RenderM n (Result n) cropResultToContext :: Result n -> RenderM n (Result n)
cropResultToContext result = do cropResultToContext result = do
c <- getContext 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