{-# LANGUAGE DeriveFunctor #-}

-- | 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 qualified GI.Gtk                         as Gtk
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 customData
  = CustomReplace
  | CustomModify (widget -> IO SomeState)
  | 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 'customData' as a way of passing
-- parameters, and emits events of type 'event'.
data CustomWidget widget customData event =
  CustomWidget
  { customWidget :: Gtk.ManagedPtr widget -> widget
  -- ^ The widget constructor
  , customCreate :: customData -> IO SomeState
  -- ^ Action that creates the initial widget
  , customPatch :: SomeState -> customData -> customData -> CustomPatch widget customData
  -- ^ Patch function, calculating a 'CustomPatch' based on the state,
  -- old custom data, and new custom data.
  , customSubscribe :: customData -> widget -> (event -> IO ()) -> IO Subscription
  -- ^ Action that creates an event subscription for the custom widget
  , customData :: customData
  -- ^ The custom data (e.g. parameters) of the custom widget
  } deriving (Functor)

instance Gtk.IsWidget widget => Patchable (CustomWidget widget customData) where
  create custom = customCreate custom (customData custom)
  patch state' old new =
    case customPatch old state' (customData old) (customData new) of
      CustomReplace -> Replace (customCreate new (customData new))
      CustomModify f -> Modify (f =<< Gtk.unsafeCastTo (customWidget new) =<< someStateWidget state')
      CustomKeep -> Keep

instance Gtk.GObject widget => EventSource (CustomWidget widget customData) where
  subscribe custom state' cb = do
    w' <- Gtk.unsafeCastTo (customWidget custom) =<< someStateWidget state'
    customSubscribe custom (customData custom) w' cb