]> gitweb.factorcode.org Git - factor.git/commitdiff
Merge branch 'master' of git://factorcode.org/git/factor
authorDaniel Ehrenberg <littledan@Macintosh-103.local>
Tue, 19 Aug 2008 19:06:26 +0000 (21:06 +0200)
committerDaniel Ehrenberg <littledan@Macintosh-103.local>
Tue, 19 Aug 2008 19:06:26 +0000 (21:06 +0200)
1  2 
basis/ui/gadgets/gadgets-tests.factor
basis/ui/gadgets/gadgets.factor
basis/ui/ui.factor

index 0000000000000000000000000000000000000000,1a2555d5381276feabc417caced99f93d5cd8d30..0bce366fccc2253c1b70bed75885fdbc07c75f4c
mode 000000,100755..100755
--- /dev/null
@@@ -1,0 -1,165 +1,165 @@@
 -tools.test namespaces models kernel dlists dequeues math sets
+ IN: ui.gadgets.tests
+ USING: accessors ui.gadgets ui.gadgets.packs ui.gadgets.worlds
 -        [ t ] [ graft-queue dequeue-empty? ] unit-test
++tools.test namespaces models kernel dlists deques math sets
+ math.parser ui sequences hashtables assocs io arrays prettyprint
+ io.streams.string math.geometry.rect ;
+ [ { 300 300 } ]
+ [
+     ! c contains b contains a
+     <gadget> "a" set
+     <gadget> "b" set
+     "a" get "b" get swap add-gadget drop
+     <gadget> "c" set
+     "b" get "c" get swap add-gadget drop
+     ! position a and b
+     { 100 200 } "a" get set-rect-loc
+     { 200 100 } "b" get set-rect-loc
+     ! give c a loc, it doesn't matter
+     { -1000 23 } "c" get set-rect-loc
+     ! what is the location of a inside c?
+     "a" get "c" get relative-loc
+ ] unit-test
+ <gadget> "g1" set
+ { 10 10 } "g1" get set-rect-loc
+ { 30 30 } "g1" get set-rect-dim
+ <gadget> "g2" set
+ { 20 20 } "g2" get set-rect-loc
+ { 50 500 } "g2" get set-rect-dim
+ <gadget> "g3" set
+ { 100 200 } "g3" get set-rect-dim
+ "g1" get "g2" get swap add-gadget drop
+ "g2" get "g3" get swap add-gadget drop
+ [ { 30 30 } ] [ "g1" get screen-loc ] unit-test
+ [ { 30 30 } ] [ "g1" get screen-rect rect-loc ] unit-test
+ [ { 30 30 } ] [ "g1" get screen-rect rect-dim ] unit-test
+ [ { 20 20 } ] [ "g2" get screen-loc ] unit-test
+ [ { 20 20 } ] [ "g2" get screen-rect rect-loc ] unit-test
+ [ { 50 180 } ] [ "g2" get screen-rect rect-dim ] unit-test
+ [ { 0 0 } ] [ "g3" get screen-loc ] unit-test
+ [ { 0 0 } ] [ "g3" get screen-rect rect-loc ] unit-test
+ [ { 100 200 } ] [ "g3" get screen-rect rect-dim ] unit-test
+ <gadget> "g1" set
+ { 300 300 } "g1" get set-rect-dim
+ <gadget> "g2" set
+ "g2" get "g1" get swap add-gadget drop
+ { 20 20 } "g2" get set-rect-loc
+ { 20 20 } "g2" get set-rect-dim
+ <gadget> "g3" set
+ "g3" get "g1" get swap add-gadget drop
+ { 100 100 } "g3" get set-rect-loc
+ { 20 20 } "g3" get set-rect-dim
+ [ t ] [ { 30 30 } "g2" get inside? ] unit-test
+ [ t ] [ { 30 30 } "g1" get (pick-up) "g2" get eq? ] unit-test
+ [ t ] [ { 30 30 } "g1" get pick-up "g2" get eq? ] unit-test
+ [ t ] [ { 110 110 } "g1" get pick-up "g3" get eq? ] unit-test
+ <gadget> "g4" set
+ "g4" get "g2" get swap add-gadget drop
+ { 5 5 } "g4" get set-rect-loc
+ { 1 1 } "g4" get set-rect-dim
+ [ t ] [ { 25 25 } "g1" get pick-up "g4" get eq? ] unit-test
+ TUPLE: mock-gadget < gadget graft-called ungraft-called ;
+ : <mock-gadget> ( -- gadget )
+     mock-gadget new-gadget 0 >>graft-called 0 >>ungraft-called ;
+ M: mock-gadget graft*
+     dup mock-gadget-graft-called 1+
+     swap set-mock-gadget-graft-called ;
+ M: mock-gadget ungraft*
+     dup mock-gadget-ungraft-called 1+
+     swap set-mock-gadget-ungraft-called ;
+ ! We can't print to output-stream here because that might be a pane
+ ! stream, and our graft-queue rebinding here would be captured
+ ! by code adding children to the pane...
+ [
+     <dlist> \ graft-queue [
+         [ ] [ <mock-gadget> dup queue-graft unqueue-graft ] unit-test
 -        [ t ] [ graft-queue dequeue-empty? ] unit-test
++        [ t ] [ graft-queue deque-empty? ] unit-test
+     ] with-variable
+     <dlist> \ graft-queue [
 -        [ f ] [ graft-queue dequeue-empty? ] unit-test
++        [ t ] [ graft-queue deque-empty? ] unit-test
+         <mock-gadget> "g" set
+         [ ] [ "g" get queue-graft ] unit-test
 -        [ t ] [ graft-queue dequeue-empty? ] unit-test
++        [ f ] [ graft-queue deque-empty? ] unit-test
+         [ { f t } ] [ "g" get gadget-graft-state ] unit-test
+         [ ] [ "g" get graft-later ] unit-test
+         [ { f t } ] [ "g" get gadget-graft-state ] unit-test
+         [ ] [ "g" get ungraft-later ] unit-test
+         [ { f f } ] [ "g" get gadget-graft-state ] unit-test
 -        [ t ] [ graft-queue dequeue-empty? ] unit-test
++        [ t ] [ graft-queue deque-empty? ] unit-test
+         [ ] [ "g" get ungraft-later ] unit-test
+         [ ] [ "g" get graft-later ] unit-test
+         [ ] [ notify-queued ] unit-test
+         [ { t t } ] [ "g" get gadget-graft-state ] unit-test
 -            [ ] [ graft-queue [ "x" print notify ] slurp-dequeue ] unit-test
++        [ t ] [ graft-queue deque-empty? ] unit-test
+         [ ] [ "g" get graft-later ] unit-test
+         [ 1 ] [ "g" get mock-gadget-graft-called ] unit-test
+         [ ] [ "g" get ungraft-later ] unit-test
+         [ { t f } ] [ "g" get gadget-graft-state ] unit-test
+         [ ] [ notify-queued ] unit-test
+         [ 1 ] [ "g" get mock-gadget-ungraft-called ] unit-test
+         [ { f f } ] [ "g" get gadget-graft-state ] unit-test
+     ] with-variable
+     : add-some-children
+         3 [
+             <mock-gadget> over <model> over set-gadget-model
+             dup "g" get swap add-gadget drop
+             swap 1+ number>string set
+         ] each ;
+     : status-flags
+         { "g" "1" "2" "3" } [ get gadget-graft-state ] map prune ;
+     : notify-combo ( ? ? -- )
+         nl "===== Combo: " write 2dup 2array . nl
+         <dlist> \ graft-queue [
+             <mock-gadget> "g" set
+             [ ] [ add-some-children ] unit-test
+             [ V{ { f f } } ] [ status-flags ] unit-test
+             [ ] [ "g" get graft ] unit-test
+             [ V{ { f t } } ] [ status-flags ] unit-test
+             dup [ [ ] [ notify-queued ] unit-test ] when
+             [ ] [ "g" get clear-gadget ] unit-test
+             [ [ 1 ] [ graft-queue dlist-length ] unit-test ] unless
+             [ [ ] [ notify-queued ] unit-test ] when
+             [ ] [ add-some-children ] unit-test
+             [ { f t } ] [ "1" get gadget-graft-state ] unit-test
+             [ { f t } ] [ "2" get gadget-graft-state ] unit-test
+             [ { f t } ] [ "3" get gadget-graft-state ] unit-test
++            [ ] [ graft-queue [ "x" print notify ] slurp-deque ] unit-test
+             [ ] [ notify-queued ] unit-test
+             [ V{ { t t } } ] [ status-flags ] unit-test
+         ] with-variable ;
+     { { f f } { f t } { t f } { t t } } [ notify-combo ] assoc-each
+ ] with-string-writer print
+ \ <gadget> must-infer
+ \ unparent must-infer
+ \ add-gadget must-infer
+ \ add-gadgets must-infer
+ \ clear-gadget must-infer
+ \ relayout must-infer
+ \ relayout-1 must-infer
+ \ pref-dim must-infer
index 0000000000000000000000000000000000000000,90eea255e8eb511ac93220ecfbd8179d5a1a8097..15a28801d603c69b9e83cf346b371e30631dda52
mode 000000,100755..100755
--- /dev/null
@@@ -1,0 -1,368 +1,368 @@@
 -       binary-search vectors dlists dequeues models threads
+ ! Copyright (C) 2005, 2008 Slava Pestov.
+ ! See http://factorcode.org/license.txt for BSD license.
+ USING: accessors arrays hashtables kernel models math namespaces
+        sequences quotations math.vectors combinators sorting
++       binary-search vectors dlists deques models threads
+        concurrency.flags math.order math.geometry.rect ;
+ IN: ui.gadgets
+ SYMBOL: ui-notify-flag
+ : notify-ui-thread ( -- ) ui-notify-flag get-global raise-flag ;
+ TUPLE: gadget < rect
+        pref-dim parent children orientation focus
+        visible? root? clipped? layout-state graft-state graft-node
+        interior boundary
+        model ;
+ M: gadget equal? 2drop f ;
+ M: gadget hashcode* drop gadget hashcode* ;
+ M: gadget model-changed 2drop ;
+ : gadget-child ( gadget -- child ) children>> first ;
+ : nth-gadget ( n gadget -- child ) children>> nth ;
+ : init-gadget ( gadget -- gadget )
+   init-rect
+   { 0 1 } >>orientation
+   t       >>visible?
+   { f f } >>graft-state ; inline
+ : new-gadget ( class -- gadget ) new init-gadget ; inline
+ : <gadget> ( -- gadget )
+     gadget new-gadget ;
+ : activate-control ( gadget -- )
+     dup model>> dup [
+         2dup add-connection
+         swap model-changed
+     ] [
+         2drop
+     ] if ;
+ : deactivate-control ( gadget -- )
+     dup model>> dup [ 2dup remove-connection ] when 2drop ;
+ : control-value ( control -- value )
+     model>> model-value ;
+ : set-control-value ( value control -- )
+     model>> set-model ;
+ : relative-loc ( fromgadget togadget -- loc )
+     2dup eq? [
+         2drop { 0 0 }
+     ] [
+         over rect-loc >r
+         >r parent>> r> relative-loc
+         r> v+
+     ] if ;
+ GENERIC: user-input* ( str gadget -- ? )
+ M: gadget user-input* 2drop t ;
+ GENERIC: children-on ( rect/point gadget -- seq )
+ M: gadget children-on nip children>> ;
+ : ((fast-children-on)) ( gadget dim axis -- <=> )
+     [ swap loc>> v- ] dip v. 0 <=> ;
+ : (fast-children-on) ( dim axis children -- i )
+     -rot [ ((fast-children-on)) ] 2curry search drop ;
+ : fast-children-on ( rect axis children -- from to )
+     [ [ rect-loc ] 2dip (fast-children-on) 0 or ]
+     [ [ rect-bounds v+ ] 2dip (fast-children-on) ?1+ ]
+     3bi ;
+ : inside? ( bounds gadget -- ? )
+     dup visible?>> [ intersects? ] [ 2drop f ] if ;
+ : (pick-up) ( point gadget -- gadget )
+     dupd children-on [ inside? ] with find-last nip ;
+ : pick-up ( point gadget -- child/f )
+     2dup (pick-up) dup
+     [ nip [ rect-loc v- ] keep pick-up ] [ rot 2drop ] if ;
+ : max-dim ( dims -- dim ) { 0 0 } [ vmax ] reduce ;
+ : dim-sum ( seq -- dim ) { 0 0 } [ v+ ] reduce ;
+ : orient ( gadget seq1 seq2 -- seq )
+     >r >r orientation>> r> r> [ pick set-axis ] 2map nip ;
+ : each-child ( gadget quot -- )
+     >r children>> r> each ; inline
+ ! Selection protocol
+ GENERIC: gadget-selection? ( gadget -- ? )
+ M: gadget gadget-selection? drop f ;
+ GENERIC: gadget-selection ( gadget -- string/f )
+ M: gadget gadget-selection drop f ;
+ ! Text protocol
+ GENERIC: gadget-text* ( gadget -- )
+ GENERIC: gadget-text-separator ( gadget -- str )
+ M: gadget gadget-text-separator
+     orientation>> { 0 1 } = "\n" "" ? ;
+ : gadget-seq-text ( seq gadget -- )
+     gadget-text-separator swap
+     [ dup % ] [ gadget-text* ] interleave drop ;
+ M: gadget gadget-text*
+     dup children>> swap gadget-seq-text ;
+ M: array gadget-text*
+     [ gadget-text* ] each ;
+ : gadget-text ( gadget -- string ) [ gadget-text* ] "" make ;
+ : invalidate ( gadget -- )
+     \ invalidate swap (>>layout-state) ;
+ : forget-pref-dim ( gadget -- ) f swap (>>pref-dim) ;
+ : layout-queue ( -- queue ) \ layout-queue get ;
+ : layout-later ( gadget -- )
+     #! When unit testing gadgets without the UI running, the
+     #! invalid queue is not initialized and we simply ignore
+     #! invalidation requests.
+     layout-queue [ push-front notify-ui-thread ] [ drop ] if* ;
+ DEFER: relayout
+ : invalidate* ( gadget -- )
+     \ invalidate* over (>>layout-state)
+     dup forget-pref-dim
+     dup gadget-root?
+     [ layout-later ] [ parent>> [ relayout ] when* ] if ;
+ : relayout ( gadget -- )
+     dup layout-state>> \ invalidate* eq?
+     [ drop ] [ invalidate* ] if ;
+ : relayout-1 ( gadget -- )
+     dup layout-state>>
+     [ drop ] [ dup invalidate layout-later ] if ;
+ : show-gadget ( gadget -- ) t swap (>>visible?) ;
+ : hide-gadget ( gadget -- ) f swap (>>visible?) ;
+ DEFER: in-layout?
+ : do-invalidate ( gadget -- gadget )
+   in-layout? get [ dup invalidate ] [ dup invalidate* ] if ;
+ M: gadget (>>dim) ( dim gadget -- )
+    2dup dim>> =
+      [ 2drop ]
+      [ tuck call-next-method do-invalidate drop ]
+    if ;
+ GENERIC: pref-dim* ( gadget -- dim )
+ : ?set-gadget-pref-dim ( dim gadget -- )
+     dup layout-state>>
+     [ 2drop ] [ (>>pref-dim) ] if ;
+ : pref-dim ( gadget -- dim )
+     dup pref-dim>> [ ] [
+         [ pref-dim* dup ] keep ?set-gadget-pref-dim
+     ] ?if ;
+ : pref-dims ( gadgets -- seq ) [ pref-dim ] map ;
+ M: gadget pref-dim* rect-dim ;
+ GENERIC: layout* ( gadget -- )
+ M: gadget layout* drop ;
+ : prefer ( gadget -- ) dup pref-dim swap (>>dim) ;
+ : validate ( gadget -- ) f swap (>>layout-state) ;
+ : layout ( gadget -- )
+     dup layout-state>> [
+         dup validate
+         dup layout*
+         dup [ layout ] each-child
+     ] when drop ;
+ : graft-queue ( -- dlist ) \ graft-queue get ;
+ : unqueue-graft ( gadget -- )
+     [ graft-node>> graft-queue delete-node ]
+     [ [ first { t t } { f f } ? ] change-graft-state drop ] bi ;
+ : (queue-graft) ( gadget flags -- )
+     >>graft-state
+     dup graft-queue push-front* >>graft-node drop
+     notify-ui-thread ;
+ : queue-graft ( gadget -- )
+     { f t } (queue-graft) ;
+ : queue-ungraft ( gadget -- )
+     { t f } (queue-graft) ;
+ : graft-later ( gadget -- )
+     dup graft-state>> {
+         { { f t } [ drop ] }
+         { { t t } [ drop ] }
+         { { t f } [ unqueue-graft ] }
+         { { f f } [ queue-graft ] }
+     } case ;
+ : ungraft-later ( gadget -- )
+     dup graft-state>> {
+         { { f f } [ drop ] }
+         { { t f } [ drop ] }
+         { { f t } [ unqueue-graft ] }
+         { { t t } [ queue-ungraft ] }
+     } case ;
+ GENERIC: graft* ( gadget -- )
+ M: gadget graft* drop ;
+ : graft ( gadget -- )
+     dup graft-later [ graft ] each-child ;
+ GENERIC: ungraft* ( gadget -- )
+ M: gadget ungraft* drop ;
+ : ungraft ( gadget -- )
+     dup [ ungraft ] each-child ungraft-later ;
+ : (unparent) ( gadget -- )
+     dup ungraft
+     dup forget-pref-dim
+     f swap (>>parent) ;
+ : unfocus-gadget ( child gadget -- )
+     tuck focus>> eq?
+     [ f swap (>>focus) ] [ drop ] if ;
+ SYMBOL: in-layout?
+ : not-in-layout ( -- )
+     in-layout? get
+     [ "Cannot add/remove gadgets in layout*" throw ] when ;
+ : unparent ( gadget -- )
+     not-in-layout
+     [
+         dup parent>> dup [
+             over (unparent)
+             [ unfocus-gadget ] 2keep
+             [ children>> delete ] keep
+             relayout
+         ] [
+             2drop
+         ] if
+     ] when* ;
+ : (clear-gadget) ( gadget -- )
+     dup [ (unparent) ] each-child
+     f over (>>focus)
+     f swap (>>children) ;
+ : clear-gadget ( gadget -- )
+     not-in-layout
+     dup (clear-gadget) relayout ;
+ : ((add-gadget)) ( parent child -- parent )
+     over children>> ?push >>children ;
+ : (add-gadget) ( parent child -- parent )
+     dup unparent
+     over >>parent
+     tuck ((add-gadget))
+     tuck graft-state>> second
+         [ graft ]
+         [ drop  ]
+     if ;
+ : add-gadget ( parent child -- parent )
+     not-in-layout
+     (add-gadget)
+     dup relayout ;
+   
+ : add-gadgets ( parent children -- parent )
+     not-in-layout
+     [ (add-gadget) ] each
+     dup relayout ;
+ : parents ( gadget -- seq )
+     [ parent>> ] follow ;
+ : each-parent ( gadget quot -- ? )
+     >r parents r> all? ; inline
+ : find-parent ( gadget quot -- parent )
+     >r parents r> find nip ; inline
+ : screen-loc ( gadget -- loc )
+     parents { 0 0 } [ rect-loc v+ ] reduce ;
+ : (screen-rect) ( gadget -- loc ext )
+     dup parent>> [
+         >r rect-extent r> (screen-rect)
+         >r tuck v+ r> vmin >r v+ r>
+     ] [
+         rect-extent
+     ] if* ;
+ : screen-rect ( gadget -- rect )
+     (screen-rect) <extent-rect> ;
+ : child? ( parent child -- ? )
+     {
+         { [ 2dup eq? ] [ 2drop t ] }
+         { [ dup not ] [ 2drop f ] }
+         [ parent>> child? ]
+     } cond ;
+ GENERIC: focusable-child* ( gadget -- child/t )
+ M: gadget focusable-child* drop t ;
+ : focusable-child ( gadget -- child )
+     dup focusable-child*
+     dup t eq? [ drop ] [ nip focusable-child ] if ;
+ GENERIC: request-focus-on ( child gadget -- )
+ M: gadget request-focus-on parent>> request-focus-on ;
+ M: f request-focus-on 2drop ;
+ : request-focus ( gadget -- )
+     [ focusable-child ] keep request-focus-on ;
+ : focus-path ( world -- seq )
+     [ focus>> ] follow ;
+ ! Deprecated
+ : construct-gadget ( class -- tuple )
+     >r <gadget> { set-delegate } r> construct ; inline
index 0000000000000000000000000000000000000000,29d1d166425bdc5dc20e1dc0939d2d56232e0f3c..0e00627cb980c7c24b0bdf7b2e54eaa9df32c73e
mode 000000,100755..100755
--- /dev/null
@@@ -1,0 -1,221 +1,221 @@@
 -prettyprint dlists dequeues sequences threads sequences words
+ ! Copyright (C) 2006, 2007 Slava Pestov.
+ ! See http://factorcode.org/license.txt for BSD license.
+ USING: arrays assocs io kernel math models namespaces
 -        { [ graft-queue dequeue-empty? not ] [ t ] }
++prettyprint dlists deques sequences threads sequences words
+ debugger ui.gadgets ui.gadgets.worlds ui.gadgets.tracks
+ ui.gestures ui.backend ui.render continuations init combinators
+ hashtables concurrency.flags sets accessors ;
+ IN: ui
+ ! Assoc mapping aliens to gadgets
+ SYMBOL: windows
+ SYMBOL: stop-after-last-window?
+ : event-loop? ( -- ? )
+     {
+         { [ stop-after-last-window? get not ] [ t ] }
 -        ] slurp-dequeue
++        { [ graft-queue deque-empty? not ] [ t ] }
+         { [ windows get-global empty? not ] [ t ] }
+         [ f ]
+     } cond ;
+ : event-loop ( -- ) [ event-loop? ] [ do-events ] [ ] while ;
+ : window ( handle -- world ) windows get-global at ;
+ : window-focus ( handle -- gadget ) window world-focus ;
+ : register-window ( world handle -- )
+     #! Add the new window just below the topmost window. Why?
+     #! So that if the new window doesn't actually receive focus
+     #! (eg, we're using focus follows mouse and the mouse is not
+     #! in the new window when it appears) Factor doesn't get
+     #! confused and send workspace operations to the new window,
+     #! etc.
+     swap 2array windows get-global push
+     windows get-global dup length 1 >
+     [ [ length 1- dup 1- ] keep exchange ] [ drop ] if ;
+ : unregister-window ( handle -- )
+     windows global [ [ first = not ] with filter ] change-at ;
+ : raised-window ( world -- )
+     windows get-global
+     [ [ second eq? ] with find drop ] keep
+     [ nth ] [ delete-nth ] [ nip ] 2tri push ;
+ : focus-gestures ( new old -- )
+     drop-prefix <reversed>
+     T{ lose-focus } swap each-gesture
+     T{ gain-focus } swap each-gesture ;
+ : focus-world ( world -- )
+     t over set-world-focused?
+     dup raised-window
+     focus-path f focus-gestures ;
+ : unfocus-world ( world -- )
+     f over set-world-focused?
+     focus-path f swap focus-gestures ;
+ M: world graft*
+     dup (open-window)
+     dup world-title over set-title
+     request-focus ;
+ : reset-world ( world -- )
+     #! This is used when a window is being closed, but also
+     #! when restoring saved worlds on image startup.
+     dup world-fonts clear-assoc
+     dup unfocus-world
+     f swap set-world-handle ;
+ M: world ungraft*
+     dup free-fonts
+     dup hand-clicked close-global
+     dup hand-gadget close-global
+     dup world-handle (close-window)
+     reset-world ;
+ : find-window ( quot -- world )
+     windows get values
+     [ gadget-child swap call ] with find-last nip ; inline
+ SYMBOL: ui-hook
+ : init-ui ( -- )
+     <dlist> \ graft-queue set-global
+     <dlist> \ layout-queue set-global
+     V{ } clone windows set-global ;
+ : restore-gadget-later ( gadget -- )
+     dup gadget-graft-state {
+         { { f f } [ ] }
+         { { f t } [ ] }
+         { { t t } [
+             { f f } over set-gadget-graft-state
+         ] }
+         { { t f } [
+             dup unqueue-graft
+             { f f } over set-gadget-graft-state
+         ] }
+     } case graft-later ;
+ : restore-gadget ( gadget -- )
+     dup restore-gadget-later
+     gadget-children [ restore-gadget ] each ;
+ : restore-world ( world -- )
+     dup reset-world restore-gadget ;
+ : restore-windows ( -- )
+     windows get [ values ] keep delete-all
+     [ restore-world ] each
+     forget-rollover ;
+ : restore-windows? ( -- ? )
+     windows get empty? not ;
+ : update-hand ( world -- )
+     dup hand-world get-global eq?
+     [ hand-loc get-global swap move-hand ] [ drop ] if ;
+ : layout-queued ( -- seq )
+     [
+         in-layout? on
+         layout-queue [
+             dup layout find-world [ , ] when*
 -    graft-queue [ notify ] slurp-dequeue ;
++        ] slurp-deque
+     ] { } make prune ;
+ : redraw-worlds ( seq -- )
+     [ dup update-hand draw-world ] each ;
+ : notify ( gadget -- )
+     dup gadget-graft-state
+     dup first { f f } { t t } ?
+     pick set-gadget-graft-state {
+         { { f t } [ dup activate-control graft* ] }
+         { { t f } [ dup deactivate-control ungraft* ] }
+     } case ;
+ : notify-queued ( -- )
++    graft-queue [ notify ] slurp-deque ;
+ : update-ui ( -- )
+     [ notify-queued layout-queued redraw-worlds ] assert-depth ;
+ : ui-wait ( -- )
+     10 sleep ;
+ : ui-try ( quot -- ) [ ui-error ] recover ;
+ SYMBOL: ui-thread
+ : ui-running ( quot -- )
+     t \ ui-running set-global
+     [ f \ ui-running set-global ] [ ] cleanup ; inline
+ : ui-running? ( -- ? )
+     \ ui-running get-global ;
+ : update-ui-loop ( -- )
+     ui-running? ui-thread get-global self eq? and [
+         ui-notify-flag get lower-flag
+         [ update-ui ] ui-try
+         update-ui-loop
+     ] when ;
+ : start-ui-thread ( -- )
+     [ self ui-thread set-global update-ui-loop ]
+     "UI update" spawn drop ;
+ : open-world-window ( world -- )
+     dup pref-dim over (>>dim) dup relayout graft ;
+ : open-window ( gadget title -- )
+     f <world> open-world-window ;
+ : set-fullscreen? ( ? gadget -- )
+     find-world set-fullscreen* ;
+ : fullscreen? ( gadget -- ? )
+     find-world fullscreen* ;
+ : raise-window ( gadget -- )
+     find-world raise-window* ;
+ HOOK: close-window ui-backend ( gadget -- )
+ M: object close-window
+     find-world [ ungraft ] when* ;
+ : start-ui ( -- )
+     restore-windows? [
+         restore-windows
+     ] [
+         init-ui ui-hook get call
+     ] if
+     notify-ui-thread start-ui-thread ;
+ [
+     f \ ui-running set-global
+     <flag> ui-notify-flag set-global
+ ] "ui" add-init-hook
+ HOOK: ui ui-backend ( -- )
+ MAIN: ui
+ : with-ui ( quot -- )
+     ui-running? [
+         call
+     ] [
+         f windows set-global
+         [
+             ui-hook set
+             stop-after-last-window? on
+             ui
+         ] with-scope
+     ] if ;