content: {-|
  Module      : Monomer.Widgets.Container
  Copyright   : (c) 2018 Francisco Vallarino
  License     : BSD-3-Clause (see the LICENSE file)
  Maintainer  : fjvallarino@gmail.com
  Stability   : experimental
  Portability : non-portable
  
  Helper for creating widgets with children elements.
  -}
  {-# LANGUAGE BangPatterns #-}
  {-# LANGUAGE ExistentialQuantification #-}
  {-# LANGUAGE FlexibleContexts #-}
  {-# LANGUAGE RankNTypes #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  
  module Monomer.Widgets.Container (
    -- * Re-exported modules
    module Monomer.Core,
    module Monomer.Core.Combinators,
    module Monomer.Event,
    module Monomer.Graphics,
    module Monomer.Widgets.Util,
  
    -- * Configuration
    ContainerGetBaseStyle,
    ContainerGetCurrentStyle,
    ContainerCreateContainerFromModel,
    ContainerUpdateCWenvHandler,
    ContainerInitHandler,
    ContainerInitPostHandler,
    ContainerMergeChildrenReqHandler,
    ContainerMergeHandler,
    ContainerMergePostHandler,
    ContainerDisposeHandler,
    ContainerFindNextFocusHandler,
    ContainerFindByPointHandler,
    ContainerFilterHandler,
    ContainerEventHandler,
    ContainerMessageHandler,
    ContainerGetSizeReqHandler,
    ContainerResizeHandler,
    ContainerRenderHandler,
    Container(..),
    updateWenvOffset,
  
    -- * Constructors
    createContainer
  ) where
  
  import Control.Applicative ((<|>))
  import Control.Lens ((&), (^.), (.~), (%~), (<>~))
  import Control.Monad
  import Data.Default
  import Data.Foldable (foldl')
  import Data.Maybe
  import Data.Map.Strict (Map)
  import Data.Typeable (Typeable)
  import Data.Sequence (Seq(..), (<|), (|>))
  
  import qualified Data.Map.Strict as M
  import qualified Data.Sequence as Seq
  
  import Monomer.Core
  import Monomer.Core.Combinators
  import Monomer.Event
  import Monomer.Graphics
  import Monomer.Widgets.Util
  
  import qualified Monomer.Lens as L
  
  {-|
  Returns the base style for this type of widget.
  
  Usually this style comes from the active theme.
  -}
  type ContainerGetBaseStyle s e
    = GetBaseStyle s e  -- ^ The base style for a new node.
  
  {-|
  Returns the active style for this type of widget. It depends on the state of
  the widget, which can be:
  
  - Basic
  - Hovered
  - Focused
  - Hovered and Focused
  - Active
  - Disabled
  
  In general there's no needed to override it, except when the widget does not use
  the full content rect.
  
  An example can be found in "Monomer.Widgets.Containers.Tooltip".
  -}
  type ContainerGetCurrentStyle s e
    = WidgetEnv s e      -- ^ The widget environment.
    -> WidgetNode s e    -- ^ The widget node.
    -> StyleState        -- ^ The active style for the node.
  
  {-|
  Returns an updated version of the Container instance. Currently only used
  during merge. Not needed in general, except for widgets that modify offset
  or layout direction.
  
  An example can be found in "Monomer.Widgets.Containers.Scroll".
  
  Note:
  
  Some widgets, scroll for example, provide offset/layout direction to the
  Container instance; this information is passed down to children widgets in all
  the lifecycle methods. During merge a problem arises: the new node has not yet
  processed the old state and, since it is the Container whose merge function is
  being invoked, it is not able to provide the correct offset/layout direction.
  It is also not possible to directly extract this information from the old node,
  because we have a Widget instance and not its Container instance. This function
  provides a workaround for this.
  
  This is a hacky solution; a more flexible dispatcher for Composite could avoid it.
  -}
  type ContainerCreateContainerFromModel s e a
    = WidgetEnv s e            -- ^ The widget environment.
    -> WidgetNode s e          -- ^ The widget node.
    -> a                       -- ^ The previous model.
    -> Maybe (Container s e a) -- ^ An updated Container instance.
  
  {-|
  Updates the widget environment before passing it down to children. This function
  is called during the execution of all the widget functions. Useful for
  restricting viewport or modifying other kind of contextual information.
  
  An example can be found in "Monomer.Widgets.Containers.ThemeSwitch".
  -}
  type ContainerUpdateCWenvHandler s e
    = WidgetEnv s e    -- ^ The widget environment.
    -> WidgetNode s e  -- ^ The widget node.
    -> WidgetNode s e  -- ^ The child node.
    -> Int             -- ^ The index of the node.
    -> WidgetEnv s e   -- ^ The updated widget environment.
  
  {-|
  Initializes the given node. This could include rebuilding the widget in case
  internal state needs to use model/environment information, generate user
  events or make requests to the runtime.
  
  During init, a widget can return an arbitrary list of child widgets as part of
  its result, even replacing those provided by the user. For this reason, child
  widgets are initialized after this init function is complete. In case you need
  to retrieve the 'WidgetId' of a child widget, you should use 'containerInitPost'
  instead.
  
  An example can be found in "Monomer.Widgets.Containers.SelectList".
  
  Most of the current containers serve layout purposes and don't need a custom
  /init/.
  -}
  type ContainerInitHandler s e
    = WidgetEnv s e      -- ^ The widget environment.
    -> WidgetNode s e    -- ^ The widget node.
    -> WidgetResult s e  -- ^ The result of the init operation.
  
  {-|
  Allows making further operations after children have been initialized.
  
  Note: if state was modified on 'containerInit', you should use the new state
  provided as an argument, since the state referenced in the closure will be
  outdated.
  -}
  type ContainerInitPostHandler s e a
    = WidgetEnv s e      -- ^ The widget environment.
    -> WidgetNode s e    -- ^ The widget node.
    -> a                 -- ^ The current state of the widget node.
    -> WidgetResult s e  -- ^ The result after children have been initialized.
    -> WidgetResult s e  -- ^ The result of the init post operation.
  
  {-|
  Returns whether merge is required for children. It's mostly used for performance
  optimization.
  
  An example can be found in "Monomer.Widgets.Containers.SelectList".
  -}
  type ContainerMergeChildrenReqHandler s e a
    = WidgetEnv s e    -- ^ The widget environment.
    -> WidgetNode s e  -- ^ The widget node.
    -> WidgetNode s e  -- ^ The previous widget node.
    -> a               -- ^ The state of the previous widget node.
    -> Bool            -- ^ True if widget is needed.
  
  {-|
  Merges the current node with the node it matched with during the merge process.
  Receives the newly created node (whose *init* function is not called), the
  previous node and the state extracted from that node. This process is widget
  dependent, and may use or ignore the previous state depending on newly available
  information.
  
  In general, you want to at least keep the previous state unless the widget is
  stateless or only consumes model/environment information.
  
  During merge, a widget can return an arbitrary list of child widgets as part of
  its result, even replacing those provided by the user. For this reason, new
  child widgets are initialized after this merge function is complete. In case you
  need to retrieve the 'WidgetId' of a child widget, you should use
  'containerMergePost' instead.
  
  Examples can be found in "Monomer.Widgets.Containers.Fade" and
  "Monomer.Widgets.Containers.Tooltip". On the other hand,
  "Monomer.Widgets.Containers.Grid" does not need to override merge since it's
  stateless.
  -}
  type ContainerMergeHandler s e a
    = WidgetEnv s e      -- ^ The widget environment.
    -> WidgetNode s e    -- ^ The widget node.
    -> WidgetNode s e    -- ^ The previous widget node.
    -> a                 -- ^ The state of the previous widget node.
    -> WidgetResult s e  -- ^ The result of the merge operation.
  
  {-|
  Allows making further operations after children have been merged.
  
  Examples can be found in "Monomer.Widgets.Containers.SelectList" and
  "Monomer.Widgets.Containers.ZStack".
  
  Note: if state was modified during merge, you should use the new state provided
  as an argument, since the state referenced in the closure will be outdated.
  -}
  type ContainerMergePostHandler s e a
    = WidgetEnv s e      -- ^ The widget environment.
    -> WidgetNode s e    -- ^ The widget node.
    -> WidgetNode s e    -- ^ The previous widget node.
    -> a                 -- ^ The state of the previous widget node.
    -> a                 -- ^ The current state of the widget node.
    -> WidgetResult s e  -- ^ The result after children have been merged.
    -> WidgetResult s e  -- ^ The result of the merge post operation.
  
  {-|
  Disposes the current node. Only used by widgets which allocate resources during
  /init/ or /merge/, and will usually involve requests to the runtime.
  
  An example can be found in "Monomer.Widgets.Containers.Dropdown".
  -}
  type ContainerDisposeHandler s e
    = WidgetEnv s e      -- ^ The widget environment.
    -> WidgetNode s e    -- ^ The widget node.
    -> WidgetResult s e  -- ^ The result of the dispose operation.
  
  {-|
  Returns the next focusable node. What next/previous is, depends on how the
  container works. Moving right -> bottom is usually considered forward.
  -}
  type ContainerFindNextFocusHandler s e
    = WidgetEnv s e          -- ^ The widget environment.
    -> WidgetNode s e        -- ^ The widget node.
    -> FocusDirection        -- ^ The direction in which focus is moving.
    -> Path                  -- ^ The start path from which to search.
    -> Seq (WidgetNode s e)  -- ^ The next focusable node info.
  
  {-|
  Returns the currently hovered widget, if any. If the widget is rectangular and
  uses the full content area, there is not need to override this function.
  
  An example can be found in "Monomer.Widgets.Containers.Dropdown".
  -}
  type ContainerFindByPointHandler s e
    = WidgetEnv s e    -- ^ The widget environment.
    -> WidgetNode s e  -- ^ The widget node.
    -> Path            -- ^ The start path from which to search.
    -> Point           -- ^ The point to test for.
    -> Maybe Int       -- ^ The hovered child index, if any.
  
  {-|
  Receives a System event and, optionally, modifies the event, its target, or
  stops the event propagation by returning Nothing.
  
  Examples can be found in "Monomer.Widgets.Containers.Base.LabeledItem".
  -}
  type ContainerFilterHandler s e
    = WidgetEnv s e               -- ^ The widget environment.
    -> WidgetNode s e             -- ^ The widget node.
    -> Path                       -- ^ The target path of the event.
    -> SystemEvent                -- ^ The SystemEvent to handle.
    -> Maybe (Path, SystemEvent)  -- ^ The optional modified event/target.
  
  {-|
  Receives a System event and, optionally, returns a result. This can include an
  updated version of the widget (in case it has internal state), user events or
  requests to the runtime.
  
  Examples can be found in "Monomer.Widgets.Containers.Draggable" and
  "Monomer.Widgets.Containers.Keystroke".
  -}
  type ContainerEventHandler s e
    = WidgetEnv s e              -- ^ The widget environment.
    -> WidgetNode s e            -- ^ The widget node.
    -> Path                      -- ^ The target path of the event.
    -> SystemEvent               -- ^ The SystemEvent to handle.
    -> Maybe (WidgetResult s e)  -- ^ The result of handling the event, if any.
  
  {-|
  Receives a message and, optionally, returns a result. This can include an
  updated version of the widget (in case it has internal state), user events or
  requests to the runtime. There is no validation regarding the message type, and
  the widget should take care of _casting_ to the correct type using
  "Data.Typeable.cast"
  
  Examples can be found in "Monomer.Widgets.Containers.Fade" and
  "Monomer.Widgets.Containers.Scroll".
  -}
  type ContainerMessageHandler s e
    = forall i . Typeable i
    => WidgetEnv s e             -- ^ The widget environment.
    -> WidgetNode s e            -- ^ The widget node.
    -> Path                      -- ^ The target path of the message.
    -> i                         -- ^ The message to handle.
    -> Maybe (WidgetResult s e)  -- ^ The result of handling the message, if any.
  
  {-|
  Returns the preferred size for the widget. This size should not include border
  and padding; those are added automatically by Container.
  
  This is called to update WidgetNodeInfo only at specific times.
  
  Examples can be found in "Monomer.Widgets.Containers.Grid" and
  "Monomer.Widgets.Containers.Stack".
  -}
  type ContainerGetSizeReqHandler s e
    = WidgetEnv s e          -- ^ The widget environment.
    -> WidgetNode s e        -- ^ The widget node.
    -> Seq (WidgetNode s e)  -- ^ The children widgets
    -> (SizeReq, SizeReq)    -- ^ The horizontal and vertical requirements.
  
  {-|
  Assigns space to children according to the Container's logic, returning a 'Seq'
  of 'Rect's in the same order of the child widgets. If the container has a single
  child it is not generally necessary to override this function. A new version of
  the Container may be returned if state needs to be updated.
  
  Examples can be found in "Monomer.Widgets.Containers.Grid" and
  "Monomer.Widgets.Containers.Stack".
  
  Note: if the Container's state depends on the current size, visibility should be
  considered; since invisible nodes receive zero space, it may affect calculations
  and cause unexpected behavior when the widget is made visible again. Examples
  can be found in "Monomer.Widgets.Containers.Scroll" and
  "Monomer.Widgets.Containers.Split".
  -}
  type ContainerResizeHandler s e
    = WidgetEnv s e                  -- ^ The widget environment.
    -> WidgetNode s e                -- ^ The widget node.
    -> Rect                          -- ^ The new viewport.
    -> Seq (WidgetNode s e)          -- ^ The children widgets
    -> (WidgetResult s e, Seq Rect)  -- ^ The result of resizing the widget.
  
  {-|
  Renders the widget's content using the given Renderer. In general, this method
  needs to be overridden. There are two render methods: one runs before children,
  the other one after.
  
  Examples can be found in "Monomer.Widgets.Containers.Draggable" and
  "Monomer.Widgets.Containers.Scroll".
  -}
  type ContainerRenderHandler s e
    = WidgetEnv s e      -- ^ The widget environment.
    -> WidgetNode s e    -- ^ The widget node.
    -> Renderer          -- ^ The renderer, providing low level drawing functions.
    -> IO ()             -- ^ The IO action with rendering instructions.
  
  -- | Interface for Container widgets.
  data Container s e a = Container {
    -- | True if border and padding should be added to size requirement. Defaults
    --   to True.
    containerAddStyleReq :: Bool,
    -- | Offset to apply to children. This not only includes rendering, but also
    --   updating SystemEvents and all coordinate related functions.
    containerChildrenOffset :: Maybe Point,
    -- | Scissor to apply to child widgets. This is not the same as the widget
    --   enabled by containerUseScissor
    containerChildrenScissor :: Maybe Rect,
    -- | If True, the container will render its background and border. In some
    --   cases passing rendering control to children is useful. Defaults to True.
    containerDrawDecorations :: Bool,
    -- | The layout direction generated by this widget. If one is indicated, it
    --   can be used by widgets such as "Monomer.Widgets.Singles.Spacer"
    containerLayoutDirection :: LayoutDirection,
    -- | If True, when none of the children is found under the pointer, indicates
    --   an event will not be handled. If False, the parent (i.e., current) widget
    --   will be returned. This is useful when using zstack and wanting for events
    --   to be handled in lower layers.
    containerIgnoreEmptyArea :: Bool,
    -- | True if style cursor should be ignored. If it's False, cursor changes need
    --   to be handled in custom code. Defaults to False.
    containerUseCustomCursor :: Bool,
    -- | If true, it will ignore extra space assigned by the parent container, but
    --   it will not use more space than assigned. Defaults to False.
    containerUseCustomSize :: Bool,
    -- | If true, it will accept the size requested by children, restricted to the
    --   space already assigned.
    containerUseChildrenSizes :: Bool,
    -- | True if automatic scissoring needs to be applied. Defaults to False.
    containerUseScissor :: Bool,
    -- | Returns the base style for this type of widget.
    containerGetBaseStyle :: ContainerGetBaseStyle s e,
    -- | Returns an updated version of the Container instance.
    containerCreateContainerFromModel :: ContainerCreateContainerFromModel s e a,
    -- | Returns the current style, depending on the status of the widget.
    containerGetCurrentStyle :: ContainerGetCurrentStyle s e,
    -- | Updates the widget environment before passing it down to children.
    containerUpdateCWenv :: ContainerUpdateCWenvHandler s e,
    -- | Initializes the given node.
    containerInit :: ContainerInitHandler s e,
    -- | Allow for extra steps after children are initialized.
    containerInitPost :: ContainerInitPostHandler s e a,
    -- | Returns whether merge is required for children.
    containerMergeChildrenReq :: ContainerMergeChildrenReqHandler s e a,
    -- | Merges the node with the node it matched with during the merge process.
    containerMerge :: ContainerMergeHandler s e a,
    -- | Allow for extra steps after children are merged.
    containerMergePost :: ContainerMergePostHandler s e a,
    -- | Disposes the current node.
    containerDispose :: ContainerDisposeHandler s e,
    -- | Returns the next focusable node.
    containerFindNextFocus :: ContainerFindNextFocusHandler s e,
    -- | Returns the currently hovered widget, if any.
    containerFindByPoint :: ContainerFindByPointHandler s e,
    -- | Receives a System event and, optionally, filters/modifies it.
    containerFilterEvent :: ContainerFilterHandler s e,
    -- | Receives a System event and, optionally, returns a result.
    containerHandleEvent :: ContainerEventHandler s e,
    -- | Receives a message and, optionally, returns a result.
    containerHandleMessage :: ContainerMessageHandler s e,
    -- | Returns the preferred size for the widget.
    containerGetSizeReq :: ContainerGetSizeReqHandler s e,
    -- | Resizes the widget to the provided size.
    containerResize :: ContainerResizeHandler s e,
    -- | Renders the widget's content. This runs before children's render.
    containerRender :: ContainerRenderHandler s e,
    -- | Renders the widget's content. This runs after children's render.
    containerRenderAfter :: ContainerRenderHandler s e
  }
  
  instance Default (Container s e a) where
    def = Container {
      containerAddStyleReq = True,
      containerChildrenOffset = Nothing,
      containerChildrenScissor = Nothing,
      containerDrawDecorations = True,
      containerLayoutDirection = LayoutNone,
      containerIgnoreEmptyArea = False,
      containerUseCustomCursor = False,
      containerUseCustomSize = False,
      containerUseChildrenSizes = False,
      containerUseScissor = False,
      containerGetBaseStyle = defaultGetBaseStyle,
      containerGetCurrentStyle = defaultGetCurrentStyle,
      containerCreateContainerFromModel = defaultCreateContainerFromModel,
      containerUpdateCWenv = defaultUpdateCWenv,
      containerInit = defaultInit,
      containerInitPost = defaultInitPost,
      containerMergeChildrenReq = defaultMergeRequired,
      containerMerge = defaultMerge,
      containerMergePost = defaultMergePost,
      containerDispose = defaultDispose,
      containerFindNextFocus = defaultFindNextFocus,
      containerFindByPoint = defaultFindByPoint,
      containerFilterEvent = defaultFilterEvent,
      containerHandleEvent = defaultHandleEvent,
      containerHandleMessage = defaultHandleMessage,
      containerGetSizeReq = defaultGetSizeReq,
      containerResize = defaultResize,
      containerRender = defaultRender,
      containerRenderAfter = defaultRender
    }
  
  {-|
  Creates a widget based on the Container infrastructure. An initial state and the
  Container definition need to be provided. In case internal state is not needed,
  __()__ can be provided. Using the __def__ instance as a starting point is
  recommended to focus on overriding only what is needed:
  
  @
  widget = createContainer () def {
    containerRender = ...
  }
  @
  -}
  createContainer
    :: WidgetModel a
    => a
    -> Container s e a
    -> Widget s e
  createContainer !state !container = Widget {
    widgetInit = initWrapper container,
    widgetMerge = mergeWrapper container,
    widgetDispose = disposeWrapper container,
    widgetGetState = makeState state,
    widgetGetInstanceTree = getInstanceTreeWrapper container,
    widgetFindNextFocus = findNextFocusWrapper container,
    widgetFindByPoint = findByPointWrapper container,
    widgetFindBranchByPath = containerFindBranchByPath,
    widgetHandleEvent = handleEventWrapper container,
    widgetHandleMessage = handleMessageWrapper container,
    widgetGetSizeReq = getSizeReqWrapper container,
    widgetResize = resizeWrapper container,
    widgetRender = renderWrapper container
  }
  
  -- | Get base style for component
  defaultGetBaseStyle :: ContainerGetBaseStyle s e
  defaultGetBaseStyle wenv node = Nothing
  
  defaultGetCurrentStyle :: ContainerGetCurrentStyle s e
  defaultGetCurrentStyle wenv node = currentStyle wenv node
  
  defaultCreateContainerFromModel :: ContainerCreateContainerFromModel s e a
  defaultCreateContainerFromModel wenv node state = Nothing
  
  defaultUpdateCWenv :: ContainerUpdateCWenvHandler s e
  defaultUpdateCWenv wenv node cnode cidx = wenv
  
  getUpdateCWenv
    :: Container s e a
    -> WidgetEnv s e
    -> WidgetNode s e
    -> WidgetNode s e
    -> Int
    -> WidgetEnv s e
  getUpdateCWenv container !wenv !node !cnode !cidx = newWenv where
    cOffset = containerChildrenOffset container
    updateCWenv = containerUpdateCWenv container
    layoutDirection = containerLayoutDirection container
  
    pViewport = node ^. L.info . L.viewport
    cViewport = cnode ^. L.info . L.viewport
    newViewport = fromMaybe def (intersectRects pViewport cViewport)
  
    offsetWenv !wenv
      | isJust cOffset = updateWenvOffset container wenv node newViewport
      | otherwise = wenv
    !directionWenv = wenv
      & L.layoutDirection .~ layoutDirection
  
    !newWenv = updateCWenv (offsetWenv directionWenv) node cnode cidx
  
  {-|
  Helper function that updates widget environment based on current container
  information. In case the created container needs to pass information down using
  wenv, it should call this function first and update the resulting wenv.
  -}
  updateWenvOffset
    :: Container s e a  -- ^ The container config
    -> WidgetEnv s e    -- ^ The widget environment.
    -> WidgetNode s e   -- ^ The widget node.
    -> Rect             -- ^ The target viewport.
    -> WidgetEnv s e    -- ^ THe updated widget environment.
  updateWenvOffset container wenv node viewport = newWenv where
    cOffset = containerChildrenOffset container
    offset = fromMaybe def cOffset
  
    updateMain (path, point)
      | isNodeParentOfPath node path = (path, addPoint (negPoint offset) point)
      | otherwise = (path, point)
  
    newWenv = wenv
      & L.viewport .~ moveRect (negPoint offset) viewport
      & L.inputStatus . L.mousePos %~ addPoint (negPoint offset)
      & L.inputStatus . L.mousePosPrev %~ addPoint (negPoint offset)
      & L.offset %~ addPoint offset
      & L.mainBtnPress %~ fmap updateMain
  
  -- | Init handler
  defaultInit :: ContainerInitHandler s e
  defaultInit wenv node = resultNode node
  
  defaultInitPost :: ContainerInitPostHandler s e a
  defaultInitPost wenv node state result = result
  
  initWrapper
    :: WidgetModel a
    => Container s e a
    -> WidgetEnv s e
    -> WidgetNode s e
    -> WidgetResult s e
  initWrapper container wenv node = result where
    initHandler = containerInit container
    initPostHandler = containerInitPost container
    getBaseStyle = containerGetBaseStyle container
    updateCWenv = getUpdateCWenv container
  
    styledNode = initNodeStyle getBaseStyle wenv node
    WidgetResult tempNode reqs = initHandler wenv styledNode
    children = tempNode ^. L.children
    initChild idx child = widgetInit newWidget cwenv newChild where
      newChild = cascadeCtx wenv tempNode child idx
      cwenv = updateCWenv wenv node newChild idx
      newWidget = newChild ^. L.widget
    results = Seq.mapWithIndex initChild children
    newReqs = foldMap _wrRequests results
    newChildren = fmap _wrNode results
    newNode = updateSizeReq wenv $ tempNode
      & L.children .~ newChildren
  
    tmpResult = WidgetResult newNode (reqs <> newReqs)
    newState = widgetGetState (newNode ^. L.widget) wenv newNode
    result = case useState newState of
      Just st -> initPostHandler wenv newNode st tmpResult
      Nothing -> tmpResult
  
  -- | Merging
  defaultMergeRequired :: ContainerMergeChildrenReqHandler s e a
  defaultMergeRequired wenv newNode oldNode oldState = True
  
  defaultMerge :: ContainerMergeHandler s e a
  defaultMerge wenv newNode oldNode oldState = resultNode newNode
  
  defaultMergePost :: ContainerMergePostHandler s e a
  defaultMergePost wenv newNode oldNode oldState newState result = result
  
  mergeWrapper
    :: WidgetModel a
    => Container s e a
    -> WidgetEnv s e
    -> WidgetNode s e
    -> WidgetNode s e
    -> WidgetResult s e
  mergeWrapper container wenv newNode oldNode = newResult where
    getBaseStyle = containerGetBaseStyle container
    createContainerFromModel = containerCreateContainerFromModel container
  
    mergeRequiredHandler = containerMergeChildrenReq container
    mergeHandler = containerMerge container
    mergePostHandler = containerMergePost container
  
    oldState = widgetGetState (oldNode ^. L.widget) wenv oldNode
    mergeRequired = case useState oldState of
      Just ostate -> mergeRequiredHandler wenv newNode oldNode ostate
      Nothing -> True
  
    styledNode = initNodeStyle getBaseStyle wenv newNode
    pResult = mergeParent mergeHandler wenv styledNode oldNode oldState
    pNode = pResult ^. L.node
  
    -- Check if an updated container can be used for offset/layout direction.
    updateCWenv = case useState oldState >>= createContainerFromModel wenv pNode of
      Just newContainer -> getUpdateCWenv newContainer
      _ -> getUpdateCWenv container
  
    cWenvHelper idx child = cwenv where
      cwenv = updateCWenv wenv pNode child idx
  
    cResult = mergeChildren cWenvHelper wenv pNode oldNode pResult
    vResult = mergeChildrenCheckVisible oldNode cResult
  
    flagsChanged = nodeFlagsChanged oldNode pNode
    themeChanged = wenv ^. L.themeChanged
    isReload = isWidgetReload wenv
    mResult
      | isReload || mergeRequired || flagsChanged || themeChanged = vResult
      | otherwise = pResult & L.node . L.children .~ oldNode ^. L.children
  
    mNode = mResult ^. L.node
    mState = widgetGetState (mNode ^. L.widget) wenv mNode
    postRes = case (,) <$> useState oldState <*> useState mState of
      Just (ost, st) -> mergePostHandler wenv mNode oldNode ost st mResult
      Nothing -> mResult
  
    tmpResult = postRes
      & handleUserSizeReqChange wenv oldNode
      & handleWidgetIdChange oldNode
  
    newResult
      | isResizeAnyResult (Just tmpResult) = tmpResult
          & L.node .~ updateSizeReq wenv (tmpResult ^. L.node)
      | otherwise = tmpResult
  
  mergeParent
    :: WidgetModel a
    => ContainerMergeHandler s e a
    -> WidgetEnv s e
    -> WidgetNode s e
    -> WidgetNode s e
    -> Maybe WidgetState
    -> WidgetResult s e
  mergeParent mergeHandler wenv newNode oldNode oldState = result where
    oldInfo = oldNode ^. L.info
    tempNode = newNode
      & L.info . L.widgetId .~ oldInfo ^. L.widgetId
      & L.info . L.viewport .~ oldInfo ^. L.viewport
      & L.info . L.sizeReqW .~ oldInfo ^. L.sizeReqW
      & L.info . L.sizeReqH .~ oldInfo ^. L.sizeReqH
    result = case useState oldState of
      Just ostate -> mergeHandler wenv tempNode oldNode ostate
      Nothing -> resultNode tempNode
  
  mergeChildren
    :: (Int -> WidgetNode s e -> WidgetEnv s e)
    -> WidgetEnv s e
    -> WidgetNode s e
    -> WidgetNode s e
    -> WidgetResult s e
    -> WidgetResult s e
  mergeChildren updateCWenv !wenv !newNode !oldNode !pResult = newResult where
    WidgetResult pNode pReqs = pResult
    oldChildren = oldNode ^. L.children
    oldIts = Seq.mapWithIndex (,) oldChildren
    updatedChildren = pNode ^. L.children
  
    mergeChild idx child = (idx, cascadeCtx wenv pNode child idx)
    newIts = Seq.mapWithIndex mergeChild updatedChildren
    oldKeys = buildLocalMap oldChildren
    newKeys = buildLocalMap (snd <$> newIts)
  
    mpairs = mergeChildSeq updateCWenv wenv oldKeys newKeys newNode oldIts newIts
    (mergedResults, removedResults) = mpairs
    mergedChildren = fmap _wrNode mergedResults
    mergedReqs = foldMap _wrRequests mergedResults
    removedReqs = foldMap _wrRequests removedResults
    mergedNode = pNode & L.children .~ mergedChildren
  
    newReqs = pReqs <> mergedReqs <> removedReqs
    !newResult = WidgetResult mergedNode newReqs
  
  mergeChildSeq
    :: (Int -> WidgetNode s e -> WidgetEnv s e)
    -> WidgetEnv s e
    -> WidgetKeyMap s e
    -> WidgetKeyMap s e
    -> WidgetNode s e
    -> Seq (Int, WidgetNode s e)
    -> Seq (Int, WidgetNode s e)
    -> (Seq (WidgetResult s e), Seq (WidgetResult s e))
  mergeChildSeq updateCWenv wenv oldKeys newKeys newNode oldIts Empty = res where
    isMember = flip M.member newKeys
    dispose (!idx, !child) = case isMember <$> child ^. L.info . L.key of
      Just True -> WidgetResult child Empty
      _ -> widgetDispose (child ^. L.widget) wenv child
    !removed = fmap dispose oldIts
    !res = (Empty, removed)
  
  mergeChildSeq updateCWenv wenv oldKeys newKeys newNode Empty newIts = res where
    init (idx, !child) = newWidget where
      newWidgetId = child ^. L.info . L.widgetId
      newWidget = widgetInit (child ^. L.widget) wenv child
        & L.requests %~ (|> ResizeWidgets newWidgetId)
  
    !merged = fmap init newIts
    !res = (merged, Empty)
  
  mergeChildSeq updateCWenv wenv oldKeys newKeys newNode oldIts newIts = res where
    (_, !oldChild) :<| oldChildren = oldIts
    (!newIdx, !newChild) :<| newChildren = newIts
    !newWidget = newChild ^. L.widget
    !newWidgetId = newChild ^. L.info . L.widgetId
    !newChildKey = newChild ^. L.info . L.key
    !cwenv = updateCWenv newIdx newChild
  
    oldKeyMatch = newChildKey >>= \key -> M.lookup key oldKeys
    oldMatch = fromMaybe newNode oldKeyMatch
    isMergeKey = isJust oldKeyMatch && nodeMatches newChild oldMatch
  
    mergedOld = widgetMerge newWidget cwenv newChild oldChild
    mergedKey = widgetMerge newWidget cwenv newChild oldMatch
    initNew = widgetInit newWidget cwenv newChild
      & L.requests %~ (|> ResizeWidgets newWidgetId)
  
    (!child, !oldRest)
      | nodeMatches newChild oldChild = (mergedOld, oldChildren)
      | isMergeKey = (mergedKey, oldIts)
      | otherwise = (initNew, oldIts)
  
    (!cmerged, !cremoved)
      = mergeChildSeq updateCWenv wenv oldKeys newKeys newNode oldRest newChildren
    !merged = child <| cmerged
    !res = (merged, cremoved)
  
  mergeChildrenCheckVisible
    :: WidgetNode s e
    -> WidgetResult s e
    -> WidgetResult s e
  mergeChildrenCheckVisible oldNode result = newResult where
    newNode = result ^. L.node
    widgetId = newNode ^. L.info . L.widgetId
    resizeRequired = childrenVisibleChanged oldNode newNode
    !newResult
      | resizeRequired = result & L.requests %~ (|> ResizeWidgets widgetId)
      | otherwise = result
  
  getInstanceTreeWrapper
    :: WidgetModel a
    => Container s e a
    -> WidgetEnv s e
    -> WidgetNode s e
    -> WidgetInstanceNode
  getInstanceTreeWrapper container wenv node = instNode where
    updateCWenv = getUpdateCWenv container
    instNode = WidgetInstanceNode {
      _winInfo = node ^. L.info,
      _winState = widgetGetState (node ^. L.widget) wenv node,
      _winChildren = Seq.mapWithIndex getChildTree (node ^. L.children)
    }
    getChildTree idx child = tree where
      cwenv = updateCWenv wenv node child idx
      tree = widgetGetInstanceTree (child ^. L.widget) cwenv child
  
  -- | Dispose handler
  defaultDispose :: ContainerInitHandler s e
  defaultDispose wenv node = resultNode node
  
  disposeWrapper
    :: Container s e a
    -> WidgetEnv s e
    -> WidgetNode s e
    -> WidgetResult s e
  disposeWrapper container wenv node = result where
    updateCWenv = getUpdateCWenv container
    disposeHandler = containerDispose container
  
    WidgetResult tempNode reqs = disposeHandler wenv node
    widgetId = node ^. L.info . L.widgetId
    children = tempNode ^. L.children
  
    dispose !idx !child = widgetDispose (child ^. L.widget) cwenv child where
      cwenv = updateCWenv wenv node child idx
    results = Seq.mapWithIndex dispose children
    newReqs = foldMap _wrRequests results |> ResetWidgetPath widgetId
    !result = WidgetResult node (reqs <> newReqs)
  
  -- | Find next focusable item
  defaultFindNextFocus :: ContainerFindNextFocusHandler s e
  defaultFindNextFocus !wenv !node !direction !start = vchildren where
    vchildren = Seq.filter (^. L.info . L.visible) (node ^. L.children)
  
  findNextFocusWrapper
    :: Container s e a
    -> WidgetEnv s e
    -> WidgetNode s e
    -> FocusDirection
    -> Path
    -> Maybe WidgetNodeInfo
  findNextFocusWrapper container !wenv !node !dir !start = nextFocus where
    handler = containerFindNextFocus container
    handlerResult = handler wenv node dir start
    children
      | dir == FocusBwd = Seq.reverse handlerResult
      | otherwise = handlerResult
    !nextFocus
      | isFocusCandidate node start dir = Just (node ^. L.info)
      | otherwise = findFocusCandidate container wenv dir start node children
  
  findFocusCandidate
    :: Container s e a
    -> WidgetEnv s e
    -> FocusDirection
    -> Path
    -> WidgetNode s e
    -> Seq (WidgetNode s e)
    -> Maybe WidgetNodeInfo
  findFocusCandidate _ _ _ _ _ Empty = Nothing
  findFocusCandidate container !wenv !dir !start !node (ch :<| chs) = result where
    updateCWenv = getUpdateCWenv container
    !path = node ^. L.info . L.path
    !idx = fromMaybe 0 (Seq.lookup (length path - 1) path)
    !cwenv = updateCWenv wenv node ch idx
    !isWidgetAfterStart
      | dir == FocusBwd = isNodeBeforePath ch start
      | otherwise = isNodeParentOfPath ch start || isNodeAfterPath ch start
  
    candidate = widgetFindNextFocus (ch ^. L.widget) cwenv ch dir start
    result
      | isWidgetAfterStart && isJust candidate = candidate
      | otherwise = findFocusCandidate container wenv dir start node chs
  
  -- | Find instance matching point
  defaultFindByPoint :: ContainerFindByPointHandler s e
  defaultFindByPoint !wenv !node !start !point = result where
    children = node ^. L.children
    pointInWidget wi = wi ^. L.visible && pointInRect point (wi ^. L.viewport)
    result = Seq.findIndexL (pointInWidget . _wnInfo) children
  
  findByPointWrapper
    :: Container s e a
    -> WidgetEnv s e
    -> WidgetNode s e
    -> Path
    -> Point
    -> Maybe WidgetNodeInfo
  findByPointWrapper !container !wenv !node !start !point = result where
    offset = fromMaybe def (containerChildrenOffset container)
    updateCWenv = getUpdateCWenv container
    ignoreEmpty = containerIgnoreEmptyArea container
    handler = containerFindByPoint container
  
    isVisible = node ^. L.info . L.visible
    inVp = isPointInNodeVp node point
    cpoint = addPoint (negPoint offset) point
    path = node ^. L.info . L.path
    children = node ^. L.children
    childIdx = nextTargetStep node start <|> handler wenv node start cpoint
    validateIdx p
      | Seq.length children > p && p >= 0 = Just p
      | otherwise = Nothing
  
    win = case childIdx >>= validateIdx of
      Just idx -> childWni where
        cwenv = updateCWenv wenv node child idx
        child = Seq.index children idx
        childWidget = child ^. L.widget
        childWni = widgetFindByPoint childWidget cwenv child start cpoint
      Nothing
        | not ignoreEmpty -> Just $ node ^. L.info
        | otherwise -> Nothing
    result
      | isVisible && (inVp || fmap (^. L.path) win /= Just path) = win
      | otherwise = Nothing
  
  containerFindBranchByPath
    :: WidgetEnv s e
    -> WidgetNode s e
    -> Path
    -> Seq WidgetNodeInfo
  containerFindBranchByPath !wenv !node !path
    | info ^. L.path == path = Seq.singleton info
    | isJust nextChild = info <| nextInst (fromJust nextChild)
    | otherwise = Seq.empty
    where
      children = node ^. L.children
      info = node ^. L.info
      nextStep = nextTargetStep node path
      nextChild = nextStep >>= flip Seq.lookup children
      nextInst child = widgetFindBranchByPath (child ^. L.widget) wenv child path
  
  -- | Event Handling
  defaultFilterEvent :: ContainerFilterHandler s e
  defaultFilterEvent wenv node target evt = Just (target, evt)
  
  defaultHandleEvent :: ContainerEventHandler s e
  defaultHandleEvent wenv node target evt = Nothing
  
  handleEventWrapper
    :: WidgetModel a
    => Container s e a
    -> WidgetEnv s e
    -> WidgetNode s e
    -> Path
    -> SystemEvent
    -> Maybe (WidgetResult s e)
  handleEventWrapper container !wenv !node !baseTarget !baseEvt
    | not (node ^. L.info . L.visible) || isNothing filteredEvt = Nothing
    | targetReached || not targetValid = pResultStyled
    | otherwise = nextTargetStep pNode target >>= cResultStyled
    where
      -- Having targetValid = False means the next path step is not in
      -- _wiChildren, but may still be valid in the receiving widget
      -- For example, Composite has its own tree of child widgets with (possibly)
      -- different types for Model and Events, and is candidate for the next step
      !offset = fromMaybe def (containerChildrenOffset container)
      !style = containerGetCurrentStyle container wenv node
      !doCursor = not (containerUseCustomCursor container)
      updateCWenv = getUpdateCWenv container
      filterHandler = containerFilterEvent container
      eventHandler = containerHandleEvent container
  
      !targetReached = isTargetReached node target
      !targetValid = isTargetValid node target
      !filteredEvt = filterHandler wenv node baseTarget baseEvt
      (!target, !evt) = fromMaybe (baseTarget, baseEvt) filteredEvt
      -- Event targeted at parent
      !pResult = eventHandler wenv node target evt
      pResultStyled = handleStyleChange wenv target style doCursor node evt
        $ handleSizeReqChange container wenv node (Just evt) pResult
      pNode = maybe node (^. L.node) pResult
  
      -- Event targeted at children
      cResultStyled childIdx = result where
        --childIdx = fromJust $ nextTargetStep pNode target
        children = pNode ^. L.children
        child = Seq.index children childIdx
        childWidget = child ^. L.widget
        cevt = translateEvent (negPoint offset) evt
        cwenv = updateCWenv wenv pNode child childIdx
  
        childrenIgnored = isJust pResult && ignoreChildren (fromJust pResult)
        parentIgnored = isJust cResult && ignoreParent (fromJust cResult)
  
        cResult
          | childrenIgnored || not (child ^. L.info . L.enabled) = Nothing
          | otherwise = widgetHandleEvent childWidget cwenv child target cevt
        cResultMerged
          | parentIgnored = mergeParentChildEvts node Nothing cResult childIdx
          | otherwise = mergeParentChildEvts pNode pResult cResult childIdx
  
        cpNode
          | parentIgnored = node
          | otherwise = pNode
        !result = handleStyleChange cwenv target style doCursor cpNode cevt
          $ handleSizeReqChange container cwenv cpNode (Just cevt) cResultMerged
  
  mergeParentChildEvts
    :: WidgetNode s e
    -> Maybe (WidgetResult s e)
    -> Maybe (WidgetResult s e)
    -> Int
    -> Maybe (WidgetResult s e)
  mergeParentChildEvts _ Nothing Nothing _ = Nothing
  mergeParentChildEvts _ pResponse Nothing _ = pResponse
  mergeParentChildEvts original Nothing (Just cResponse) idx = Just $ cResponse {
      _wrNode = replaceChild original (_wrNode cResponse) idx
    }
  mergeParentChildEvts original (Just pResponse) (Just cResponse) idx
    | ignoreChildren pResponse = Just pResponse
    | ignoreParent cResponse = Just newChildResponse
    | otherwise = Just $ WidgetResult newWidget requests
    where
      pWidget = _wrNode pResponse
      cWidget = _wrNode cResponse
      requests = _wrRequests pResponse <> _wrRequests cResponse
      newWidget = replaceChild pWidget cWidget idx
      newChildResponse = cResponse {
        _wrNode = replaceChild original (_wrNode cResponse) idx
      }
  
  -- | Message Handling
  defaultHandleMessage :: ContainerMessageHandler s e
  defaultHandleMessage wenv node target message = Nothing
  
  handleMessageWrapper
    :: (WidgetModel a, Typeable i)
    => Container s e a
    -> WidgetEnv s e
    -> WidgetNode s e
    -> Path
    -> i
    -> Maybe (WidgetResult s e)
  handleMessageWrapper container !wenv !node !target arg
    | not targetReached && not targetValid = Nothing
    | otherwise = handleSizeReqChange container wenv node Nothing result
    where
      updateCWenv = getUpdateCWenv container
      handler = containerHandleMessage container
  
      targetReached = isTargetReached node target
      targetValid = isTargetValid node target
  
      messageResult childIdx = updateChild <$> message where
        children = node ^. L.children
        child = Seq.index children childIdx
        cwenv = updateCWenv wenv node child childIdx
  
        message = widgetHandleMessage (child ^. L.widget) cwenv child target arg
        updateChild !cr = cr {
          _wrNode = replaceChild node (_wrNode cr) childIdx
        }
  
      result
        | targetReached = handler wenv node target arg
        | otherwise = nextTargetStep node target >>= messageResult
  
  -- | Preferred size
  defaultGetSizeReq :: ContainerGetSizeReqHandler s e
  defaultGetSizeReq wenv node children = (newReqW, newReqH) where
    (newReqW, newReqH) = case Seq.lookup 0 children of
      Just child -> (child ^. L.info . L.sizeReqW, child ^. L.info . L.sizeReqH)
      _ -> def
  
  getSizeReqWrapper
    :: WidgetModel a
    => Container s e a
    -> WidgetEnv s e
    -> WidgetNode s e
    -> (SizeReq, SizeReq)
  getSizeReqWrapper container wenv node = (newReqW, newReqH) where
    addStyleReq = containerAddStyleReq container
    handler = containerGetSizeReq container
    style = containerGetCurrentStyle container wenv node
  
    children = node ^. L.children
    reqs = handler wenv node children
    (tmpReqW, tmpReqH)
      | addStyleReq = sizeReqAddStyle style reqs
      | otherwise = reqs
    -- User settings take precedence
    newReqW = fromMaybe tmpReqW (style ^. L.sizeReqW)
    newReqH = fromMaybe tmpReqH (style ^. L.sizeReqH)
  
  updateSizeReq
    :: WidgetEnv s e
    -> WidgetNode s e
    -> WidgetNode s e
  updateSizeReq wenv node = newNode where
    (newReqW, newReqH) = widgetGetSizeReq (node ^. L.widget) wenv node
    newNode = node
      & L.info . L.sizeReqW .~ newReqW
      & L.info . L.sizeReqH .~ newReqH
  
  handleSizeReqChange
    :: WidgetModel a
    => Container s e a
    -> WidgetEnv s e
    -> WidgetNode s e
    -> Maybe SystemEvent
    -> Maybe (WidgetResult s e)
    -> Maybe (WidgetResult s e)
  handleSizeReqChange container wenv node evt mResult = result where
    baseResult = fromMaybe (resultNode node) mResult
    baseNode = baseResult ^. L.node
    resizeReq = isResizeAnyResult mResult
    styleChanged = isJust evt && styleStateChanged wenv baseNode (fromJust evt)
    result
      | styleChanged || resizeReq = Just $ baseResult
        & L.node .~ updateSizeReq wenv baseNode
      | otherwise = mResult
  
  -- | Resize
  defaultResize :: ContainerResizeHandler s e
  defaultResize wenv node viewport children = resized where
    style = currentStyle wenv node
    contentArea = fromMaybe def (removeOuterBounds style viewport)
    childrenSizes = Seq.replicate (Seq.length children) contentArea
    resized = (resultNode node, childrenSizes)
  
  resizeWrapper
    :: WidgetModel a
    => Container s e a
    -> WidgetEnv s e
    -> WidgetNode s e
    -> Rect
    -> (Path -> Bool)
    -> WidgetResult s e
  resizeWrapper container wenv node viewport resizeReq = result where
    updateCWenv = getUpdateCWenv container
    useCustomSize = containerUseCustomSize container
    useChildSize = containerUseChildrenSizes container
    handler = containerResize container
  
    lensVp = L.info . L.viewport
    vpChanged = viewport /= node ^. lensVp
    path = node ^. L.info . L.path
    children = node ^. L.children
  
    (tempRes, assigned) = handler wenv node viewport children
    resize idx (!child, !vp) = newChildRes where
      !cwenv = updateCWenv wenv node child idx
      tempChildRes = widgetResize (child ^. L.widget) cwenv child vp resizeReq
      cvp = tempChildRes ^. L.node . L.info . L.viewport
      icvp = fromMaybe vp (intersectRects vp cvp)
      !newChildRes = tempChildRes
        & L.node . L.info . L.viewport .~ (if useChildSize then icvp else vp)
  
    newChildrenRes = Seq.mapWithIndex resize (Seq.zip children assigned)
    newChildren = fmap _wrNode newChildrenRes
    newChildrenReqs = foldMap _wrRequests newChildrenRes
    newVp
      | useCustomSize = tempRes ^. L.node . lensVp
      | otherwise = viewport
    tmpResult
      | vpChanged || resizeReq path = Just $ tempRes
        & L.node . L.info . L.viewport .~ newVp
        & L.node . L.children .~ newChildren
        & L.requests <>~ newChildrenReqs
      | otherwise = Just $ resultNode node
    result = fromJust $
      handleSizeReqChange container wenv (tempRes ^. L.node) Nothing tmpResult
  
  -- | Rendering
  defaultRender :: ContainerRenderHandler s e
  defaultRender renderer wenv node = return ()
  
  renderWrapper
    :: Container s e a
    -> WidgetEnv s e
    -> WidgetNode s e
    -> Renderer
    -> IO ()
  renderWrapper container wenv !node !renderer =
    when (isWidgetVisible wenv node) $
      drawInScissor renderer useScissor viewport $
        drawStyledAction_ renderer drawDecorations viewport style $ \_ -> do
          renderBefore wenv node renderer
  
          drawInScissor renderer useChildrenScissor childrenScissorRect $ do
            when (isJust offset) $ do
              saveContext renderer
              setTranslation renderer (fromJust offset)
  
            forM_ pairs $ \(idx, child) ->
              widgetRender (child ^. L.widget) (cwenv child idx) child renderer
  
            when (isJust offset) $
              restoreContext renderer
  
          -- Outside children scissor
          renderAfter wenv node renderer
    where
      style = containerGetCurrentStyle container wenv node
      updateCWenv = getUpdateCWenv container
      drawDecorations = containerDrawDecorations container
      useScissor = containerUseScissor container
      childrenScissor = containerChildrenScissor container
      offset = containerChildrenOffset container
      renderBefore = containerRender container
      renderAfter = containerRenderAfter container
  
      children = node ^. L.children
      viewport = node ^. L.info . L.viewport
      useChildrenScissor = isJust childrenScissor
      childrenScissorRect = fromMaybe def childrenScissor
      pairs = Seq.mapWithIndex (,) children
      cwenv !child !idx = updateCWenv wenv node child idx
  
  -- | Event Handling Helpers
  ignoreChildren :: WidgetResult s e -> Bool
  ignoreChildren result = not (Seq.null ignoreReqs) where
    ignoreReqs = Seq.filter isIgnoreChildrenEvents (_wrRequests result)
  
  ignoreParent :: WidgetResult s e -> Bool
  ignoreParent result = not (Seq.null ignoreReqs) where
    ignoreReqs = Seq.filter isIgnoreParentEvents (_wrRequests result)
  
  replaceChild
    :: WidgetNode s e -> WidgetNode s e -> Int -> WidgetNode s e
  replaceChild !parent !child !idx = parent & L.children .~ newChildren where
    newChildren = Seq.update idx child (parent ^. L.children)
  
  cascadeCtx
    :: WidgetEnv s e -> WidgetNode s e -> WidgetNode s e -> Int -> WidgetNode s e
  cascadeCtx !wenv !parent !child !idx = newChild where
    pInfo = parent ^. L.info
    cInfo = child ^. L.info
    parentPath = pInfo ^. L.path
    parentVisible = pInfo ^. L.visible
    parentEnabled = pInfo ^. L.enabled
    !newPath = parentPath |> idx
    !newChild = child
      & L.info . L.widgetId .~ WidgetId (wenv ^. L.timestamp) newPath
      & L.info . L.path .~ newPath
      & L.info . L.visible .~ (cInfo ^. L.visible && parentVisible)
      & L.info . L.enabled .~ (cInfo ^. L.enabled && parentEnabled)
  
  buildLocalMap :: Seq (WidgetNode s e) -> Map WidgetKey (WidgetNode s e)
  buildLocalMap widgets = newMap where
    addWidget map widget
      | isJust key = M.insert (fromJust key) widget map
      | otherwise = map
      where
        key = widget ^. L.info . L.key
    newMap = foldl' addWidget M.empty widgets
  