monomer-1.4.0.0: src/Monomer/Widgets/Composite.hs
{-|
Module : Monomer.Widgets.Composite
Copyright : (c) 2018 Francisco Vallarino
License : BSD-3-Clause (see the LICENSE file)
Maintainer : fjvallarino@gmail.com
Stability : experimental
Portability : non-portable
Composite widget. Main glue between all the other widgets, also acts as the main
app widget. Composite allows to split an application into reusable parts without
the need to implement a lower level widget. It can comunicate with its parent
component by reporting events.
Requires two functions:
- UI Builder: creates the widget tree based on the provided Widget Environment
and model. This widget tree is made of other widgets, in general combinations of
containers and singles.
- Event Handler: processes user defined events which are raised by the widgets
created when building the UI.
Composite is discussed in detail in the tutorials.
-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Monomer.Widgets.Composite (
-- * Re-exported modules
module Monomer.Core,
module Monomer.Event,
module Monomer.Widgets.Util,
-- * Configuration
CompositeCfg,
EventResponse(..),
CompParentModel,
CompositeModel,
CompositeEvent,
MergeRequired,
MergeReqsHandler,
MergeEventsHandler,
MergeModelHandler,
EventHandler,
UIBuilder,
TaskHandler,
ProducerHandler,
CompMsgUpdate,
compositeMergeReqs,
compositeMergeEvents,
compositeMergeModel,
-- * Constructors
composite,
composite_,
compositeV,
compositeV_,
compositeD_
) where
import Debug.Trace
import Control.Applicative ((<|>))
import Control.Exception (AssertionFailed(..), throw)
import Control.Lens (ALens', (&), (^.), (^?), (.~), (%~), (<>~), at, ix, non)
import Data.Default
import Data.Either
import Data.List (foldl')
import Data.Map.Strict (Map)
import Data.Maybe
import Data.Sequence (Seq(..), (|>), (<|), fromList)
import Data.Typeable (Typeable, cast, typeOf)
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.Types
import Monomer.Helper
import Monomer.Widgets.Singles.Spacer
import Monomer.Widgets.Util
import qualified Monomer.Core.Lens as L
-- | Type of the parent's model
type CompParentModel sp = Typeable sp
-- | Type of the composite's model
type CompositeModel s = (Eq s, WidgetModel s)
-- | Type of the composite's event
type CompositeEvent e = WidgetEvent e
-- | Checks if merging the composite is required.
type MergeRequired s e
= WidgetEnv s e -- ^ Widget environment.
-> s -- ^ Old composite model.
-> s -- ^ New composite model
-> Bool -- ^ True if merge is required.
-- | Generates requests during the merge process.
type MergeReqsHandler s e sp
= WidgetEnv s e -- ^ Widget environment.
-> WidgetNode s e -- ^ New widget node.
-> WidgetNode s e -- ^ Old widget node.
-> sp -- ^ Parent model.
-> s -- ^ Old composite model.
-> s -- ^ New composite model.
-> [WidgetRequest s e] -- ^ The list of requests.
-- | Generates events during the merge process.
type MergeEventsHandler s e sp
= WidgetEnv s e -- ^ Widget environment.
-> WidgetNode s e -- ^ New widget node.
-> WidgetNode s e -- ^ Old widget node.
-> sp -- ^ Parent model.
-> s -- ^ Old composite model.
-> s -- ^ New composite model.
-> [e] -- ^ The list of events.
-- | Allows updating the composite model with information from the parent model.
type MergeModelHandler s e sp
= WidgetEnv s e -- ^ Widget environment.
-> sp -- ^ Parent model.
-> s -- ^ Old composite model.
-> s -- ^ New composite model.
-> s -- ^ Updated composite model.
-- | Handles a composite event and returns a set of responses.
type EventHandler s e sp ep
= WidgetEnv s e
-> WidgetNode s e
-> s
-> e
-> [EventResponse s e sp ep]
-- | Creates the widget tree based on the given model.
type UIBuilder s e = WidgetEnv s e -> s -> WidgetNode s e
-- | Asynchronous task generating a single event.
type TaskHandler e = IO e
-- | Asynchronous task generating multiple events.
type ProducerHandler e = (e -> IO ()) -> IO ()
-- | Model update function wrapped as a message.
data CompMsgUpdate
= forall s . CompositeModel s => CompMsgUpdate (s -> s)
{-|
Delayed request. Used to account for widget tree changes in previous steps. When
processing EventResponses that depend on WidgetKeys, resolving the key at the
time the response is created may result in missing/no longer valid keys. The
delayed message allows resolving the key right before the WidgetRequest is
processed.
-}
data CompMsgDelayedRequest
= CompMsgSetFocus WidgetKey
| CompMsgMoveFocus (Maybe WidgetKey) FocusDirection
| forall i . Typeable i => CompMsgMessage WidgetKey i
-- | Response options for an event handler.
data EventResponse s e sp ep
-- | Modifies the current model, prompting a merge.
= Model s
-- | Raises a new event, which will be handled in the same cycle.
| Event e
-- | Raises an event that will be handled by the parent.
| Report ep
-- | Generates a 'WidgetRequest'.
| Request (WidgetRequest s e)
{-|
Generates a 'WidgetRequest' matching the parent's types. Useful when receiving
requests as configuration from the parent, since the types will not match
otherwise.
-}
| RequestParent (WidgetRequest sp ep)
{-|
Generates a request to set focus on the widget with the matching key. If the
key does not exist, focus remains on the currently focused widget.
-}
| SetFocusOnKey WidgetKey
{-|
Generates a request to move focus forward/backward, optionally indicating the
key of the starting widget.
-}
| MoveFocusFromKey (Maybe WidgetKey) FocusDirection
{-|
Sends a message to the given key. If the key does not exist, the message will
not be delivered.
-}
| forall i . Typeable i => Message WidgetKey i
{-|
Runs an asynchronous task that will return a single result. The task is
responsible for reporting errors using the expected event type. If the task
crashes without returning a value, the composite will not know about it.
-}
| Task (TaskHandler e)
{-|
Runs an asynchronous task that will produce unlimited result. The producer is
responsible for reporting errors using the expected event type. If the
producer crashes without sending a value, composite will not know about it.
-}
| Producer (ProducerHandler e)
{-|
Configuration options for composite:
- 'onInit': event to raise when the widget is created. Useful for initializing
required resources.
- 'onDispose': event to raise when the widget is disposed. Useful for freeing
acquired resources.
- 'onResize': event to raise when the size of the widget changes.
- 'onChange': event to raise when the model changes. The value passed to the
provided event is the previous version of the model. The current version of
the model is always available as a parameter in the _handleEvent_ function.
- 'onChangeReq': 'WidgetRequest' to generate when the model changes.
- 'onEnabledChange': event to raise when the enabled status changes.
- 'onVisibleChange': event to raise when the visibility changes.
- 'mergeRequired': indicates if merging is necessary for this widget. In case
the UI build process references information outside the model, it can be used
to signal that merging is required even if the model has not changed. It can
also be used as a performance tweak if the changes do not require rebuilding
the UI.
- 'compositeMergeReqs': functions to generate WidgetRequests during the merge
process. Since merge is already handled by Composite (by merging its tree),
this is complementary for the cases when more control, and the previous
version of the widget tree, is required. For example, it is used in
'Monomer.Widgets.Containers.Confirm' to set the focus on its Accept button
when visibility is restored (this usually means it was brought to the front in
a zstack, and the visibility flag of the previous version needs to be
checked).
- 'compositeMergeModel': Allows updating the composite model with information
from the parent model. Useful when the composite needs a more complex model
than what the user is binding.
-}
data CompositeCfg s e sp ep = CompositeCfg {
_cmcMergeRequired :: Maybe (MergeRequired s e),
_cmcMergeReqs :: [MergeReqsHandler s e sp],
_cmcMergeModel :: Maybe (MergeModelHandler s e sp),
_cmcOnInitReq :: [WidgetRequest s e],
_cmcOnDisposeReq :: [WidgetRequest s e],
_cmcOnResize :: [Rect -> e],
_cmcOnChangeReq :: [s -> WidgetRequest s e],
_cmcOnEnabledChange :: [e],
_cmcOnVisibleChange :: [e]
}
instance Default (CompositeCfg s e sp ep) where
def = CompositeCfg {
_cmcMergeModel = Nothing,
_cmcMergeRequired = Nothing,
_cmcMergeReqs = [],
_cmcOnInitReq = [],
_cmcOnDisposeReq = [],
_cmcOnResize = [],
_cmcOnChangeReq = [],
_cmcOnEnabledChange = [],
_cmcOnVisibleChange = []
}
instance Semigroup (CompositeCfg s e sp ep) where
(<>) c1 c2 = CompositeCfg {
_cmcMergeModel = _cmcMergeModel c2 <|> _cmcMergeModel c1,
_cmcMergeRequired = _cmcMergeRequired c2 <|> _cmcMergeRequired c1,
_cmcMergeReqs = _cmcMergeReqs c1 <> _cmcMergeReqs c2,
_cmcOnInitReq = _cmcOnInitReq c1 <> _cmcOnInitReq c2,
_cmcOnDisposeReq = _cmcOnDisposeReq c1 <> _cmcOnDisposeReq c2,
_cmcOnResize = _cmcOnResize c1 <> _cmcOnResize c2,
_cmcOnChangeReq = _cmcOnChangeReq c1 <> _cmcOnChangeReq c2,
_cmcOnEnabledChange = _cmcOnEnabledChange c1 <> _cmcOnEnabledChange c2,
_cmcOnVisibleChange = _cmcOnVisibleChange c1 <> _cmcOnVisibleChange c2
}
instance Monoid (CompositeCfg s e sp ep) where
mempty = def
instance CmbMergeRequired (CompositeCfg s e sp ep) (WidgetEnv s e) s where
mergeRequired fn = def {
_cmcMergeRequired = Just fn
}
instance WidgetEvent e => CmbOnInit (CompositeCfg s e sp ep) e where
onInit fn = def {
_cmcOnInitReq = [RaiseEvent fn]
}
instance CmbOnInitReq (CompositeCfg s e sp ep) s e where
onInitReq req = def {
_cmcOnInitReq = [req]
}
instance WidgetEvent e => CmbOnDispose (CompositeCfg s e sp ep) e where
onDispose fn = def {
_cmcOnDisposeReq = [RaiseEvent fn]
}
instance CmbOnDisposeReq (CompositeCfg s e sp ep) s e where
onDisposeReq req = def {
_cmcOnDisposeReq = [req]
}
instance CmbOnResize (CompositeCfg s e sp ep) e Rect where
onResize fn = def {
_cmcOnResize = [fn]
}
instance WidgetEvent e => CmbOnChange (CompositeCfg s e sp ep) s e where
onChange fn = def {
_cmcOnChangeReq = [RaiseEvent . fn]
}
instance CmbOnChangeReq (CompositeCfg s e sp ep) s e s where
onChangeReq req = def {
_cmcOnChangeReq = [req]
}
instance CmbOnEnabledChange (CompositeCfg s e sp ep) e where
onEnabledChange fn = def {
_cmcOnEnabledChange = [fn]
}
instance CmbOnVisibleChange (CompositeCfg s e sp ep) e where
onVisibleChange fn = def {
_cmcOnVisibleChange = [fn]
}
{-|
Generate WidgetRequests during the merge process.
This function is not called during initialization; 'onInitReq' can be used.
-}
compositeMergeReqs :: MergeReqsHandler s e sp -> CompositeCfg s e sp ep
compositeMergeReqs fn = def {
_cmcMergeReqs = [fn]
}
{-|
Generate events during the merge process.
This function is not called during initialization; 'onInit' can be used.
-}
compositeMergeEvents
:: WidgetEvent e => MergeEventsHandler s e sp -> CompositeCfg s e sp ep
compositeMergeEvents fn = cfg where
cfg = def {
_cmcMergeReqs = [wrapper]
}
wrapper wenv node oldNode parentModel oldModel newModel
= RaiseEvent <$> fn wenv node oldNode parentModel oldModel newModel
{-|
Allows updating the composite model with information from the parent model.
Useful when the composite needs a more complex model than what the user is
binding.
For example, a database record may be binded as the model from the parent, but
the composite needs its own boolean flags to toggle visibility on different
sections.
This function is called during both merge and init. On init, the oldModel will
be equal to the current model.
-}
compositeMergeModel :: MergeModelHandler s e sp -> CompositeCfg s e sp ep
compositeMergeModel fn = def {
_cmcMergeModel = Just fn
}
data Composite s e sp ep = Composite {
_cmpWidgetData :: !(WidgetData sp s),
_cmpEventHandler :: !(EventHandler s e sp ep),
_cmpUiBuilder :: !(UIBuilder s e),
_cmpMergeRequired :: MergeRequired s e,
_cmpMergeReqs :: [MergeReqsHandler s e sp],
_cmpMergeModel :: Maybe (MergeModelHandler s e sp),
_cmpOnInitReq :: [WidgetRequest s e],
_cmpOnDisposeReq :: [WidgetRequest s e],
_cmpOnResize :: [Rect -> e],
_cmpOnChangeReq :: [s -> WidgetRequest s e],
_cmpOnEnabledChange :: [e],
_cmpOnVisibleChange :: [e]
}
data CompositeState s e = CompositeState {
_cpsModel :: !(Maybe s),
_cpsRoot :: !(WidgetNode s e),
_cpsWidgetKeyMap :: WidgetKeyMap s e
}
data ReducedEvents s e sp ep = ReducedEvents {
_reModel :: s,
_reEvents :: Seq e,
_reReports :: Seq ep,
_reRequests :: Seq (WidgetRequest s e),
_reMessages :: Seq (WidgetRequest sp ep),
_reTasks :: Seq (TaskHandler e),
_reProducers :: Seq (ProducerHandler e)
}
{-|
Creates a composite taking its model from a lens into the parent model.
-}
composite
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> WidgetType -- ^ The name of the composite.
-> ALens' sp s -- ^ The lens into the parent's model.
-> UIBuilder s e -- ^ The UI builder function.
-> EventHandler s e sp ep -- ^ The event handler.
-> WidgetNode sp ep -- ^ The resulting widget.
composite widgetType field uiBuilder evtHandler = newNode where
newNode = composite_ widgetType field uiBuilder evtHandler def
{-|
Creates a composite taking its model from a lens into the parent model. Accepts
config.
-}
composite_
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> WidgetType -- ^ The name of the composite.
-> ALens' sp s -- ^ The lens into the parent's model.
-> UIBuilder s e -- ^ The UI builder function.
-> EventHandler s e sp ep -- ^ The event handler.
-> [CompositeCfg s e sp ep] -- ^ The config options.
-> WidgetNode sp ep -- ^ The resulting widget.
composite_ widgetType field uiBuilder evtHandler cfgs = newNode where
widgetData = WidgetLens field
newNode = compositeD_ widgetType widgetData uiBuilder evtHandler cfgs
-- | Creates a composite using the given model and onChange event handler.
compositeV
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> WidgetType -- ^ The name of the composite.
-> s -- ^ The model.
-> (s -> e) -- ^ The event to report when model changes.
-> UIBuilder s e -- ^ The UI builder function.
-> EventHandler s e sp ep -- ^ The event handler.
-> WidgetNode sp ep -- ^ The resulting widget.
compositeV wType val handler uiBuilder evtHandler = newNode where
newNode = compositeV_ wType val handler uiBuilder evtHandler def
{-|
Creates a composite using the given model and onChange event handler. Accepts
config.
-}
compositeV_
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> WidgetType -- ^ The name of the composite.
-> s -- ^ The model.
-> (s -> e) -- ^ The event to report when model changes.
-> UIBuilder s e -- ^ The UI builder function.
-> EventHandler s e sp ep -- ^ The event handler.
-> [CompositeCfg s e sp ep] -- ^ The config options.
-> WidgetNode sp ep -- ^ The resulting widget.
compositeV_ wType val handler uiBuilder evtHandler cfgs = newNode where
widgetData = WidgetValue val
newCfgs = onChange handler : cfgs
newNode = compositeD_ wType widgetData uiBuilder evtHandler newCfgs
-- | Creates a composite providing a WidgetData instance and config.
compositeD_
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> WidgetType -- ^ The name of the composite.
-> WidgetData sp s -- ^ The model.
-> UIBuilder s e -- ^ The UI builder function.
-> EventHandler s e sp ep -- ^ The event handler.
-> [CompositeCfg s e sp ep] -- ^ The config options.
-> WidgetNode sp ep -- ^ The resulting widget.
compositeD_ wType wData uiBuilder evtHandler configs = newNode where
config = mconcat configs
mergeReq = fromMaybe (const (/=)) (_cmcMergeRequired config)
!widgetRoot = spacer
composite = Composite {
_cmpWidgetData = wData,
_cmpEventHandler = evtHandler,
_cmpUiBuilder = uiBuilder,
_cmpMergeRequired = mergeReq,
_cmpMergeReqs = _cmcMergeReqs config,
_cmpMergeModel = _cmcMergeModel config,
_cmpOnInitReq = _cmcOnInitReq config,
_cmpOnDisposeReq = _cmcOnDisposeReq config,
_cmpOnResize = _cmcOnResize config,
_cmpOnChangeReq = _cmcOnChangeReq config,
_cmpOnEnabledChange = _cmcOnEnabledChange config,
_cmpOnVisibleChange = _cmcOnVisibleChange config
}
state = CompositeState Nothing widgetRoot M.empty
widget = createComposite composite state
!newNode = defaultWidgetNode wType widget
createComposite
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> Widget sp ep
createComposite !comp !state = widget where
widget = Widget {
widgetInit = compositeInit comp state,
widgetMerge = compositeMerge comp state,
widgetDispose = compositeDispose comp state,
widgetGetState = makeState state,
widgetGetInstanceTree = compositeGetInstanceTree comp state,
widgetFindNextFocus = compositeFindNextFocus comp state,
widgetFindByPoint = compositeFindByPoint comp state,
widgetFindBranchByPath = compositeFindBranchByPath comp state,
widgetHandleEvent = compositeHandleEvent comp state,
widgetHandleMessage = compositeHandleMessage comp state,
widgetGetSizeReq = compositeGetSizeReq comp state,
widgetResize = compositeResize comp state,
widgetRender = compositeRender comp state
}
-- | Init
compositeInit
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> WidgetResult sp ep
compositeInit comp state wenv widgetComp = newResult where
CompositeState{..} = state
!mergeModel = _cmpMergeModel comp
!parentModel = wenv ^. L.model
!userModel = getUserModel comp wenv
!model = case mergeModel of
Just merge -> merge cwenv parentModel userModel userModel where
!cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap userModel
_ -> userModel
-- Creates UI using provided function
!cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model
!builtRoot = _cmpUiBuilder comp cwenv model
!tempRoot = cascadeCtx wenv widgetComp builtRoot
WidgetResult root reqs = widgetInit (tempRoot ^. L.widget) cwenv tempRoot
!newState = state {
_cpsModel = Just model,
_cpsRoot = root,
_cpsWidgetKeyMap = collectWidgetKeys M.empty root
}
getBaseStyle wenv node = Nothing
styledComp = initNodeStyle getBaseStyle wenv widgetComp
initReqs = Seq.fromList (_cmpOnInitReq comp)
tempResult = WidgetResult root (RenderOnce <| reqs <> initReqs)
!newResult = toParentResult comp newState wenv styledComp tempResult
-- | Merge
compositeMerge
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> WidgetNode sp ep
-> WidgetResult sp ep
compositeMerge comp state wenv newComp oldComp = newResult where
widgetId = oldComp ^. L.info . L.widgetId
oldState = widgetGetState (oldComp ^. L.widget) wenv oldComp
validState = fromMaybe state (useState oldState)
CompositeState oldModel oldRoot oldWidgetKeys = validState
!mergeModel = _cmpMergeModel comp
!parentModel = wenv ^. L.model
!userModel = getUserModel comp wenv
!model = case mergeModel of
Just merge -> merge cwenv parentModel (fromJust oldModel) userModel where
cwenv = convertWidgetEnv wenv oldWidgetKeys userModel
_ -> userModel
-- Creates new UI using provided function
cwenv = convertWidgetEnv wenv oldWidgetKeys model
tempRoot = cascadeCtx wenv newComp (_cmpUiBuilder comp cwenv model)
tempWidget = tempRoot ^. L.widget
-- Needed in case the user references something outside model when building UI
-- The same model is provided as old since nothing else is available, but
-- mergeRequired may be using data from a closure
modelChanged = _cmpMergeRequired comp cwenv (fromJust oldModel) model
visibleChg = nodeVisibleChanged oldComp newComp
enabledChg = nodeEnabledChanged oldComp newComp
flagsChanged = visibleChg || enabledChg
themeChanged = wenv ^. L.themeChanged
mergeRequired
| isJust oldModel = modelChanged || flagsChanged || themeChanged
| otherwise = True
initRequired = not (nodeMatches tempRoot oldRoot)
useNewRoot = initRequired || mergeRequired
WidgetResult !newRoot !tmpReqs
| initRequired = widgetInit tempWidget cwenv tempRoot
| mergeRequired = widgetMerge tempWidget cwenv tempRoot oldRoot
| otherwise = resultNode oldRoot
!newState = validState {
_cpsModel = Just model,
_cpsRoot = newRoot,
_cpsWidgetKeyMap = collectWidgetKeys M.empty newRoot
}
getBaseStyle wenv node = Nothing
styledComp = initNodeStyle getBaseStyle wenv newComp
& L.info . L.widgetId .~ oldComp ^. L.info . L.widgetId
& L.info . L.viewport .~ oldComp ^. L.info . L.viewport
& L.info . L.sizeReqW .~ oldComp ^. L.info . L.sizeReqW
& L.info . L.sizeReqH .~ oldComp ^. L.info . L.sizeReqH
visibleEvts
| useNewRoot && visibleChg = _cmpOnVisibleChange comp
| otherwise = []
enabledEvts
| useNewRoot && enabledChg = _cmpOnEnabledChange comp
| otherwise = []
evts = RaiseEvent <$> Seq.fromList (visibleEvts ++ enabledEvts)
mergeReqsFns = _cmpMergeReqs comp
mergeHelper f = f cwenv newRoot oldRoot parentModel (fromJust oldModel) model
mergeReqs = concatMap mergeHelper mergeReqsFns
extraReqs = seqCatMaybes (toParentReq widgetId <$> Seq.fromList mergeReqs)
tmpResult = WidgetResult newRoot (RenderOnce <| tmpReqs <> extraReqs <> evts)
reducedResult
| useNewRoot = toParentResult comp newState wenv styledComp tmpResult
| otherwise = resultNode oldComp
!newResult = handleWidgetIdChange oldComp reducedResult
-- | Dispose
compositeDispose
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> WidgetResult sp ep
compositeDispose comp state wenv widgetComp = result where
CompositeState{..} = state
model = getCompositeModel state
cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model
widget = _cpsRoot ^. L.widget
WidgetResult _ reqs = widgetDispose widget cwenv _cpsRoot
disposeReqs = Seq.fromList (_cmpOnDisposeReq comp)
tempResult = WidgetResult _cpsRoot (reqs <> disposeReqs)
result = toParentResult comp state wenv widgetComp tempResult
compositeGetInstanceTree
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> WidgetInstanceNode
compositeGetInstanceTree comp state wenv node = instTree where
CompositeState{..} = state
widget = _cpsRoot ^. L.widget
model = getCompositeModel state
cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model
cInstTree = widgetGetInstanceTree widget cwenv _cpsRoot
instTree = WidgetInstanceNode {
_winInfo = node ^. L.info,
_winState = Just (WidgetState state),
_winChildren = Seq.singleton cInstTree
}
-- | Next focusable
compositeFindNextFocus
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> FocusDirection
-> Path
-> Maybe WidgetNodeInfo
compositeFindNextFocus comp state wenv widgetComp dir start = nextFocus where
CompositeState{..} = state
widget = _cpsRoot ^. L.widget
model = getCompositeModel state
cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model
nextFocus = widgetFindNextFocus widget cwenv _cpsRoot dir start
-- | Find
compositeFindByPoint
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> Path
-> Point
-> Maybe WidgetNodeInfo
compositeFindByPoint comp state wenv widgetComp start point
| widgetComp ^. L.info . L.visible && validStep = resultInfo
| otherwise = Nothing
where
CompositeState{..} = state
widget = _cpsRoot ^. L.widget
model = getCompositeModel state
cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model
next = nextTargetStep widgetComp start
validStep = isNothing next || next == Just 0
resultInfo = widgetFindByPoint widget cwenv _cpsRoot start point
compositeFindBranchByPath
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> Path
-> Seq WidgetNodeInfo
compositeFindBranchByPath comp state wenv widgetComp path
| info ^. L.path == path = Seq.singleton info
| nextStep == Just 0 = info <| childrenInst
| otherwise = Seq.empty
where
CompositeState{..} = state
model = getCompositeModel state
cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model
info = widgetComp ^. L.info
nextStep = nextTargetStep widgetComp path
child = _cpsRoot
childrenInst = widgetFindBranchByPath (child ^. L.widget) cwenv child path
-- | Event handling
compositeHandleEvent
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> Path
-> SystemEvent
-> Maybe (WidgetResult sp ep)
compositeHandleEvent comp state wenv widgetComp target evt = result where
CompositeState{..} = state
widget = _cpsRoot ^. L.widget
!model = getCompositeModel state
!cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model
rootEnabled = _cpsRoot ^. L.info . L.enabled
compVisible = widgetComp ^. L.info . L.visible
compEnabled = widgetComp ^. L.info . L.enabled
processEvent = toParentResult comp state wenv widgetComp
!evtResult
| not (compVisible && compEnabled) = Nothing
| rootEnabled = widgetHandleEvent widget cwenv _cpsRoot target evt
| otherwise = Nothing
!result = fmap processEvent evtResult
-- | Message handling
compositeHandleMessage
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp, Typeable i)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> Path
-> i
-> Maybe (WidgetResult sp ep)
compositeHandleMessage comp state@CompositeState{..} !wenv !widgetComp !target arg
| isTargetReached widgetComp target = case cast arg of
Just evt -> Just $ handleMsgEvent comp state wenv widgetComp evt
Nothing -> case cast arg of
Just (CompMsgUpdate msg) -> handleMsgUpdate comp state wenv widgetComp <$> cast msg
Nothing -> case cast arg of
Just req -> handleDelayedRequest comp state wenv widgetComp req
_ -> traceShow ("Failed match on Composite handleMessage", typeOf arg) Nothing
| otherwise = fmap processEvent result where
processEvent = toParentResult comp state wenv widgetComp
cmpWidget = _cpsRoot ^. L.widget
!model = getCompositeModel state
!cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model
result = widgetHandleMessage cmpWidget cwenv _cpsRoot target arg
-- Preferred size
compositeGetSizeReq
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> (SizeReq, SizeReq)
compositeGetSizeReq comp state wenv widgetComp = (newReqW, newReqH) where
CompositeState{..} = state
style = currentStyle wenv widgetComp
widget = _cpsRoot ^. L.widget
currReqW = _cpsRoot ^. L.info . L.sizeReqW
currReqH = _cpsRoot ^. L.info . L.sizeReqH
(tmpReqW, tmpReqH) = sizeReqAddStyle style (currReqW, currReqH)
-- User settings take precedence
newReqW = fromMaybe tmpReqW (style ^. L.sizeReqW)
newReqH = fromMaybe tmpReqH (style ^. L.sizeReqH)
-- Preferred size
updateSizeReq
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> WidgetNode sp ep
updateSizeReq comp state wenv widgetComp = newComp where
(newReqW, newReqH) = compositeGetSizeReq comp state wenv widgetComp
newComp = widgetComp
& L.info . L.sizeReqW .~ newReqW
& L.info . L.sizeReqH .~ newReqH
-- Resize
compositeResize
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> Rect
-> (Path -> Bool)
-> WidgetResult sp ep
compositeResize comp state wenv widgetComp viewport rszReq = resizedRes where
CompositeState{..} = state
style = currentStyle wenv widgetComp
carea = fromMaybe def (removeOuterBounds style viewport)
widget = _cpsRoot ^. L.widget
model = getCompositeModel state
cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model
WidgetResult newRoot newReqs = widgetResize widget cwenv _cpsRoot carea rszReq
oldVp = widgetComp ^. L.info . L.viewport
sizeChanged = viewport /= oldVp
resizeEvts = fmap ($ viewport) (_cmpOnResize comp)
resizeReqs
| sizeChanged = RaiseEvent <$> Seq.fromList resizeEvts
| otherwise = Empty
childRes = WidgetResult newRoot (newReqs <> resizeReqs)
& L.node . L.info . L.viewport .~ carea
resizedRes = toParentResult comp state wenv widgetComp childRes
& L.node . L.info . L.viewport .~ viewport
-- Render
compositeRender
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> Renderer
-> IO ()
compositeRender comp state wenv widgetComp renderer =
drawStyledAction renderer viewport style $ \_ ->
widgetRender widget cwenv _cpsRoot renderer
where
CompositeState{..} = state
widget = _cpsRoot ^. L.widget
viewport = widgetComp ^. L.info . L.viewport
style = currentStyle wenv widgetComp
!model = getCompositeModel state
!cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model
handleMsgEvent
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> e
-> WidgetResult sp ep
handleMsgEvent comp state wenv widgetComp event = newResult where
CompositeState{..} = state
evtHandler = _cmpEventHandler comp
!model = getCompositeModel state
!cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model
!response = evtHandler cwenv _cpsRoot model event
!newReqs = evtResponseToRequest widgetComp _cpsWidgetKeyMap <$> response
!newResult = WidgetResult widgetComp (Seq.fromList (catMaybes newReqs))
handleMsgUpdate
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> (s -> s)
-> WidgetResult sp ep
handleMsgUpdate comp state wenv widgetComp fnUpdate = result where
CompositeState{..} = state
!model = getCompositeModel state
!newModel = fnUpdate model
!result
| model == newModel = resultNode widgetComp
| otherwise = mergeChild comp state wenv newModel _cpsRoot widgetComp
toParentResult
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> WidgetResult s e
-> WidgetResult sp ep
toParentResult comp state !wenv !widgetComp !result = newResult where
WidgetResult newRoot reqs = result
widgetId = widgetComp ^. L.info . L.widgetId
newState = state {
_cpsRoot = newRoot
}
newComp = widgetComp
& L.widget .~ createComposite comp newState
newNode = updateSizeReq comp newState wenv newComp
newReqs = seqCatMaybes (toParentReq widgetId <$> reqs)
!newResult = WidgetResult newNode newReqs
evtResponseToRequest
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> WidgetNode sp ep
-> WidgetKeyMap s e
-> EventResponse s e sp ep
-> Maybe (WidgetRequest sp ep)
evtResponseToRequest widgetComp widgetKeys response = case response of
Model newModel -> Just $ sendMsgTo widgetComp (CompMsgUpdate $ const newModel)
Event event -> Just $ sendMsgTo widgetComp event
Report report -> Just (RaiseEvent report)
Request req -> toParentReq widgetId req
RequestParent req -> Just req
SetFocusOnKey key -> Just $ sendMsgTo widgetComp (CompMsgSetFocus key)
MoveFocusFromKey key dir -> Just $ sendMsgTo widgetComp (CompMsgMoveFocus key dir)
Message key msg -> Just $ sendMsgTo widgetComp (CompMsgMessage key msg)
Task task -> Just $ RunTask widgetId path task
Producer producer -> Just $ RunProducer widgetId path producer
where
widgetId = widgetComp ^. L.info . L.widgetId
path = widgetComp ^. L.info . L.path
handleDelayedRequest
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> WidgetNode sp ep
-> CompMsgDelayedRequest
-> Maybe (WidgetResult sp ep)
handleDelayedRequest comp state wenv node req = result where
widgetKeys = _cpsWidgetKeyMap state
newReq = case req of
CompMsgSetFocus key -> setFocus <$> lookupNode widgetKeys "SetFocusOnKey" key
CompMsgMoveFocus (Just key) dir -> moveFocusFrom key dir
CompMsgMoveFocus _ dir -> Just $ MoveFocus Nothing dir
CompMsgMessage key msg -> (`sendMsgTo` msg) <$> lookupNode widgetKeys "Message" key
result = resultReqs node . (: []) <$> newReq
setFocus node = SetFocus (node ^. L.info . L.widgetId)
moveFocusFrom key dir = mwid >> Just (MoveFocus mwid dir) where
mnode = lookupNode widgetKeys "MoveFocusFromKey" key
mwid = (^. L.info . L.widgetId) <$> mnode
mergeChild
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> CompositeState s e
-> WidgetEnv sp ep
-> s
-> WidgetNode s e
-> WidgetNode sp ep
-> WidgetResult sp ep
mergeChild comp state wenv newModel widgetRoot widgetComp = parentResult where
CompositeState{..} = state
oldModel = getCompositeModel state
cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap newModel
widgetId = _cpsRoot ^. L.info . L.widgetId
builtRoot = cascadeCtx wenv widgetComp (_cmpUiBuilder comp cwenv newModel)
& L.info . L.widgetId .~ widgetId
builtWidget = builtRoot ^. L.widget
initRequired = not (nodeMatches widgetRoot builtRoot)
mergedResult
| initRequired = widgetInit builtWidget cwenv builtRoot
| otherwise = widgetMerge builtWidget cwenv builtRoot widgetRoot
!mergedState = state {
_cpsModel = Just newModel,
_cpsRoot = mergedResult ^. L.node,
_cpsWidgetKeyMap = collectWidgetKeys M.empty (mergedResult ^. L.node)
}
childReqs = fmap ($ oldModel) (_cmpOnChangeReq comp)
parentReqs = widgetDataSet (_cmpWidgetData comp) newModel
++ [ResizeWidgets widgetId | initRequired]
childResult = mergedResult
& L.requests <>~ Seq.fromList childReqs
result = toParentResult comp mergedState wenv widgetComp childResult
parentResult = result
& L.requests .~ Seq.fromList parentReqs <> result ^. L.requests
getUserModel
:: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)
=> Composite s e sp ep
-> WidgetEnv sp ep
-> s
getUserModel comp wenv = widgetDataGet (_weModel wenv) (_cmpWidgetData comp)
getCompositeModel
:: (CompositeModel s, CompositeEvent e)
=> CompositeState s e
-> s
getCompositeModel state = case _cpsModel state of
Just model -> model
_ -> error "Error calling getCompositeModel: widgetInit has not been invoked."
toParentReq
:: (CompositeModel s, CompParentModel sp)
=> WidgetId
-> WidgetRequest s e
-> Maybe (WidgetRequest sp ep)
toParentReq _ IgnoreParentEvents = Just IgnoreParentEvents
toParentReq _ IgnoreChildrenEvents = Just IgnoreChildrenEvents
toParentReq _ (ResizeWidgets wid) = Just (ResizeWidgets wid)
toParentReq _ (ResizeWidgetsImmediate wid) = Just (ResizeWidgetsImmediate wid)
toParentReq _ (MoveFocus start dir) = Just (MoveFocus start dir)
toParentReq _ (SetFocus path) = Just (SetFocus path)
toParentReq _ (GetClipboard path) = Just (GetClipboard path)
toParentReq _ (SetClipboard clipboard) = Just (SetClipboard clipboard)
toParentReq _ (StartTextInput rect) = Just (StartTextInput rect)
toParentReq _ StopTextInput = Just StopTextInput
toParentReq _ (ResetOverlay wid) = Just (ResetOverlay wid)
toParentReq _ (SetOverlay wid path) = Just (SetOverlay wid path)
toParentReq _ (SetCursorIcon wid icon) = Just (SetCursorIcon wid icon)
toParentReq _ (ResetCursorIcon wid) = Just (ResetCursorIcon wid)
toParentReq _ (StartDrag wid path info) = Just (StartDrag wid path info)
toParentReq _ (StopDrag wid) = Just (StopDrag wid)
toParentReq _ RenderOnce = Just RenderOnce
toParentReq _ (RenderEvery path ms repeat) = Just (RenderEvery path ms repeat)
toParentReq _ (RenderStop path) = Just (RenderStop path)
toParentReq _ (RemoveRendererImage name) = Just (RemoveRendererImage name)
toParentReq _ (ExitApplication exit) = Just (ExitApplication exit)
toParentReq _ (UpdateWindow req) = Just (UpdateWindow req)
toParentReq _ (SetWidgetPath wid path) = Just (SetWidgetPath wid path)
toParentReq _ (ResetWidgetPath wid) = Just (ResetWidgetPath wid)
toParentReq wid (UpdateModel fn) = Just (SendMessage wid (CompMsgUpdate fn))
toParentReq wid (RaiseEvent message) = Just (SendMessage wid message)
toParentReq _ (SendMessage wid message) = Just (SendMessage wid message)
toParentReq _ (RunTask wid path action) = Just (RunTask wid path action)
toParentReq _ (RunProducer wid path action) = Just (RunProducer wid path action)
toParentReq _ (RunInRenderThread wid path action) = Just (RunInRenderThread wid path action)
collectWidgetKeys
:: Map WidgetKey (WidgetNode s e)
-> WidgetNode s e
-> Map WidgetKey (WidgetNode s e)
collectWidgetKeys keys node = newMap where
children = node ^. L.children
collect currKeys child = collectWidgetKeys currKeys child
updatedMap = case node ^. L.info . L.key of
Just key -> M.insert key node keys
_ -> keys
newMap = foldl' collect updatedMap children
convertWidgetEnv :: WidgetEnv sp ep -> WidgetKeyMap s e -> s -> WidgetEnv s e
convertWidgetEnv wenv widgetKeyMap model = WidgetEnv {
_weOs = _weOs wenv,
_weDpr = _weDpr wenv,
_weAppStartTs = _weAppStartTs wenv,
_weFontManager = _weFontManager wenv,
_weFindBranchByPath = _weFindBranchByPath wenv,
_weMainButton = _weMainButton wenv,
_weContextButton = _weContextButton wenv,
_weTheme = _weTheme wenv,
_weWindowSize = _weWindowSize wenv,
_weWidgetShared = _weWidgetShared wenv,
_weWidgetKeyMap = widgetKeyMap,
_weCursor = _weCursor wenv,
_weHoveredPath = _weHoveredPath wenv,
_weFocusedPath = _weFocusedPath wenv,
_weDragStatus = _weDragStatus wenv,
_weMainBtnPress = _weMainBtnPress wenv,
_weOverlayPath = _weOverlayPath wenv,
_weModel = model,
_weInputStatus = _weInputStatus wenv,
_weTimestamp = _weTimestamp wenv,
_weThemeChanged = _weThemeChanged wenv,
_weInTopLayer = _weInTopLayer wenv,
_weLayoutDirection = LayoutNone,
_weViewport = _weViewport wenv,
_weOffset = _weOffset wenv
}
cascadeCtx
:: WidgetEnv sp ep -> WidgetNode sp ep -> WidgetNode s e -> WidgetNode s e
cascadeCtx wenv parent child = newChild where
pVisible = parent ^. L.info . L.visible
pEnabled = parent ^. L.info . L.enabled
cVisible = child ^. L.info . L.visible
cEnabled = child ^. L.info . L.enabled
newPath = parent ^. L.info . L.path |> 0
newChild = child
& L.info . L.widgetId .~ WidgetId (wenv ^. L.timestamp) newPath
& L.info . L.path .~ newPath
& L.info . L.visible .~ (cVisible && pVisible)
& L.info . L.enabled .~ (cEnabled && pEnabled)
lookupNode :: WidgetKeyMap s e -> String -> WidgetKey -> Maybe (WidgetNode s e)
lookupNode widgetKeys desc key = case M.lookup key widgetKeys of
Nothing -> trace ("Key " ++ show key ++ " not found for " ++ desc) Nothing
res -> res
sendMsgTo :: Typeable i => WidgetNode s e -> i -> WidgetRequest sp ep
sendMsgTo node msg = SendMessage (node ^. L.info . L.widgetId) msg