caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
From: Philippe Veber <philippe.veber@gmail.com>
To: Anthony Tavener <anthony.tavener@gmail.com>,
	Adrien <camaradetux@gmail.com>
Cc: caml users <caml-list@inria.fr>
Subject: Re: [Caml-list] Functional GUI programming: looking for good practices
Date: Wed, 22 Feb 2012 12:57:44 +0100	[thread overview]
Message-ID: <CAOOOohR-zvcvHazu0JjXx2ZQ+FdNoPUQEEpNAk7em+76rU1rbg@mail.gmail.com> (raw)
In-Reply-To: <CAN=ouMQQYAKcQHYV_A6QuN0-J=KMPFtJ3zWVr9BFQOrpZ4v1Pw@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 6891 bytes --]

Thank you very much Anthony and Adrien, your comments helped me a lot, I
now have plenty of material to think on!
Best,
  ph.

2012/2/15 Anthony Tavener <anthony.tavener@gmail.com>

> Hrm... when I re-read my prior post before sending, it made sense. Several
> hours later it seems inadequate, and I've thought of something to say more
> clearly...
>
> The execution of code bounces between the UI and the mainline. When the
> mainline processes a "gui hit" it will resume the UI code *right where it
> left off (yielded)*... then the UI will do something and get to another
> point where it yields, awaiting input and thereby resuming the mainline
> where it was (back to processing gui hits).
>
> Why would I want this? So that I don't have stateful UI code which has to
> trickle down to the right place all the time. The UI code is clearer
> because it doesn't need to be re-entered from the top each frame. This
> suits the declarative style of GUI specification, like the example you
> gave, since we don't need special state or messages to communicate what the
> GUI is doing. GUI state becomes a property of the... well, the stack. It's
> the current scope of the code.
>
> Hope this helps!
>
>  -Tony
>
>
> On Tue, Feb 14, 2012 at 11:02 AM, Anthony Tavener <
> anthony.tavener@gmail.com> wrote:
>
>> Apologies Philippe, this is a bit long...
>>
>> The "update loop" I mentioned might be a bit of a red-herring, as I'm
>> only using that for continuously active UI elements: such as changing
>> cursor to represent the action which would be taken on click. It has
>> nothing to do with the basic UI flow.
>>
>> I didn't understand delimcc right away, and I hardly understand it now! :)
>>
>> I was looking to write UI code much as your example of packing buttons
>> together with directly bound activation functions.
>>
>> Here's my "menu_of_list", which takes a list of string * 'a pairs,
>> calling GUI code to make a menu using the strings as labels, and using the
>> 'a values as return values...
>>
>> let menu_of_list lst return =
>>   (* snipped: preamble which handles positioning, stacking order, getting
>> font, etc *)
>>   Gui.add_widget gui
>>     (new Gui.vbox pos stacking spacing
>>       (lst |> List.map
>>         (fun (label,value) ->
>>           Gui.wit_fn gui
>>             (new Gui.rectangle_text w h fnt label)
>>             (fun () -> return (Some value) ) )))
>>
>> The important part here is the "return" function. This will resume the UI
>> coroutine. It is given to "menu_of_list" by the UI code, then when the GUI
>> has an activation of a menu button this "return" is called... resuming the
>> UI where it left off, and with the "Some value" which was associated to the
>> button.
>>
>> The magic of how this works is in delimcc capturing portions of the run
>> stack. Here I've extracted the relevant bits of code:
>>
>>
>> (* this runs as a coroutine with the "mainline" *)
>> let ui ui_process () =
>>
>>   (* in actual code, this menu comes after a "right click", for special
>> actions *)
>>   let act = yield ui_process (menu_of_list coord
>>     [ ("Equip",`Equip);
>>       ("Spell",`Spell);
>>       ("End",`End) ] ) in
>>   (* ... handle whatever action the user chose! *)
>>
>>
>> (* given SDL events, calls activation functions of 'hit' widgets *)
>> let gui_react gui events =
>>   let hits = gui_select gui events in
>>   match hits with
>>   | h::t -> begin
>>     match get_binding gui h with
>>     | Some f -> f ()  (* runs activation function of widget, such as
>> "return" to coroutine! *)
>>     | None -> ()
>>     end
>>   | [] -> ()
>>
>> let _ =
>>   (* A prompt is delimcc's mechanism for marking the stack to -limit- the
>>    * continuation, rather than creating whole-program continuations. *)
>>   (* So here, the "ui" is initially run, and will *resume* this mainline
>> continuation
>>    * at the end of the "user" function call. *)
>>   let ui_prompt = new_prompt () in
>>   push_prompt ui_prompt (ui ui_prompt);
>>
>>   ...
>>
>>   (* in mainloop *)
>>     gui_react gui events;
>>
>> ----------------------
>>
>> Now I'm going to restate the yield function here, and try to explain...
>>
>> let yield level fn =
>>   shift level (fun k ->
>>     fn k;
>>     abort level () )
>>
>> "shift" and "abort" are delimcc. This runs the given function "fn" with a
>> continuation 'k'... so this continuation is the "return" function passed to
>> menu_of_list, and therefore bound to each menu-button. The abort exits the
>> "ui coroutine", resuming the mainline, hence the name: yield.
>>
>> The continuation 'k' comes from the shift... the continuation is the code
>> "outside" of the shift call... so when it's called (by the mainline's
>> gui_react), it resume at the end of 'yield' and keep on going... in this
>> example, handling the returned action!
>>
>> I hope this conveys at least the gist of what's going on... I read a lot
>> of papers over-and-over, not understanding... although none were
>> specifically GUI. Delimited continuations have numerous applications and a
>> surprising number of configurations for just a pair of functions! (Abort is
>> really a special case of "reset"... so it's shift and reset, in ump-teen
>> configurations.)
>>
>> I'll try to explain if you have any further questions! However, I'm still
>> trying to sort out how best to write my GUI code -- there is a lot of room
>> for improvement. :)
>>
>>  -Tony
>>
>> PS. I'd like to blame X. Leroy ;)... for a post some 10 years ago
>> replying to someone's attempt to do a GUI in OCaml... Xavier casually
>> replied something like "oh, you can use coroutines". That was a wild goose
>> chase (or Dahu?)! Delimcc didn't exist at the time, and it's native-code
>> implementation came about just a few years ago (thank-you Oleg!). Even
>> then, I had no idea how I was supposed to use a coroutine to write GUI
>> code! Oh well, it was an adventure, and I still don't know if this is a
>> good thing, but I like it a lot more than "signals and slots" -- the usual
>> scattered GUI code which is connected by messages/events.
>>
>>
>> On Tue, Feb 14, 2012 at 3:17 AM, Philippe Veber <philippe.veber@gmail.com
>> > wrote:
>>
>>> Hi Anthony,
>>>
>>> This looks interesting, however as I'm not very familiar with delimcc
>>> (that's a shame, I admit), I fail to understand the flow of the program.
>>> Would you mind giving a snippet of the update loop you mentionned?
>>>
>>>
>>>> So far, I'm not sure how well this works out for a complete project. I
>>>> like it so far, but I have complexity growing in some "update loop" stuff,
>>>> which are little closures I add to be run each frame-update for reacting to
>>>> mouse-over/hover.
>>>>
>>>>
>>>
>>> Good luck in the hunt for elegant UI (code)!
>>>>
>>>
>>> Let's hope I'm not just Dahu hunting!
>>> (http://en.wikipedia.org/wiki/Dahu)
>>>
>>>
>>>
>>>>
>>>>  Tony
>>>>
>>>>
>>>
>>
>

[-- Attachment #2: Type: text/html, Size: 9624 bytes --]

  reply	other threads:[~2012-02-22 11:58 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
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 [this message]
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=CAOOOohR-zvcvHazu0JjXx2ZQ+FdNoPUQEEpNAk7em+76rU1rbg@mail.gmail.com \
    --to=philippe.veber@gmail.com \
    --cc=anthony.tavener@gmail.com \
    --cc=camaradetux@gmail.com \
    --cc=caml-list@inria.fr \
    /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;
as well as URLs for NNTP newsgroup(s).