PreviousUpNext

15.4.1497  src/lib/x-kit/widget/old/layout/line-of-widgets.pkg

## line-of-widgets.pkg
#
# Lay out widgets in a line or column.
#
# The core layout algorithm is actually in
#
#     src/lib/x-kit/widget/old/layout/lay-out-linearly.pkg

# Compiled by:
#     src/lib/x-kit/widget/xkit-widget.sublib



###       "We hang the petty thieves and
###        appoint the great ones to public office."
###
###                 -- Aesop (circa 620 - 560 BCE)


# Stough + DeBoer in "The Future of eXene",
#
#     http://mythryl.org/pub/exene/future.pdf
#
# note:
#     "Although very useful, the existing [line-of-widgets]
#      widget produces some odd results in some cases. E.g.,
#      putting inflexible glue around a flexible widget
#      results in an overall widget that's inflexible."
# They mention a project to write a new layout widget
# from scratch.


stipulate
    include package   threadkit;                        # threadkit             is from   src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg
    #
    package g2d=  geometry2d;                           # geometry2d            is from   src/lib/std/2d/geometry2d.pkg
    #
    package xc =  xclient;                              # xclient               is from   src/lib/x-kit/xclient/xclient.pkg
    #
    package bg =  background;                           # background            is from   src/lib/x-kit/widget/old/wrapper/background.pkg
    package li =  list_indexing;                        # list_indexing         is from   src/lib/x-kit/widget/old/lib/list-indexing.pkg
    package lo =  lay_out_linearly;                     # lay_out_linearly      is from   src/lib/x-kit/widget/old/layout/lay-out-linearly.pkg
    package mr =  xevent_mail_router;                   # xevent_mail_router    is from   src/lib/x-kit/widget/old/basic/xevent-mail-router.pkg
    package wg =  widget;                               # widget                is from   src/lib/x-kit/widget/old/basic/widget.pkg
    package wt =  widget_types;                         # widget_types          is from   src/lib/x-kit/widget/old/basic/widget-types.pkg
herein

    package   line_of_widgets
    : (weak)  Line_Of_Widgets                           # Line_Of_Widgets       is from   src/lib/x-kit/widget/old/layout/line-of-widgets.api
    {

    /* DEBUG
        package f = format
        fun rectToString (geometry2d::BOX { x, y, wid, ht } ) = f::format "(%d,%d,%d,%d)" (map f::INT [x, y, wid, ht])
        fun print s = (file::write (file::stderr, s); file::flush file::stderr)
       END DEBUG
    */

        exception BAD_INDEX =  li::BAD_INDEX;

        Layout_Tree
          #
          = HZ_TOP     List( Layout_Tree )
          | HZ_CENTER  List( Layout_Tree )
          | HZ_BOTTOM  List( Layout_Tree )
          #
          | VT_LEFT    List( Layout_Tree )
          | VT_CENTER  List( Layout_Tree )
          | VT_RIGHT   List( Layout_Tree )
          #
          | WIDGET     wg::Widget
          #
          | SPACER { min_size:    Int,
                     best_size:  Int,
                     max_size:    Null_Or( Int )
                   }
          ;



        Reply_Mail = ERROR  Exception | OKAY;

        Plea_Mail
          = GET_SIZE 
          | DO_REALIZE  {
              kidplug:      xc::Kidplug,
              window:       xc::Window,
              window_size:  g2d::Size
            }
          | INSERT   (Int, List( Layout_Tree ))
          | DELETE   List( Int )
       #  | Replace of (Int * Null_Or( Int ) * List( Layout_Tree ) ) 
          | MAP      (Bool, List( Int ))
          ;

        Line_Of_Widgets
            =
            LINE_OF_WIDGETS
              {
                plea_slot:   Mailslot( Plea_Mail  ),
                reply_slot:  Mailslot( Reply_Mail ),
                widget:      wg::Widget
              };

        Box_Rep
            =
            { widget:     wg::Widget,
              window:     xc::Window,

              box:        g2d::Box,
              from_kid':  Mailop( xc::Mail_To_Mom )
            };

        Layout_Rep
            =
            { box:    g2d::Box,
              clist:  List( (Bool, lo::Box_Item, List( Box_Rep )) )
            };

        loose_preference
            =
            wg::INT_PREFERENCE { start_at=>0, step_by=>1, min_steps=>0, best_steps=>0, max_steps=>NULL };

        fun make_vertical_glue { min_size, best_size, max_size }
            =
            lo::GEOMETRY {
              col_preference =>  loose_preference,
              row_preference =>  wg::INT_PREFERENCE { start_at=>0, step_by=>1, min_steps=>min_size, best_steps=>best_size, max_steps=>max_size }
            };

        fun make_horizontal_glue { min_size, best_size, max_size }
            =
            lo::GEOMETRY
              { row_preference =>  loose_preference,
                col_preference =>  wg::INT_PREFERENCE { start_at=>0, step_by=>1, min_steps=>min_size, best_steps=>best_size, max_steps=>max_size }
              };

        fun make_item glue_fn box
            =
            (b, *wl)
            where 

                my wl:  Ref( List( wg::Widget ) )
                     =  REF [];

                fun convert (HZ_TOP boxes)    =>  lo::HB (wt::VTOP, map hcvt boxes);
                    convert (HZ_CENTER boxes) =>  lo::HB (wt::VCENTER, map hcvt boxes);
                    convert (HZ_BOTTOM boxes) =>  lo::HB (wt::VBOTTOM, map hcvt boxes);

                    convert (VT_LEFT boxes)   =>  lo::NAMED_VALUE (wt::VTOP, map vcvt boxes);
                    convert (VT_CENTER boxes) =>  lo::NAMED_VALUE (wt::VCENTER, map vcvt boxes);
                    convert (VT_RIGHT boxes)  =>  lo::NAMED_VALUE (wt::VBOTTOM, map vcvt boxes);

                    convert (SPACER arg)        =>  glue_fn arg;
                    convert (WIDGET w)          =>  { wl := w ! *wl; lo::WIDGET w;};
                end 

                also
                fun hcvt (WIDGET w)     =>  { wl := w ! *wl; lo::WIDGET w;};
                    hcvt (SPACER arg) =>  make_horizontal_glue arg;
                    hcvt arg          =>  convert arg;
                end 

                also
                fun vcvt (WIDGET w)   =>  { wl := w ! *wl; lo::WIDGET w;};
                    vcvt (SPACER arg) =>  make_vertical_glue arg;
                    vcvt arg        =>  convert arg;
                end;

                b = convert box;
            end;

        fun gen_fns (HZ_TOP    boxes) =>   (\\ cl = lo::HB (wt::VTOP, cl),             make_item make_horizontal_glue, boxes);
            gen_fns (HZ_CENTER boxes) =>   (\\ cl = lo::HB (wt::VCENTER, cl),          make_item make_horizontal_glue, boxes);
            gen_fns (HZ_BOTTOM boxes) =>   (\\ cl = lo::HB (wt::VBOTTOM, cl),          make_item make_horizontal_glue, boxes);

            gen_fns (VT_LEFT   boxes) =>   (\\ cl = lo::NAMED_VALUE (wt::VTOP, cl),    make_item make_vertical_glue, boxes);
            gen_fns (VT_CENTER boxes) =>   (\\ cl = lo::NAMED_VALUE (wt::VCENTER, cl), make_item make_vertical_glue, boxes);
            gen_fns (VT_RIGHT  boxes) =>   (\\ cl = lo::NAMED_VALUE (wt::VBOTTOM, cl), make_item make_vertical_glue, boxes);

            gen_fns _ => raise exception lib_base::IMPOSSIBLE "box::genFns";
        end;

        # Compute bounds for box layout.
        #
        layout_size =  lo::compute_size;

        fun cloop from_kid' ()
            =
            {   block_until_mailop_fires  from_kid';
                cloop      from_kid' ();
            };

        fun cleanup ( { window, from_kid', ... }: Box_Rep)
            =
            {   xc::destroy_window window;
                make_thread "line_of_widgets cleanup" (cloop from_kid');
                ();
            };

        fun mapfn make_mapped
            =
            {   mf =    case make_mapped   
                            #   
                            TRUE  =>  xc::show_window;
                            FALSE =>  xc::hide_window;
                        esac;

                fun mapf (r:  Box_Rep)
                    =
                    mf r.window;

                \\ (item as (is_mapped, box, replicate))
                    =
                    if (is_mapped == make_mapped)
                        #
                        item;
                    else
                        apply mapf replicate;
                        (make_mapped, box, replicate);
                    fi;
              };

        fun any_visible [] => FALSE;
            any_visible ((TRUE, _, _) ! _) => TRUE;
            any_visible (_ ! rest) => any_visible rest;
        end;

        fun make_co cl
            =
            {   fun f' ( { window, from_kid', ... } : Box_Rep, l)
                    =
                    (from_kid' ==>  {.  (window, #mailop); })   !   l;

                fun f ((_, _, replicate), l)
                    =
                    list::fold_forward f' l replicate;

                cat_mailops (list::fold_forward f [] cl);
            };

        fun preferred_size_box w
            =
            g2d::box::make (g2d::point::zero, wg::preferred_size w);


        fun update_box (w as { box, window, widget, from_kid' }, nrect)
            =
            if   (box == nrect)

                 w;
            else
    /* DEBUG
                print (implode["update box: ", rectToString nrect, "\n"]);
       END DEBUG */

                xc::move_and_resize_window window nrect;
                { box=>nrect, window, widget, from_kid' };

            fi;

        fun make_line_of_widgets  root_window  (w as WIDGET  _) => make_line_of_widgets  root_window  (HZ_CENTER [w]);
            make_line_of_widgets  root_window  (g as SPACER  _) => make_line_of_widgets  root_window  (HZ_CENTER [g]);

            make_line_of_widgets  root_window  boxes
                =>
                {   plea_slot  =  make_mailslot ();
                    reply_slot =  make_mailslot ();
                    size_slot  =  make_mailslot ();

                    (gen_fns boxes)
                        ->
                        (cvt_fn, item_fn, clist);

                    screen =  wg::screen_of  root_window;

                    fun getvis l
                        =
                        cvt_fn
                            (li::find

                                \\ (_, (TRUE,  v, _)) =>  THE v;
                                   (_, (FALSE, _, _)) =>  NULL;
                                end

                                l
                            );

                    fun realize_box
                        { kidplug => kidplug as xc::KIDPLUG { to_mom=>myco, ... }, window, window_size }
                        ctree
                        =
                        {   (xc::make_widget_cable ())
                                ->
                                { kidplug => my_kidplug, momplug => my_momplug };
                                

                            router = mr::make_xevent_mail_router (kidplug, my_momplug, []);

                            (xc::ignore_mouse_and_keyboard  my_kidplug)
                                ->
                                xc::KIDPLUG { from_other', ... };

                            fun get_vis (me:  Layout_Rep)
                                =
                                getvis me.clist;

                            box = g2d::box::make (g2d::point::zero, window_size);

                            places = #2 (lo::compute_layout (box, getvis ctree));

                            fun reposition (clist, rlist)
                                =
                                do_repos (reverse clist, rlist, [])
                                where 

                                    fun repos ([], rl, l) => (reverse l, rl);
                                        repos (_,  [], _) => raise exception lib_base::IMPOSSIBLE "box::macroExpandBox";

                                        repos (w ! wl, (_, r) ! rl, l)
                                            =>
                                            repos (wl, rl, (update_box (w, r)) ! l);
                                    end;

                                    fun do_repos ([], _, cl) => cl;

                                        do_repos ((rep as (FALSE, _,  _)) ! rest, rl, cl) => do_repos (rest, rl, rep ! cl);
                                        do_repos ((rep as (TRUE,  b, [])) ! rest, rl, cl) => do_repos (rest, rl, rep ! cl);

                                        do_repos ((TRUE, b, wl) ! rest, rl, cl)
                                            =>
                                            {   my (replicate, rl')
                                                    =
                                                    repos (wl, rl,[]);

                                                do_repos (rest, rl', (TRUE, b, replicate) ! cl);
                                            };
                                    end;

                                end;


                            fun zombie (me:  Layout_Rep)
                                =
                                loop ()
                                where 

                                    to_child' =  make_co  me.clist;
                                    bounds    =  wg::make_tight_size_preference (1, 1);

                                    fun do_plea GET_SIZE =>  put_in_mailslot (size_slot, bounds);
                                        do_plea _        =>  ();
                                    end;

                                    fun loop ()
                                        =
                                        loop (

                                            do_one_mailop [
                                                take_from_mailslot' plea_slot ==>  do_plea,
                                                from_other'     ==>  (\\ _ = ()),
                                                to_child'       ==>  (\\ _ = ())
                                            ]
                                        );
                                end;

                            fun make_box_rep  rectfn  widget
                                =
                                { 
                                    box  =  rectfn  widget;

                                    window_size =  g2d::box::size  box;

                                    my { kidplug, momplug => momplug as xc::MOMPLUG { from_kid', ... } }
                                        =
                                        xc::make_widget_cable ();

                                    window =  wg::make_child_window (window, box, wg::args_of widget);

                                    from_kid' =  wg::wrap_queue  from_kid';

                                    rep = { widget,
                                            window,
                                            box,
                                            from_kid'
                                          };

                                    mr::add_child router (window, momplug);

                                    wg::realize_widget widget { kidplug, window, window_size };

                                    rep;
                                };

                            fun init_fn (clist, rlist)
                                =
                                init (reverse clist, rlist, [])
                                where
                                    fun make_box ([], rl, l) => (reverse l, rl);
                                        make_box (_,[], _) => raise exception lib_base::IMPOSSIBLE "box::initFn";
                                        make_box (w ! wl, (_, r) ! rl, l) => make_box (wl, rl, (make_box_rep (\\ _ = r) w) ! l);
                                    end;

                                    fun init ([], _, cl)
                                            =>
                                            cl;

                                        init ((ison, b,[]) ! rest, rl, cl)
                                            =>
                                            init (rest, rl, (ison, b,[]) ! cl);

                                        init ((FALSE, b, wl) ! rest, rl, cl)
                                            => 
                                            init (rest, rl, (FALSE, b, map (make_box_rep preferred_size_box) wl) ! cl);

                                        init ((TRUE, b, wl) ! rest, rl, cl)
                                            =>
                                            {   my (replicate, rl')
                                                    =
                                                    make_box (wl, rl,[]);

                                                apply (\\ ( { window, ... }: Box_Rep) =  xc::show_window window)
                                                      replicate;

                                                init (rest, rl', (TRUE, b, replicate) ! cl); 
                                            };
                                    end;
                                end;

                            fun insert_fn  box
                                =
                                {   (item_fn  box)
                                        ->
                                        (b, wl);

                                    (FALSE, b, map (make_box_rep preferred_size_box) wl);
                                };

                            fun resize (me:  Layout_Rep)
                                =
                                {
                                    (lo::compute_layout (me.box, get_vis me))
                                        ->
                                        (fits, nlist);

                                        
              /* DEBUG
                                    print (implode["resize: box = ", rectToString me.box, "\n"])
                                    print (f::format "resize: fits = %B\n        nlist =\n" [f::BOOL fits])
                                    apply (\\ (_, r) => print (implode["        ", rectToString r, "\n"])) nlist
                 END DEBUG */
                                    clist' = reposition (me.clist, nlist);
                                    me' = { box => me.box, clist => clist'};

                                    if (not fits)
                                        #
                                        block_until_mailop_fires (myco xc::REQ_RESIZE);
                                    fi;

                                    me';
                                };

                            fun do_to_child (me, (_, xc::REQ_RESIZE)) => resize me;
                                do_to_child (me, (_, xc::REQ_DESTRUCTION  )) => me;               #  FIX XXX BUGGO FIXME
                            end;

                            fun do_mom (me:  Layout_Rep, xc::ETC_RESIZE r)
                                    =>
                                    {   nrect =  g2d::box::make (g2d::point::zero, g2d::box::size r);
                                         #
                                        nlist =  #2 (lo::compute_layout (nrect, get_vis me));

                                        { box  => nrect,
                                          clist => reposition (me.clist, nlist)
                                        };
                                    };

                                do_mom (me, xc::ETC_CHILD_DEATH child)
                                    =>
                                    {   mr::del_child router child;
                                        me;
                                    };

                                do_mom (me, xc::ETC_OWN_DEATH) =>  zombie me;
                                do_mom (me, _                ) =>  me;
                            end;

                            fun do_plea (me, plea)
                                =
                                case plea
                                    #
                                    GET_SIZE
                                        =>
                                        {   put_in_mailslot (size_slot, layout_size (get_vis me));
                                            me;
                                        };

                                    INSERT (index, bl)
                                        =>
                                        {   bl' = map insert_fn bl; 
                                            ct' = li::set (me.clist, index, bl');

                                            put_in_mailslot (reply_slot, OKAY);
                                            main { box=> me.box, clist=>ct'};
                                        }
                                        except e = {   put_in_mailslot (reply_slot, ERROR e);
                                                       me;
                                                   };

                                    DELETE indices
                                        =>
                                        {   (li::delete (me.clist, li::check_sort indices))
                                                ->
                                                (ct', dl);

                                            me' = { box=> me.box, clist=>ct'};

                                            apply (\\ (_, _, replicate) = apply cleanup replicate)
                                                  dl;

                                            put_in_mailslot (reply_slot, OKAY);

                                            if (any_visible dl)  main (resize me');
                                            else                 main me';
                                            fi;
                                       }
                                       except e = { put_in_mailslot (reply_slot, ERROR e); me;};

                                    MAP (mapped, indices)
                                        =>
                                        {   ct' =  li::do_map (me.clist, mapfn mapped, li::check_sort indices);
                                            #
                                            put_in_mailslot (reply_slot, OKAY);

                                            resize { box=> me.box, clist=>ct'};
                                        }
                                        except e = {   put_in_mailslot (reply_slot, ERROR e);
                                                       me;
                                                   };

                                    DO_REALIZE _ => me;
                                esac

                            also
                            fun main me
                                =
                                loop me
                                where
                                    to_child' = make_co me.clist;
                                    #
                                    fun loop me
                                        =
                                        loop (
                                            do_one_mailop [
                                                #
                                                to_child'   ==>   (\\ mail =  do_to_child (me, mail)),
                                                from_other' ==>   (\\ mail =  do_mom (me, xc::get_contents_of_envelope  mail)),

                                                take_from_mailslot'  plea_slot
                                                    ==>
                                                    (\\ msg =  do_plea (me, msg))
                                            ]
                                        );
                                end;

                              main { box, clist => init_fn (ctree, places) };

                              ();
                          };

                    fun init_item_fn  vis  b
                        =
                        {   (item_fn  b) ->  (box, wl);
                            #
                            (vis, box, wl);
                        };

                    fun init_loop  ct
                        = 
                        case (take_from_mailslot  plea_slot)
                            #
                            GET_SIZE
                                =>
                                {   put_in_mailslot (size_slot, layout_size (getvis ct));
                                    init_loop ct;
                                };

                            DO_REALIZE  arg
                                =>
                                realize_box  arg  ct;

                            INSERT (index, bl)
                                =>
                                {   ct' = li::set (ct, index, map (init_item_fn FALSE) bl);
                                    #
                                    put_in_mailslot (reply_slot, OKAY);
                                    init_loop ct';
                                }
                                except e = {   put_in_mailslot (reply_slot, ERROR e);
                                               init_loop ct;
                                           };

                            DELETE indices
                                =>
                                {   ct' = #1 (li::delete (ct, li::check_sort indices));
                                    #
                                    put_in_mailslot (reply_slot, OKAY);
                                    init_loop ct';
                                }
                                except e = {   put_in_mailslot (reply_slot, ERROR e);
                                               init_loop ct;
                                           };

                            MAP (mapped, indices)
                                =>
                                {   ct' =   li::do_map
                                              ( ct,
                                                \\ (_, b, wl) = (mapped, b, wl),
                                                li::check_sort indices
                                              );

                                    put_in_mailslot (reply_slot, OKAY);
                                    init_loop ct';
                                }
                                except e = {   put_in_mailslot (reply_slot, ERROR e);
                                               init_loop ct;
                                           };
                        esac;

                    make_thread "line_of_widgets init" {.
                        #
                        init_loop (map (init_item_fn TRUE) clist);
                    };

                    LINE_OF_WIDGETS   { plea_slot,
                                        reply_slot,
                                        widget =>   wg::make_widget { root_window,
                                                                      args                      =>  (\\ ()  = {   background => NULL }),
                                                                      size_preference_thunk_of  =>  (\\ ()  = {   put_in_mailslot (plea_slot, GET_SIZE);   take_from_mailslot size_slot;   }),
                                                                      realize_widget            =>  (\\ arg =     put_in_mailslot (plea_slot, DO_REALIZE arg))
                                                                    }
                                      };
                };
        end;            #  fun make_line_of_widgets 

        fun line_of_widgets (root_window, view, _) box
            =
            {   (make_line_of_widgets  root_window  box)
                    ->
                    LINE_OF_WIDGETS { widget, plea_slot, reply_slot };

                widget =  bg::background  (root_window, view,[])  widget;

                LINE_OF_WIDGETS { widget, plea_slot, reply_slot };
            };

        fun as_widget (LINE_OF_WIDGETS r)
            =
            r.widget;

        stipulate

            fun command wrapfn (LINE_OF_WIDGETS { plea_slot, reply_slot, ... } )
                =
                \\ arg
                    =
                    {   put_in_mailslot  (plea_slot,  wrapfn  arg);
                        #
                        case (take_from_mailslot  reply_slot)
                            #
                            OKAY    => ();
                            ERROR e => raise exception e;
                        esac;
                    };
        herein

            insert = command INSERT;

            fun append box (i, bl)
                =
                insert box (i+1, bl);

            delete = command DELETE;
            #  replace = command REPLACE 

            show = command  (\\ l =  MAP (TRUE,  l));
            hide = command  (\\ l =  MAP (FALSE, l));

        end;
    };          # package box 

end;


Comments and suggestions to: bugs@mythryl.org

PreviousUpNext