Recording my progress porting gi-gtk-declarative to gtk4

Posted on February 1, 2022

This blog post is used to track my progress in porting gi-gtk-declarative to Gtk4.

TODO Understand how gi-gtk-declarative works

TODO The overall architecture

Terminology

The difference between “Widget” and “widget”

EventSource

class EventSource widget where
  subscribe
    :: widget event     -- ^ Declarative widget with event handlers.
    -> SomeState        -- ^ State of rendered widget tree.
    -> (event -> IO ()) -- ^ Event callback, invoked on each emitted event until
                        -- the 'Subscription' is cancelled, or widget is otherwise
                        -- destroyed.
    -> IO Subscription  -- ^ A 'Subscription' is returned, which can be cancelled.

The Widget wrapper and FromWidget class

data Widget event where
  Widget
    ::( Typeable widget
       , Patchable widget  -- ^ widget is Patchable
       , Functor widget
       , EventSource widget -- ^ widget is EventSource
       )
    => widget event
    -> Widget event

Functor is defined for Widget

The default Patchable implementation delegates creation and patching to the underlying widget. The default EventSource implmentation delegates subscription to the underlying widget.

class FromWidget widget target where
  fromWidget :: widget event -> target event

instance ( Typeable parent
         , Typeable child
         , Patchable (parent child)
         , Functor (parent child)
         , EventSource (parent child)
         )
         => FromWidget (parent child) Widget where
  fromWidget = Widget

FromWidget a b means that you can convert from (a event) to (b event). You can convert from unwrapped widget to wrapped widget or vice versa. The instance defined here provides the default implementation to wrap a widget.

  1. TODO What is this (parent child) thing???

    Firstly, remember that Typeable is polykinded.

    Notice that (parent child) has kind * -> *, we can guess it’s like (parent child) ~ widget

    The purpose of using (parent child) instead of widget is that we preserve the knowledge that both parent and child is Typeable?

TODO Single Widget

use widget smart constructor from SingleWidget.hs

data SingleWidget widget event where
  SingleWidget
    ::(Typeable widget, Gtk.IsWidget widget, Functor (Attribute widget))
    => (Gtk.ManagedPtr widget -> widget)
    -> Vector (Attribute widget event)
    -> SingleWidget widget event

  -- | Construct a /leaf/ widget, i.e. one without any children.
  widget
    :: ( Typeable widget
       , Gtk.IsWidget widget
       , FromWidget (SingleWidget widget) target
       )
    => (Gtk.ManagedPtr widget -> widget) -- ^ A widget constructor from the underlying gi-gtk library.
    -> Vector (Attribute widget event)   -- ^ List of 'Attribute's.
    -> target event                      -- ^ The target, whose type is decided by 'FromWidget'.
  widget ctor = fromWidget . SingleWidget ctor

The patchable instance

Attribute is from GI.GTK.Attributes

TODO understand the shadow state tree

Patchable can be diffed to produce a patch a patch is IO SomeState

-- | A 'Data.Dynamic.Dynamic'-like container of a 'StateTree' value.
data SomeState where
  SomeState
    :: ( Gtk.IsWidget widget
      , Typeable widget
      , Typeable customState
      )
    => StateTree stateType widget child event customState
    -> SomeState

-- | The types of state trees that are available, matching the types
-- of GTK+ widgets (single widget, bin, and container.)
data StateType = WidgetState | BinState | ContainerState

-- | A state tree for a specific 'widget'. This is built up recursively
-- to contain child state trees, for bin and container child widgets.
data StateTree (stateType :: StateType) widget child event customState where
  StateTreeWidget
    :: !(StateTreeNode widget event customState)
    -> StateTree 'WidgetState widget child event customState
  StateTreeBin
    :: !(StateTreeNode widget event customState)
    -> SomeState
    -> StateTree 'BinState widget child event customState
  StateTreeContainer
    :: ( Gtk.IsContainer widget
       , IsContainer widget child
       )
    => !(StateTreeNode widget event customState)
    -> Vector SomeState
    -> StateTree 'ContainerState widget child event customState

TODO deal with the removal of IsBin and IsContainer class

removal of destroyWidget

removal of ChildStates

Paned

Gtk3: https://docs.gtk.org/gtk3/class.Paned.html Gtk4: https://docs.gtk.org/gtk4/class.Paned.html

call label function in instances

in Data.GI.Base

get :: forall info attr obj result m. (AttrGetC info obj attr result, MonadIO m) => obj -> AttrLabelProxy (attr :: Symbol) -> m result

also a possible example from Attributes.hs

-- | The attribute GADT represent a supported attribute for a declarative
-- widget. This extends the regular notion of GTK+ attributes to also include
-- event handling and CSS classes.
data Attribute widget event where
  -- | An attribute/value mapping for a declarative widget. The
  -- 'GI.AttrLabelProxy' is parameterized by 'attr', which represents the
  -- GTK-defined attribute name. The underlying GI object needs to support
  -- the /construct/, /get/, and /set/ operations for the given attribute.
  (:=)
    ::(GI.AttrOpAllowed 'GI.AttrConstruct info widget
      , GI.AttrOpAllowed 'GI.AttrSet info widget
      , GI.AttrGetC info widget attr getValue
      , GI.AttrSetTypeConstraint info setValue
      , KnownSymbol attr
      , Typeable attr
      , Eq setValue
      , Typeable setValue
      )
   => GI.AttrLabelProxy (attr :: Symbol) -> setValue -> Attribute widget event

AttrSetC -> AttrSetTypeConstraint -> IsWidget

so we need IsWidget in the context of Bin child

seems that GHC cannot infer it? is it because of Widget and fromWidget?