on develop help sign in
Improve documentation of Layout and Focus
commit254f9a6bfd396e91fa65650af52a1e5fb3fb8f7d authorAli Abrar <aliabrar@gmail.com> dateFri 19 Mar 2021, 14:34 (6 years ago) parentc2768eb
2 files changed+239−238
src/Reflex/Vty/Widget.hs+1 src/Reflex/Vty/Widget/Layout.hs+238−238
src/Reflex/Vty/Widget.hs+1
@@ -130,6 +130,7 @@ instance MonadNodeId m => MonadNodeId (VtyWidget t m) where
130130 getNextNodeId = VtyWidget $ do
131131 lift $ lift getNextNodeId
132132
133+-- | A reader-like class for the vty widget context. Allows actions to be run in a sub-context.
133134class HasVtyWidgetCtx t m | m -> t where
134135 askCtx :: m (VtyWidgetCtx t)
135136 default askCtx :: (f m' ~ m, Monad m', MonadTrans f, HasVtyWidgetCtx t m') => m (VtyWidgetCtx t)
src/Reflex/Vty/Widget/Layout.hs+238−238
@@ -23,6 +23,19 @@ import Reflex.Host.Class (MonadReflexCreateTrigger)
2323import Reflex.Vty.Widget
2424
2525-- * Focus
26+--
27+-- $focus
28+--
29+-- The focus monad tracks which element is currently focused and processes
30+-- requests to change focus. Focusable elements are assigned a 'FocusId' and
31+-- can manually request focus or receive focus due to some other action (e.g.,
32+-- a tab press in a sibling element, a click event).
33+--
34+-- Focusable elements will usually be created via 'tile', but can also be
35+-- constructed via 'makeFocus' in 'MonadFocus'. The latter option allows for
36+-- more find-grained control of focus behavior.
37+
38+-- ** Storing focus state
2639
2740-- | Identifies an element that is focusable. Can be created using 'makeFocus'.
2841newtype FocusId = FocusId NodeId
@@ -42,6 +55,13 @@ instance Monoid FocusSet where
4255singletonFS :: FocusId -> FocusSet
4356singletonFS = FocusSet . OSet.singleton
4457
58+-- ** Changing focus state
59+
60+-- | Operations that change the currently focused element.
61+data Refocus = Refocus_Shift Int -- ^ Shift the focus by a certain number of positions (see 'shiftFS')
62+ | Refocus_Id FocusId -- ^ Focus a particular element
63+ | Refocus_Clear -- ^ Remove focus from all elements
64+
4565-- | Given a 'FocusSet', a currently focused element, and a number of positions
4666-- to move by, determine the newly focused element.
4767shiftFS :: FocusSet -> Maybe FocusId -> Int -> Maybe FocusId
@@ -50,10 +70,20 @@ shiftFS (FocusSet s) fid n = case OSet.findIndex <$> fid <*> pure s of
5070 Just Nothing -> OSet.elemAt s 0
5171 Just (Just ix) -> OSet.elemAt s $ mod (ix + n) (OSet.size s)
5272
53−-- | Operations that change the currently focused element.
54−data Refocus = Refocus_Shift Int -- ^ Shift the focus by a certain number of positions (see 'shiftFS')
55− | Refocus_Id FocusId -- ^ Focus a particular element
56− | Refocus_Clear -- ^ Remove focus from all elements
73+-- ** The focus management monad
74+
75+-- | A class for things that can produce focusable elements.
76+class (Monad m, Reflex t) => MonadFocus t m | m -> t where
77+ -- | Create a focusable element.
78+ makeFocus :: m FocusId
79+ -- | Emit an 'Event' of requests to change the focus.
80+ requestFocus :: Event t Refocus -> m ()
81+ -- | Produce a 'Dynamic' that indicates whether the given 'FocusId' is focused.
82+ isFocused :: FocusId -> m (Dynamic t Bool)
83+ -- | Run an action, additionally returning the focusable elements it produced.
84+ subFoci :: m a -> m (a, Dynamic t FocusSet)
85+ -- | Get a 'Dynamic' of the currently focused element identifier.
86+ focusedId :: m (Dynamic t (Maybe FocusId))
5787
5888-- | A monad transformer that keeps track of the set of focusable elements and
5989-- which, if any, are currently focused, and allows focus requests.
@@ -78,6 +108,7 @@ newtype Focus t m a = Focus
78108 , MonadNodeId
79109 )
80110
111+
81112instance MonadTrans (Focus t) where
82113 lift = Focus . lift . lift . lift
83114
@@ -101,19 +132,6 @@ instance ImageWriter t m => ImageWriter t (Focus t m)
101132
102133instance (HasFocus t m, Monad m) => HasFocus t (Focus t m)
103134
104−-- | A class for things that can produce focusable elements.
105−class (Monad m, Reflex t) => MonadFocus t m | m -> t where
106− -- | Create a focusable element.
107− makeFocus :: m FocusId
108− -- | Emit an 'Event' of requests to change the focus.
109− requestFocus :: Event t Refocus -> m ()
110− -- | Produce a 'Dynamic' that indicates whether the given 'FocusId' is focused.
111− isFocused :: FocusId -> m (Dynamic t Bool)
112− -- | Run an action, additionally returning the focusable elements it produced.
113− subFoci :: m a -> m (a, Dynamic t FocusSet)
114− -- | Get a 'Dynamic' of the currently focused element identifier.
115− focusedId :: m (Dynamic t (Maybe FocusId))
116−
117135instance (Reflex t, MonadFix m, MonadNodeId m) => MonadFocus t (Focus t m) where
118136 makeFocus = do
119137 fid <- FocusId <$> lift getNextNodeId
@@ -145,51 +163,81 @@ runFocus (Focus x) = do
145163 Refocus_Id fid -> Just fid
146164 Refocus_Shift n -> shiftFS fs mf n
147165
148−-- | A widget that is focusable and occupies a layout region based on the
149−-- provided constraint. Returns the 'FocusId' allowing for manual focus
150−-- management.
151−tile'
152− :: (MonadNodeId m, MonadFix m, Reflex t, HasVtyWidgetCtx t m, HasVtyInput t m, MonadFocus t m, MonadLayout t m)
153− => Dynamic t Constraint
154− -> m a
155− -> m (FocusId, a)
156−tile' c w = do
157− fid <- makeFocus
158− r <- region c
159− fid' <- focusedId
160− parentFocused <- isFocused fid
161− rec (click, result, subs) <- pane r focused $ do
162− m <- mouseDown V.BLeft
163− (x, sf) <- subFoci w
164− pure (m, x, sf)
165− let childFocused = liftA2 (\f s -> case f of
166− Nothing -> False
167− Just f' -> OSet.member f' $ unFocusSet s) fid' subs
168− let focused = (||) <$> parentFocused <*> childFocused
169− requestFocus $ Refocus_Id fid <$ click
170− pure (fid, result)
166+-- ** Focus controls
171167
172−-- | A widget that is focusable and occupies a layout region based on the
173−-- provided constraint.
174−tile
175− :: (MonadNodeId m, MonadFix m, Reflex t, HasVtyWidgetCtx t m, HasVtyInput t m, MonadFocus t m, MonadLayout t m)
176− => Dynamic t Constraint
177− -> m a
178− -> m a
179−tile c = fmap snd . tile' c
168+-- | Request focus be shifted backward and forward based on tab presses. <Tab>
169+-- shifts focus forward and <Shift+Tab> shifts focus backward.
170+tabNavigation :: (Reflex t, MonadNodeId m, HasVtyInput t m, MonadFocus t m) => m ()
171+tabNavigation = do
172+ fwd <- fmap (const 1) <$> key (V.KChar '\t')
173+ back <- fmap (const (-1)) <$> key V.KBackTab
174+ requestFocus $ Refocus_Shift <$> leftmost [fwd, back]
180175
181−-- | A widget that is not focusable and occupies a layout region based on the
182−-- provided constraint.
183−unfocusable
184− :: (Reflex t, MonadNodeId m, HasVtyWidgetCtx t m, MonadLayout t m)
176+-- * Layout
177+--
178+-- $layout
179+-- The layout monad keeps track of a tree of elements, each having its own
180+-- layout constraints and orientation. Given the available rendering space, it
181+-- computes a layout solution and provides child elements with their particular
182+-- layout solution (the width and height of their rendering space).
183+--
184+-- Complex layouts are built up though some combination of:
185+--
186+-- - 'axis', which lays out its children in a particular orientation, and
187+-- - 'region', which "claims" some part of the screen according to its constraints
188+--
189+
190+-- ** Layout restrictions
191+
192+-- *** Constraints
193+
194+-- | Datatype representing constraints on a widget's size along the main axis (see 'Orientation')
195+data Constraint = Constraint_Fixed Int
196+ | Constraint_Min Int
197+ deriving (Show, Read, Eq, Ord)
198+
199+-- | Shorthand for constructing a fixed constraint
200+fixed
201+ :: Reflex t
202+ => Dynamic t Int
203+ -> Dynamic t Constraint
204+fixed = fmap Constraint_Fixed
205+
206+-- | Shorthand for constructing a minimum size constraint
207+stretch
208+ :: Reflex t
209+ => Dynamic t Int
210+ -> Dynamic t Constraint
211+stretch = fmap Constraint_Min
212+
213+-- | Shorthand for constructing a constraint of no minimum size
214+flex
215+ :: Reflex t
185216 => Dynamic t Constraint
217+flex = pure $ Constraint_Min 0
218+
219+-- *** Orientation
220+
221+-- | The main-axis orientation of a 'Layout' widget
222+data Orientation = Orientation_Column
223+ | Orientation_Row
224+ deriving (Show, Read, Eq, Ord)
225+
226+-- | Create a row-oriented 'axis'
227+row
228+ :: (Reflex t, MonadNodeId m, MonadFix m, MonadLayout t m)
229+ => m a
186230 -> m a
231+row = axis (pure Orientation_Row) flex
232+
233+-- | Create a column-oriented 'axis'
234+col
235+ :: (Reflex t, MonadNodeId m, MonadFix m, MonadLayout t m)
236+ => m a
187237 -> m a
188−unfocusable c w = do
189− r <- region c
190− pane r (pure False) w
238+col = axis (pure Orientation_Column) flex
191239
192−-- * Layout
240+-- ** Layout management data
193241
194242-- | A collection of information related to the layout of the screen. The root
195243-- node is a "parent" widget, and the contents of the 'LayoutForest' are its
@@ -228,6 +276,75 @@ rootLT (LayoutTree a _) = a
228276childrenLT :: LayoutTree a -> LayoutForest a
229277childrenLT (LayoutTree _ a) = a
230278
279+-- | Produce a layout solution given a starting orientation, the overall screen
280+-- size, and a set of constraints.
281+solve
282+ :: Orientation
283+ -> Region
284+ -> LayoutForest (Constraint, Orientation)
285+ -> LayoutTree (Region, Orientation)
286+solve o0 r0 (LayoutForest cs) =
287+ let a = map (\(x, t@(LayoutTree (c, _) _)) -> ((x, t), c)) $ OMap.assocs cs
288+ extent = case o0 of
289+ Orientation_Row -> _region_width r0
290+ Orientation_Column -> _region_height r0
291+ sizes = computeEdges $ computeSizes extent a
292+ chunks = [ (nodeId, solve o1 r1 f)
293+ | ((nodeId, LayoutTree (_, o1) f), sz) <- sizes
294+ , let r1 = chunk o0 r0 sz
295+ ]
296+ in LayoutTree (r0, o0) $ fromListLF chunks
297+ where
298+ computeEdges :: [(a, Int)] -> [(a, (Int, Int))]
299+ computeEdges = ($ []) . fst . foldl (\(m, offset) (a, sz) ->
300+ (((a, (offset, sz)) :) . m, sz + offset)) (id, 0)
301+ computeSizes
302+ :: Int
303+ -> [(a, Constraint)]
304+ -> [(a, Int)]
305+ computeSizes available constraints =
306+ let minTotal = sum $ ffor constraints $ \case
307+ (_, Constraint_Fixed n) -> n
308+ (_, Constraint_Min n) -> n
309+ leftover = max 0 (available - minTotal)
310+ numStretch = length $ filter (isMin . snd) constraints
311+ szStretch = floor $ leftover % max numStretch 1
312+ adjustment = max 0 $ available - minTotal - szStretch * numStretch
313+ in snd $ mapAccumL (\adj (a, c) -> case c of
314+ Constraint_Fixed n -> (adj, (a, n))
315+ Constraint_Min n -> (0, (a, n + szStretch + adj))) adjustment constraints
316+ isMin (Constraint_Min _) = True
317+ isMin _ = False
318+
319+-- | Produce a 'Region' given a starting orientation and region, and the offset
320+-- and main-axis size of the chunk.
321+chunk :: Orientation -> Region -> (Int, Int) -> Region
322+chunk o r (offset, sz) = case o of
323+ Orientation_Column -> r
324+ { _region_top = _region_top r + offset
325+ , _region_height = sz
326+ }
327+ Orientation_Row -> r
328+ { _region_left = _region_left r + offset
329+ , _region_width = sz
330+ }
331+
332+
333+
334+-- ** The layout monad
335+
336+-- | A class of operations for creating screen layouts.
337+class Monad m => MonadLayout t m | m -> t where
338+ -- | Creates a parent element in the current layout with the given size
339+ -- constraint, which lays out its children according to the provided
340+ -- orientation.
341+ axis :: Dynamic t Orientation -> Dynamic t Constraint -> m a -> m a
342+ -- | Creates a child element in the current layout with the given size
343+ -- constraint, returning the 'Region' that the child element is allocated.
344+ region :: Dynamic t Constraint -> m (Dynamic t Region)
345+ -- | Returns the orientation of the containing 'axis'.
346+ askOrientation :: m (Dynamic t Orientation)
347+
231348-- | A monad transformer that collects layout constraints and provides a layout
232349-- solution that satisfies those constraints.
233350newtype Layout t m a = Layout
@@ -259,7 +376,6 @@ instance (Adjustable t m, MonadFix m, MonadHold t m) => Adjustable t (Layout t m
259376 traverseDMapWithKeyWithAdjust f m e = Layout $ traverseDMapWithKeyWithAdjust (\k v -> unLayout $ f k v) m e
260377 traverseDMapWithKeyWithAdjustWithMove f m e = Layout $ traverseDMapWithKeyWithAdjustWithMove (\k v -> unLayout $ f k v) m e
261378
262−
263379instance (HasVtyWidgetCtx t m, HasDisplaySize t m, Reflex t, MonadFix m) => HasVtyWidgetCtx t (Layout t m) where
264380 localCtx f g x = do
265381 solution <- Layout ask
@@ -276,18 +392,6 @@ instance ImageWriter t m => ImageWriter t (Layout t m)
276392
277393instance (HasFocus t m, Monad m) => HasFocus t (Layout t m)
278394
279−-- | A class of operations for creating screen layouts.
280−class Monad m => MonadLayout t m | m -> t where
281− -- | Creates a parent element in the current layout with the given size
282− -- constraint, which lays out its children according to the provided
283− -- orientation.
284− axis :: Dynamic t Orientation -> Dynamic t Constraint -> m a -> m a
285− -- | Creates a child element in the current layout with the given size
286− -- constraint, returning the 'Region' that the child element is allocated.
287− region :: Dynamic t Constraint -> m (Dynamic t Region)
288− -- | Returns the orientation of the containing 'axis'.
289− askOrientation :: m (Dynamic t Orientation)
290−
291395instance (Monad m, MonadNodeId m, Reflex t, MonadFix m) => MonadLayout t (Layout t m) where
292396 axis o c (Layout x) = Layout $ do
293397 nodeId <- getNextNodeId
@@ -312,134 +416,6 @@ instance (MonadFix m, MonadFocus t m) => MonadFocus t (Layout t m) where
312416 tellDyn w
313417 pure (a, sf)
314418
315−-- | Datatype representing constraints on a widget's size along the main axis (see 'Orientation')
316−data Constraint = Constraint_Fixed Int
317− | Constraint_Min Int
318− deriving (Show, Read, Eq, Ord)
319−
320−-- | The main-axis orientation of a 'Layout' widget
321−data Orientation = Orientation_Column
322− | Orientation_Row
323− deriving (Show, Read, Eq, Ord)
324−
325−-- | Create a row-oriented 'axis'
326−row
327− :: (Reflex t, MonadNodeId m, MonadFix m, MonadLayout t m)
328− => m a
329− -> m a
330−row = axis (pure Orientation_Row) (pure $ Constraint_Min 0)
331−
332−-- | Create a column-oriented 'axis'
333−col
334− :: (Reflex t, MonadNodeId m, MonadFix m, MonadLayout t m)
335− => m a
336− -> m a
337−col = axis (pure Orientation_Column) (pure $ Constraint_Min 0)
338−
339−-- | A tile constrained to the given size. Returns the 'FocusId' of the tile,
340−-- allowing for manual focus management.
341−fixed'
342− :: (Reflex t, MonadNodeId m, MonadFix m, HasVtyWidgetCtx t m, HasVtyInput t m, MonadFocus t m, MonadLayout t m)
343− => Dynamic t Int
344− -> m a
345− -> m (FocusId, a)
346−fixed' n = tile' (Constraint_Fixed <$> n)
347−
348−-- | A tile constrained to the given size.
349−fixed
350− :: (Reflex t, MonadNodeId m, MonadFix m, HasVtyWidgetCtx t m, HasVtyInput t m, MonadFocus t m, MonadLayout t m)
351− => Dynamic t Int
352− -> m a
353− -> m a
354−fixed n = fmap snd . fixed' n
355−
356−-- | A tile that can grow, but will always occupy at least the given minimum
357−-- size. Returns the 'FocusId' of the tile, allowing for manual focus
358−-- management.
359−stretch'
360− :: (Reflex t, MonadNodeId m, MonadFix m, HasVtyWidgetCtx t m, HasVtyInput t m, MonadFocus t m, MonadLayout t m)
361− => Dynamic t Int
362− -> m a
363− -> m (FocusId, a)
364−stretch' minSz = tile' (Constraint_Min <$> minSz)
365−
366−-- | A tile that can grow, but will always occupy at least the given minimum
367−-- size.
368−stretch
369− :: (Reflex t, MonadNodeId m, MonadFix m, HasVtyWidgetCtx t m, HasVtyInput t m, MonadFocus t m, MonadLayout t m)
370− => Dynamic t Int
371− -> m a
372− -> m a
373−stretch minSz = fmap snd . stretch' minSz
374−
375−-- | A tile that can grow or shrink without constraint. Returns the 'FocusId'
376−-- of the tile, allowing for manual focus management.
377−flex'
378− :: (Reflex t, MonadNodeId m, MonadFix m, HasVtyWidgetCtx t m, HasVtyInput t m, MonadFocus t m, MonadLayout t m)
379− => m a
380− -> m (FocusId, a)
381−flex' = tile' (pure $ Constraint_Min 0)
382−
383−-- | A tile that can grow or shrink without constraint.
384−flex
385− :: (Reflex t,MonadNodeId m, MonadFix m, HasVtyWidgetCtx t m, HasVtyInput t m, MonadFocus t m, MonadLayout t m)
386− => m a
387− -> m a
388−flex = fmap snd . flex'
389−
390−-- | Produce a layout solution given a starting orientation, the overall screen
391−-- size, and a set of constraints.
392−solve
393− :: Orientation
394− -> Region
395− -> LayoutForest (Constraint, Orientation)
396− -> LayoutTree (Region, Orientation)
397−solve o0 r0 (LayoutForest cs) =
398− let a = map (\(x, t@(LayoutTree (c, _) _)) -> ((x, t), c)) $ OMap.assocs cs
399− extent = case o0 of
400− Orientation_Row -> _region_width r0
401− Orientation_Column -> _region_height r0
402− sizes = computeEdges $ computeSizes extent a
403− chunks = [ (nodeId, solve o1 r1 f)
404− | ((nodeId, LayoutTree (_, o1) f), sz) <- sizes
405− , let r1 = chunk o0 r0 sz
406− ]
407− in LayoutTree (r0, o0) $ fromListLF chunks
408− where
409− computeEdges :: [(a, Int)] -> [(a, (Int, Int))]
410− computeEdges = ($ []) . fst . foldl (\(m, offset) (a, sz) ->
411− (((a, (offset, sz)) :) . m, sz + offset)) (id, 0)
412− computeSizes
413− :: Int
414− -> [(a, Constraint)]
415− -> [(a, Int)]
416− computeSizes available constraints =
417− let minTotal = sum $ ffor constraints $ \case
418− (_, Constraint_Fixed n) -> n
419− (_, Constraint_Min n) -> n
420− leftover = max 0 (available - minTotal)
421− numStretch = length $ filter (isMin . snd) constraints
422− szStretch = floor $ leftover % max numStretch 1
423− adjustment = max 0 $ available - minTotal - szStretch * numStretch
424− in snd $ mapAccumL (\adj (a, c) -> case c of
425− Constraint_Fixed n -> (adj, (a, n))
426− Constraint_Min n -> (0, (a, n + szStretch + adj))) adjustment constraints
427− isMin (Constraint_Min _) = True
428− isMin _ = False
429−
430−-- | Produce a 'Region' given a starting orientation and region, and the offset
431−-- and main-axis size of the chunk.
432−chunk :: Orientation -> Region -> (Int, Int) -> Region
433−chunk o r (offset, sz) = case o of
434− Orientation_Column -> r
435− { _region_top = _region_top r + offset
436− , _region_height = sz
437− }
438− Orientation_Row -> r
439− { _region_left = _region_left r + offset
440− , _region_width = sz
441− }
442−
443419-- | Runs a 'Layout' action, using the given orientation and region to
444420-- calculate layout solutions.
445421runLayout
@@ -453,54 +429,28 @@ runLayout o r (Layout x) = do
453429 let solutions = solve <$> o <*> r <*> w
454430 return result
455431
456−-- | Request focus be shifted backward and forward based on tab presses. <Tab>
457−-- shifts focus forward and <Shift+Tab> shifts focus backward.
458−tabNavigation :: (Reflex t, MonadNodeId m, HasVtyInput t m, MonadFocus t m) => m ()
459−tabNavigation = do
460− fwd <- fmap (const 1) <$> key (V.KChar '\t')
461− back <- fmap (const (-1)) <$> key V.KBackTab
462− requestFocus $ Refocus_Shift <$> leftmost [fwd, back]
432+-- | Initialize and run the layout monad, using all of the available screen space.
433+initLayout :: (HasDisplaySize t m, MonadFix m) => Layout t m a -> m a
434+initLayout f = do
435+ dw <- displayWidth
436+ dh <- displayHeight
437+ let r = Region 0 0 <$> dw <*> dh
438+ runLayout (pure Orientation_Column) r f
463439
464−test :: IO ()
465−test = mainWidget $ do
466− inp <- input
467− let box' w = do
468− f <- focus
469− box (ffor (current f) $ \x -> if x then doubleBoxStyle else singleBoxStyle) $ w
470− text' t = do
471− f <- focus
472− box' $ richText (RichTextConfig $ current $ (\x -> if x then V.withStyle V.defAttr V.underline else V.defAttr) <$> f) t
473− initManager_ $ do
474− tabNavigation
475− col $ do
476− fixed 5 $ box' $ row $ do
477− flex $ do
478− text' "asdf"
479− flex $ text' "xyz"
480− fixed 5 $ do
481− text' " 1"
482− row $ do
483− flex $ do
484− text' "2"
485− flex $ text' "3"
486− flex $ text' "xyz"
487− flex $ text' "xyz"
488− flex $ text' "asdf"
489− flex $ text' "asdf"
490− return $ fforMaybe inp $ \case
491− V.EvKey (V.KChar 'c') [V.MCtrl] -> Just ()
492− _ -> Nothing
440+-- * The tile "window manager"
441+--
442+-- $tiling
443+-- Generally MonadLayout and MonadFocus are used together to build a user
444+-- interface. These functions check the available screen size and initialize
445+-- the layout monad with that information, and also initialize the focus monad.
493446
494447-- | Initialize a 'Layout' and 'Focus' management context, returning the produced 'FocusSet'.
495448initManager
496449 :: (HasDisplaySize t m, Reflex t, MonadHold t m, MonadFix m)
497450 => Layout t (Focus t m) a
498451 -> m (a, Dynamic t FocusSet)
499−initManager f = do
500− dw <- displayWidth
501− dh <- displayHeight
502− let r = Region 0 0 <$> dw <*> dh
503− runFocus $ runLayout (pure Orientation_Column) r f
452+initManager =
453+ runFocus . initLayout
504454
505455-- | Initialize a 'Layout' and 'Focus' management context.
506456initManager_
@@ -508,3 +458,53 @@ initManager_
508458 => Layout t (Focus t m) a
509459 -> m a
510460initManager_ = fmap fst . initManager
461+
462+-- ** Layout tiles
463+
464+-- *** Focusable
465+
466+-- | A widget that is focusable and occupies a layout region based on the
467+-- provided constraint. Returns the 'FocusId' allowing for manual focus
468+-- management.
469+tile'
470+ :: (MonadNodeId m, MonadFix m, Reflex t, HasVtyWidgetCtx t m, HasVtyInput t m, MonadFocus t m, MonadLayout t m)
471+ => Dynamic t Constraint
472+ -> m a
473+ -> m (FocusId, a)
474+tile' c w = do
475+ fid <- makeFocus
476+ r <- region c
477+ fid' <- focusedId
478+ parentFocused <- isFocused fid
479+ rec (click, result, subs) <- pane r focused $ do
480+ m <- mouseDown V.BLeft
481+ (x, sf) <- subFoci w
482+ pure (m, x, sf)
483+ let childFocused = liftA2 (\f s -> case f of
484+ Nothing -> False
485+ Just f' -> OSet.member f' $ unFocusSet s) fid' subs
486+ let focused = (||) <$> parentFocused <*> childFocused
487+ requestFocus $ Refocus_Id fid <$ click
488+ pure (fid, result)
489+
490+-- | A widget that is focusable and occupies a layout region based on the
491+-- provided constraint.
492+tile
493+ :: (MonadNodeId m, MonadFix m, Reflex t, HasVtyWidgetCtx t m, HasVtyInput t m, MonadFocus t m, MonadLayout t m)
494+ => Dynamic t Constraint
495+ -> m a
496+ -> m a
497+tile c = fmap snd . tile' c
498+
499+-- *** Unfocusable
500+
501+-- | A widget that is not focusable and occupies a layout region based on the
502+-- provided constraint.
503+grout
504+ :: (Reflex t, MonadNodeId m, HasVtyWidgetCtx t m, MonadLayout t m)
505+ => Dynamic t Constraint
506+ -> m a
507+ -> m a
508+grout c w = do
509+ r <- region c
510+ pane r (pure False) w