owickstrom / gi-gtk-declarative

Declarative GTK+ programming in Haskell
https://owickstrom.github.io/gi-gtk-declarative/
288 stars 35 forks source link

An example without `App.Simple`? #96

Open kindaro opened 3 years ago

kindaro commented 3 years ago

There are many great examples, but all of them use App.Simple. I would like to use some other architecture for my application. A simple example of opening a window with some buttons would go a long way. So far I have this:

module Main where

import qualified Control.Concurrent.Async as Async
import qualified Data.GI.Base as Base
import qualified GI.Gtk as Gtk
import GI.Gtk.Declarative

main ∷ IO ( )
main = do
  _ ← Gtk.init Nothing
  main ← Async.async Gtk.main
  win ← Base.new Gtk.Window [#title Base.:= ""]
  #showAll win
  -- Insert code here.
  Gtk.mainQuit
  Async.wait main

It works so far, but it took me something like 2 hours to get there! And it does not really use any features of gi-gtk-declarative because I could not figure out how to open a window yet.

kindaro commented 3 years ago

An example that declaratively creates a «close» button:

module Main where

import qualified Control.Concurrent.Async as Async
import qualified GI.Gtk as Gtk

import GI.Gtk.Declarative
import GI.Gtk.Declarative.State
import GI.Gtk.Declarative.EventSource

main ∷ IO ( )
main = do
  _ ← Gtk.init Nothing
  main ← Async.async Gtk.main
  let windowWidget = bin Gtk.Window [#title := ""] (widget Gtk.Button [#label := "close", on #clicked ( )])
  windowState ← create @Widget windowWidget
  window ← someStateWidget windowState
  #showAll window
  subscribe windowWidget windowState (\ ( ) → Gtk.mainQuit)
  Async.wait main
Dretch commented 3 years ago

I don't really have a general answer to this question... but personally I ended up forking gi-gtk-declarative and hacking a component system in - see: https://github.com/Dretch/gi-gtk-declarative

I'd like to move the component system to a package that sits on-top of the regular gi-gtk-declarative, but so far I can't find a way around the Functor constraint on widgets (since this means you can't do dynamic typing with events, basically).

owickstrom commented 3 years ago

I don't have any clean example of another architecture to share, unfortunately. https://github.com/owickstrom/komposition uses this library but with an indexed monad approach to stateful resources (GTK windows primarily). It's a bunch of (some mutually recursive) functions that create new windows and modals, patch the widget trees in them, and eventually destroys the resources they created. It's more advanced and I believe hard to generalize, but I would make sense with a simplified example. I haven't had time to extract it from Komposition, though. If anyone would like to have a stab it I think it would be a welcome contribution to the examples!

owickstrom commented 3 years ago

By the way, there's an in-between approach that comes to mind, where you don't go all the way to indexed monads to ensure correct resource handling. You could just have values that represent windows/modals, and define create/update/destroy functions for such resources. I wrote about that style in Finite-State Machines, Part 2: Explicit Typed State Transitions.

With Linear Haskell that could even be made safe, but it's not something I've fiddled with.

kindaro commented 3 years ago

I appreciate the experts sharing their advanced architectures with me and I would like to eventually explore them all. But at this time it would be hard for me to do so. I have not been working with GUI previously, so for me the question is more like «how to build up from first principles», rather than «how to get more power». I hardly even understand the basics, so it would be hard for me to appreciate the benefits of a finer architecture.

Observe that any underlying imperative platform may be wrapped into the «update + view + state₀» idiom:

Almost the same! So, while gi-gtk-declarative-app-simple is a comfortable interface, it does not give me a way to appreciate the features of gi-gtk-declarative per se.

gi-gtk-declarative-app-simple is not at all trivial, and I have not been able to reverse engineer it yet. (At this time I am trying to get both a subscription and an event in the same place by smuggling the event from subscribe through an MVar and I get a thread blocked indefinitely exception.) Some of this complexity is essential, some is accidental, and some of it is due to the interface offered by gi-gtk-declarative being hard to use.

For example, patch wants two widgets and a SomeState, and it is implied that the SomeState was obtained from the first widget — but what if I confuse the widgets? Since SomeState is made from a widget, it may as well remember from which. Then patch would only take SomeState and a new widget. subscribe is another example — it provides an event and a Subscription in two different places, and then I have to somehow cancel the subscription while handling the event. Also, the same logic applies as to patch — if the widget was bundled into SomeState, subscribe would need one argument less.

This is why I think it would be good to give an example of how the basic building blocks offered by gi-gtk-declarative may be put together in the most simple way. On the one hand, it will give the reader an appreciation of how gi-gtk-declarative works, and on the other hand it may show us ways to improve and simplify its interface.

P. S.   I fixed the MVar error. But now I get a transient segmentation fault. Eh.

kindaro commented 3 years ago

My self-contained example, so far.