gi-gtk-declarative-0.6.3: src/GI/Gtk/Declarative/CustomWidget.hs
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
-- | While you can instantiate 'Patchable' and 'EventSource' for your
-- own data types, it's a bit complicated. The 'CustomWidget' data
-- type takes care of some lower-level detail, so that you can focus
-- on the custom behavior of your widget. You still need to think
-- about and implement a patching function, but in an easier way.
module GI.Gtk.Declarative.CustomWidget
( CustomPatch(..)
, CustomWidget(..)
)
where
import Data.Typeable
import Data.Vector ( Vector )
import qualified GI.Gtk as Gtk
import GI.Gtk.Declarative.Attributes
import GI.Gtk.Declarative.Attributes.Collected
import GI.Gtk.Declarative.EventSource
import GI.Gtk.Declarative.Patch
import GI.Gtk.Declarative.State
-- | Similar to 'Patch', describing a possible action to perform on a
-- 'Gtk.Widget', decided by 'customPatch'.
data CustomPatch widget internalState
= CustomReplace
| CustomModify (widget -> IO internalState)
| CustomKeep
-- | A custom widget specification, with all functions needed to
-- instantiate 'Patchable' and 'EventSource'. A custom widget:
--
-- * is based on a top 'widget'
-- * can use 'internalState' as a way of keeping an internal state
-- value threaded through updates, which is often useful for passing
-- references to child widgets used in a custom widget
-- * emits events of type 'event'
data CustomWidget widget params internalState event
= CustomWidget
{ -- | The widget constructor
customWidget :: Gtk.ManagedPtr widget -> widget,
-- | Action that creates the initial widget
customCreate :: params -> IO (widget, internalState),
-- | Patch function, calculating a 'CustomPatch' based on the state,
-- old custom data, and new custom data
customPatch :: params -> params -> internalState -> CustomPatch widget internalState,
-- | Action that creates an event subscription for the custom widget
customSubscribe :: params -> internalState -> widget -> (event -> IO ()) -> IO Subscription,
-- | Declarative 'Attribute's for the custom widget (properties and
-- classes are handled automatically in patching)
customAttributes :: Vector (Attribute widget event),
-- | Parameters passed when constructing the declarative custom widget
customParams :: params
}
deriving (Functor)
instance
( Typeable widget,
Typeable internalState,
Gtk.IsWidget widget
) =>
Patchable (CustomWidget widget params internalState)
where
create custom = do
(widget, internalState) <- customCreate custom (customParams custom)
Gtk.widgetShow widget
let collected = collectAttributes (customAttributes custom)
updateProperties widget mempty (collectedProperties collected)
sc <- Gtk.widgetGetStyleContext widget
updateClasses sc mempty (collectedClasses collected)
pure
(SomeState
(StateTreeWidget (StateTreeNode widget sc collected internalState))
)
patch (SomeState (stateTree :: StateTree st w e c cs)) old new =
case (eqT @cs @internalState, eqT @widget @w) of
(Just Refl, Just Refl) ->
let
oldCollected = stateTreeCollectedAttributes (stateTreeNode stateTree)
newCollected = collectAttributes (customAttributes new)
oldCollectedProps = collectedProperties oldCollected
newCollectedProps = collectedProperties newCollected
canBeModified = oldCollectedProps `canBeModifiedTo` newCollectedProps
in
case
customPatch new
(customParams old)
(customParams new)
(stateTreeCustomState (stateTreeNode stateTree))
of
CustomReplace -> Replace (create new)
p
| canBeModified -> Modify $ do
let widget' = stateTreeNodeWidget stateTree
updateProperties widget' oldCollectedProps newCollectedProps
updateClasses
(stateTreeStyleContext (stateTreeNode stateTree))
(collectedClasses oldCollected)
(collectedClasses newCollected)
let node = stateTreeNode stateTree
internalState' <- case p of
CustomModify f ->
f =<< Gtk.unsafeCastTo (customWidget new) widget'
CustomKeep -> pure (stateTreeCustomState node)
CustomReplace -> pure (stateTreeCustomState node) -- already handled above
return
(SomeState
(StateTreeWidget node
{ stateTreeCustomState = internalState'
, stateTreeCollectedAttributes = newCollected
}
)
)
| otherwise -> Replace (create new)
_ -> Replace (create new)
instance
(Typeable internalState, Gtk.GObject widget) =>
EventSource (CustomWidget widget params internalState)
where
subscribe custom (SomeState (stateTree :: StateTree st w e c cs)) cb =
case eqT @cs @internalState of
Just Refl -> do
w' <- Gtk.unsafeCastTo (customWidget custom)
(stateTreeNodeWidget stateTree)
customSubscribe custom
(customParams custom)
(stateTreeCustomState (stateTreeNode stateTree))
w'
cb
Nothing -> pure (fromCancellation (pure ()))