Mailing list for all users of the OCaml language and system.
 help / color / mirror / Atom feed
From: Adrien <camaradetux@gmail.com>
To: Philippe Veber <philippe.veber@gmail.com>
Cc: caml users <caml-list@inria.fr>
Subject: Re: [Caml-list] Functional GUI programming: looking for good practices
Date: Tue, 14 Feb 2012 14:29:52 +0100	[thread overview]
Message-ID: <CAP5QFJkT6y0SDzmAeKnRdMTYMmRVbjUgKMNHKcu8vGOX49_zhA@mail.gmail.com> (raw)
In-Reply-To: <CAOOOohRd7VxXv8AYeGK0Te2FWHfMY_wJaL4LnPCcM=XcR52aEg@mail.gmail.com>

On 14/02/2012, Philippe Veber <philippe.veber@gmail.com> wrote:
> 2012/2/13 Adrien <camaradetux@gmail.com>
>> I've created a lablgtk branch named "adrien/react" to get react signals
>> out
>> of gtk properties and react events out of gtk signals (they match quite
>> well). Support isn't perfect but enough to test and experiment.
>>
>> The issue was to write the application itself: it was way too complicated
>> and it involved many many react values which had to be somehow kept alive.
>> It was also not very useful. The reason was that the very first thing I
>> would do with all the events was React.E.select: I would create distinct
>> signals only to merge them!
>>
>> What I've started doing instead is to have one "object" with a
>> corresponding
>> event: callbacks only send a message to that event and look like "send
>> (`Foo
>> bar)" and from then I can use match over the message in a different
>> location
>> in the code, typically in an FRP context.
>>
> I did that too, and it does seem a good practice: a central bus for
> information that is relevant to the whole application. [snip]

There is one clear benefit: less links.

When you have n elements that can trigger y actions, you need to setup (n*y)
links. If you go through a "central bus", you will only need (n+y) links.

Moreover, the callback code is often going to be very simple and you will be
able to touch the UI code less.

>> (* Warning: this is about work in-progress; it might not always make
>> sense,
>> might have some weird things and I might not be able to explain everything
>> properly. *)
>>
>> My current application is a web browser which I want to make much more
>> intelligent than the browsers available today.
>
> Just curious: in what way?

I want to do everything but make a browser that is a mere collection of
tabs without any relation between them. I consider a web browser to be a
tool and I want to improve my own tools.

Currently, if you don't use a hundred firefox add-ons, you get:
- linear history (if you use vim, try ':h undo-tree)
- linear tabs instead of a tree on screen and no way or a poor way to group
  pages together (including in the bookmarks)
- stupid popup/ad blockers because a popup opened by a popup is treated like
  a page opened on purpose by the user
- no way to recreate your browsing session: that you moved from page A to
  page B to page C, went back to page B, moved to page D, back to B and then
  to C again; ever browsed through your history looking for one element, and
  finding one which you had visited right before but still couldn't get a
  hold on the one you wanted?

There's a lot of semantic going on in browsing these days and I want take
advantage of it to improve browsing, especially in these horrible days of UI
simplification, which I'm having troubles using and which I believe are
dumbing down everything, moving everything away from the client and only to
facebook and twitter while making all the other non-stupid uses harder.


Of course, there's more to it: not throwing everything in an sqlite
database, hoping it'll be good for all uses of everything, including
full-text search. Faster everything. Configuration settings (chrom* has
almost none and firefox has a lump of completely undocumented crap).


And I want OCaml scripting inside the browser. ;-)


>> For this reason, I store
>> web pages in a data structure which is of course purely functional. I can
>> have several layers of data structures containing objects in the same way.
>>
>> My objects start with a default state and evolve (in a functional way)
>> through a fold according to the messages they receive. Each time the state
>> changes, two sets of callbacks are triggered: first, to change the UI;
>> second, to update the data structure containing the object which is needed
>> because of functional updates. I also use that last mechanism to propagate
>> messages from the inner objects to the outter ones.
>>
>> One last characteristic is that I have a UI side besides the functional
>> one.
>> It contains a handful of things which are needed to work with GTK.I also
>> use
>> it to propagate messages from the outter objects to the inner ones.
>>
>> This is work-in-progress and some details could be improved but I think
>> that
>> the big picture is there. As far as I can tell, the UI and functional
>> sides
>> are properly separated, constraints aren't heavy and I seem to be able to
>> get the usual qualities of ocaml in GUIs.
>>
> What module would you recommend reading to get a taste of it?

I have the "lablgtk-react" project but it's not well documented (partly
because it has changed a lot and documentation hasn't been updated).

Actually, it has changed so much that the "lablgtk" part has disappeared,
and react, which was not needed since it's using a functor for any FRP
implementation, is now not working anymore (I'll be fixing it soonish).

The best example is maybe caravel itself (the browser I'm doing):
  http://git.ocamlcore.org/cgi-bin/gitweb.cgi?p=caravel/caravel.git;a=tree;f=src/browser

The model/ folder has, huh, models which are purely functionals, and the ui/
folder has the UI code. At the top of the ui/page.ml, you can see:

  module Page = LablgtkReact.Core (MiniFRP) (BrowserModel.Page)

and a bit below:

  let core = new Page.core ~state_callbacks:[propagator] $foo in

That's because it's the UI which is the source of the messages.

The UI send message to "core" which contains an FRP signal of a data
structure. This data structure then changes according to the signal and both
the UI side and the parent data structure get a message about that.

The UI receives a message in through "core#msg_callbacks" with self#sink
(there is usually little work to do there).

The parent data structure gets a message through "core#state_callbacks" with
the propagator argument in the code above. In my case, this is a `Set
message which is handled like all the messages which are sent to the model
(i.e. through the on_action_func function, here
model/Navigation.on_action_func).

I need to make 2 or 3 diagrams to show the messaging that takes place.

>> PS: for layout management, I found that using the ~packing option when
>> creating the widgets is usually much better than #add'ing the afterwards.
>>
>
> I was hoping to build APIs with this kind of formulation:
>
> let window = panel (hpack [
>   picture "picture.bmp" ;
>   vpack [
>     button "Ok" (fun () -> set_some_event true) ;
>     button "Cancel" (fun () -> set_some_event false)
>   ]
> ])
>
> that is avoiding a "create and configure" style. If I can't get that, I'll
> probably stick with a traditional, imperative approach.

What about this?

  let window = GWindow.window () in
  let vbox = GPack.vbox ~packing:window#add () in
  let picture = foo "picture.bmp" ~packing:vbox#pack () in
  let hbox = GPack.hbox ~packing:vbox#pack ()in
  let ok = gbutton.button ~pack:hbox#pack () in
  let cancel = gbutton.button ~pack:hbox#pack () in
  ignore (ok#connect#clicked (fun () -> set_some_event true));
  ignore (cancel#connect#clicked (fun () -> set_some_event false))

It's a bit longer but not more difficult and it avoids a number of issues
you have in your code:
- typing issues: you can't store a button and a box in the same list: you
  have to use variants which make handling a bit heavier
- no easy way to access widgets (no variable name) with their "full" types
- very simple API
- not much freedom

That can work quite well depending on what you do. If your UI only ever
generates messages but never has to receive any, it should be good. As far
as I remember, there is support for such code in Lablgtk-extras (on the
ocaml forge).

It's quite nice to have something purely declarative and for which the code
can be formatted to look like the UI but I don't see this anymore as the
biggest issue for GUI programming and have therefore not spent much on that
recently.


Regards,
Adrien Nader

  parent reply	other threads:[~2012-02-14 13:30 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-02-13 11:01 Philippe Veber
2012-02-13 15:27 ` Adrien
2012-02-14 10:02   ` Philippe Veber
2012-02-14 10:21     ` Daniel Bünzli
2012-02-14 10:39       ` Philippe Veber
2012-02-14 11:52         ` Adrien
2012-02-14 13:00           ` Daniel Bünzli
2012-02-14 13:29     ` Adrien [this message]
2012-02-13 18:13 ` Raoul Duke
2012-02-14  0:36   ` Anthony Tavener
2012-02-14 10:17     ` Philippe Veber
2012-02-14 18:02       ` Anthony Tavener
2012-02-15  4:47         ` Anthony Tavener
2012-02-22 11:57           ` Philippe Veber
2012-02-28 10:10             ` Adrien

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=CAP5QFJkT6y0SDzmAeKnRdMTYMmRVbjUgKMNHKcu8vGOX49_zhA@mail.gmail.com \
    --to=camaradetux@gmail.com \
    --cc=caml-list@inria.fr \
    --cc=philippe.veber@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox