caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
From: Anthony Tavener <anthony.tavener@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 11:02:15 -0700	[thread overview]
Message-ID: <CAN=ouMSeauccf_+3upwsmTCxNzyKJBMkvjmatbKaV=4j=AscmQ@mail.gmail.com> (raw)
In-Reply-To: <CAOOOohS8pQxJS=VCfuRQg6OAJJkKi2w1KeHp+vcTOjsdApc8Nw@mail.gmail.com>

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

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: 7460 bytes --]

  reply	other threads:[~2012-02-14 18:02 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 [this message]
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='CAN=ouMSeauccf_+3upwsmTCxNzyKJBMkvjmatbKaV=4j=AscmQ@mail.gmail.com' \
    --to=anthony.tavener@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;
as well as URLs for NNTP newsgroup(s).