From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail4-relais-sop.national.inria.fr (mail4-relais-sop.national.inria.fr [192.134.164.105]) by walapai.inria.fr (8.13.6/8.13.6) with ESMTP id q1F4lHQP027474 for ; Wed, 15 Feb 2012 05:47:17 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AjkBAPo2O09KfVI0imdsb2JhbABDqAYBiE8IIgEBAQoJDQcSBiOBcgEBAQMBEgITGQEbHQEDAQsGBQs7IQEBEQEFARwGEyKHWgmcJgqLcYJwhHo/iHMCBQuIOYMaAwgCBwQBCQcBBAENBQECBQODeUsCG4MvBIhLjGmLEoMVPYQi X-IronPort-AV: E=Sophos;i="4.73,421,1325458800"; d="scan'208";a="131389906" Received: from mail-ww0-f52.google.com ([74.125.82.52]) by mail4-smtp-sop.national.inria.fr with ESMTP/TLS/RC4-SHA; 15 Feb 2012 05:47:11 +0100 Received: by wgbds10 with SMTP id ds10so672930wgb.9 for ; Tue, 14 Feb 2012 20:47:11 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=mime-version:in-reply-to:references:date:message-id:subject:from:to :cc:content-type; bh=BoQVQekap0WO3T7oGNJA6Mu5l4bmmOTNGYCqDSbMLpg=; b=mycOCsHMF2d2OInf69RN55VBeWsTmPz2HwR6g3RHX99jelvE+4avDD1abvLo0M96Gf tibeyPBYrhGc2MyiGbLEwNG7j1xuAmT0sfsq/1R19uu9CiD6WLMlDaIea3eITkaNUEZY ytvkfHwlV5kutFwAXT5LPOn3gx0xNpvM4/9SA= MIME-Version: 1.0 Received: by 10.216.138.86 with SMTP id z64mr2050276wei.31.1329281230912; Tue, 14 Feb 2012 20:47:10 -0800 (PST) Received: by 10.223.7.69 with HTTP; Tue, 14 Feb 2012 20:47:10 -0800 (PST) In-Reply-To: References: Date: Tue, 14 Feb 2012 21:47:10 -0700 Message-ID: From: Anthony Tavener To: Philippe Veber Cc: caml users Content-Type: multipart/alternative; boundary=0016e6d64762933ceb04b8f9678f Subject: Re: [Caml-list] Functional GUI programming: looking for good practices --0016e6d64762933ceb04b8f9678f Content-Type: text/plain; charset=ISO-8859-1 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 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 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 >>> >>> >> > --0016e6d64762933ceb04b8f9678f Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable 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 m= ore 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 statefu= l 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 com= municate 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!

=A0-Tony


On Tue, Feb 14, 20= 12 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 element= s: 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 hardl= y understand it now! :)

I was looking to write UI = code much as your example of packing buttons together with directly bound a= ctivation functions.

Here's my "menu_of_list", which takes a l= ist of string * 'a pairs, calling GUI code to make a menu using the str= ings as labels, and using the 'a values as return values...

let menu_of_list lst return =3D
=A0 (* snipped: pr= eamble which handles positioning, stacking order, getting font, etc *)
=A0 Gui.add_widget gui
=A0 =A0 (new Gui.vbox pos stacking s= pacing
=A0 =A0 =A0 (lst |> List.map
=A0 =A0 =A0 =A0 (fun (label,= value) ->
=A0 =A0 =A0 =A0 =A0 Gui.wit_fn gui
=A0 =A0= =A0 =A0 =A0 =A0 (new Gui.rectangle_text w h fnt label)
=A0 =A0 = =A0 =A0 =A0 =A0 (fun () -> return (Some value) ) )))

The important part here is the "return" funct= ion. This will resume the UI coroutine. It is given to "menu_of_list&q= uot; by the UI code, then when the GUI has an activation of a menu button t= his "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 por= tions of the run stack. Here I've extracted the relevant bits of code:<= /div>


(* this runs as a coroutine with th= e "mainline" *)
let ui ui_process () =3D

=A0 (* in actual cod= e, this menu comes after a "right click", for special actions *)<= /div>
=A0 let act =3D yield ui_process (menu_of_list coord
=A0 =A0 [ ("Equip",`Equip);
=A0 =A0 =A0 ("Spell",`Spell);
=A0 =A0 =A0 ("E= nd",`End) ] ) in
=A0 (* ... handle whatever action the= user chose! *)


(* given SDL events= , calls activation functions of 'hit' widgets *)
let gui_react gui events =3D
=A0 let hits =3D gui_select gui= events in
=A0 match hits with
=A0 | h::t -> begin
=A0 =A0 match get_binding gui h with
=A0 =A0 | Some f -&= gt; f () =A0(* runs activation function of widget, such as "return&quo= t; to coroutine! *)
=A0 =A0 | None -> ()
=A0 =A0 end
=A0 | [] ->= ()

let _ =3D
=A0 (* A prompt is delimcc= 's mechanism for marking the stack to -limit- the
=A0 =A0* co= ntinuation, rather than creating whole-program continuations. *)
=A0 (* So here, the "ui" is initially run, and will *resume*= this mainline continuation
=A0 =A0* at the end of the "user= " function call. *)
=A0 let ui_prompt =3D new_prompt () in
=A0 push_prompt ui_prompt (ui ui_prompt);

=A0 ...<= /div>

=A0 (* in mainloop *)
=A0 =A0 gui_react = gui events;
=A0=A0
----------------------
Now I'm going to restate the yield function here, and try to explain...=

let yield level fn =3D
=A0 shift level (fun k ->
=A0 =A0 fn k;
=A0 =A0 ab= ort level () )

"shift" and "abort" are delimcc. This = runs the given function "fn" with a continuation 'k'... s= o this continuation is the "return" function passed to menu_of_li= st, and therefore bound to each menu-button. The abort exits the "ui c= oroutine", resuming the mainline, hence the name: yield.

The continuation 'k' comes from the shift... th= e 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 returne= d action!

I hope this conveys at least the gist of what's goi= ng on... I read a lot of papers over-and-over, not understanding... althoug= h none were specifically GUI. Delimited continuations have numerous applica= tions and a surprising number of configurations for just a pair of function= s! (Abort is really a special case of "reset"... so it's shif= t and reset, in ump-teen configurations.)

I'll try to explain if you have any further questio= ns! However, I'm still trying to sort out how best to write my GUI code= -- there is a lot of room for improvement. :)

=A0-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 co= routines". 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 suppo= sed 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 t= han "signals and slots" -- the usual scattered GUI code which is = connected by messages/events.


On Tue, Feb 14, 20= 12 at 3:17 AM, Philippe Veber <philippe.veber@gmail.com> wrote:
Hi Anthony,

This looks interesting, however as I'm not very fami= liar with delimcc (that's a shame, I admit), I fail to understand the f= low of the program. Would you mind giving a snippet of the update loop you = mentionned?
=A0
So far, I'm not sure how well this works= out for a complete project. I like it so far, but I have complexity growin= g 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 th= e hunt for elegant UI (code)!

Let's hope I'm not just Dahu hunt= ing!
(h= ttp://en.wikipedia.org/wiki/Dahu)

=A0

=A0Tony




--0016e6d64762933ceb04b8f9678f--