PreviousUpNext

15.4.1378  src/lib/x-kit/tut/plaid/plaid-app.pkg

## plaid-app.pkg
#
# This app draws screensaver-like rectangular patterns in a window.
#
# It has two modes, "idle" and "active".
#
# In "idle" mode it uses normal buffered xd::fill_boxes XOR-draw
# commands to fill the window with a rectangular pattern and stops.
#
# In "active" mode is uses unbuffered xd::fill_boxes XOR-draw
# commands to draw a continuously changing (another draw every
# 100ms) pattern.
#
# Clicking any mouse button anywhere in the window toggles
# the app between these two modes.
#
# This app uses the "unbuffered" window mode designed to support
# rubber-banding and also the XOR drawing mode used by rubber-banding;
# this makes it a useful unit test for rubber-banding functionality.
#
# One way to run this app from the base-directory commandline is:
#
#     linux% my
#     eval: make "src/lib/x-kit/tut/plaid/plaid-app.lib";
#     eval: plaid_app::do_it ();

# Compiled by:
#     src/lib/x-kit/tut/plaid/plaid-app.lib

stipulate
    include package   threadkit;                        # threadkit                             is from   src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg
    #
    package fil =  file__premicrothread;                # file__premicrothread                  is from   src/lib/std/src/posix/file--premicrothread.pkg
    package mps =  microthread_preemptive_scheduler;    # microthread_preemptive_scheduler      is from   src/lib/src/lib/thread-kit/src/core-thread-kit/microthread-preemptive-scheduler.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 rx  =  run_in_x_window_old;                 # run_in_x_window_old                   is from   src/lib/x-kit/widget/old/lib/run-in-x-window-old.pkg
    #
    package top =  hostwindow;                          # hostwindow                            is from   src/lib/x-kit/widget/old/basic/hostwindow.pkg
    package wg  =  widget;                              # widget                                is from   src/lib/x-kit/widget/old/basic/widget.pkg
    package wa  =  widget_attribute_old;                # widget_attribute_old                  is from   src/lib/x-kit/widget/old/lib/widget-attribute-old.pkg
    package wy  =  widget_style_old;                    # widget_style_old                      is from   src/lib/x-kit/widget/old/lib/widget-style-old.pkg
    #
    package sz  =  size_preference_wrapper;             # size_preference_wrapper               is from   src/lib/x-kit/widget/old/wrapper/size-preference-wrapper.pkg
    #
    package xtr =  xlogger;                             # xlogger                               is from   src/lib/x-kit/xclient/src/stuff/xlogger.pkg
    #
    tracefile   =  "plaid-app.trace.log";
    tracing     =  logger::make_logtree_leaf { parent => xlogger::xkit_logging, name => "plaid_app::tracing", default => FALSE };
    trace       =  xtr::log_if  tracing 0;              # Conditionally write strings to tracing.log or whatever.
        #
        # To debug via tracelogging, annotate the code with lines like
        #
        #       trace {. sprintf "foo/top: bar d=%d" bar; };
        #
        # and then set   write_tracelog = TRUE;   below.
herein

    package   plaid_app
    :         Plaid_App                                 # Plaid_App                     is from   src/lib/x-kit/tut/plaid/plaid-app.api
    {
        write_tracelog = FALSE;

        fun set_up_tracing ()
            =
            {   # Open tracelog file and select tracing level.
                # We don't need to truncate any existing file
                # because that is already done by the logic in
                #     src/lib/std/src/posix/winix-text-file-io-driver-for-posix--premicrothread.pkg
                #
                include package   logger;                       # logger                        is from   src/lib/src/lib/thread-kit/src/lib/logger.pkg
                #
                set_logger_to  (fil::LOG_TO_FILE tracefile);
#               enable  fil::all_logging;                               # Gross overkill.
            };

        ######## Begin mutable selfcheck globals ########
        #

        app_task                        =  REF (NULL: Null_Or( Apptask   ));

        selfcheck_tests_passed          =  REF 0;
        selfcheck_tests_failed          =  REF 0;

        run_selfcheck                   =  REF FALSE;

        #
        ######## End mutable selfcheck globals ########




        fun reset_global_mutable_state ()                                       # Reset above state variables to load-time values.
            =                                                                   # This will be needed if (say) we get run multiple times interactively without being reloaded.
            {   run_selfcheck                                   :=  FALSE;
                #
                app_task                                        :=  NULL;
                #
                selfcheck_tests_passed                          :=  0;
                selfcheck_tests_failed                          :=  0;
            };

        fun test_passed () =  selfcheck_tests_passed :=  *selfcheck_tests_passed + 1;
        fun test_failed () =  selfcheck_tests_failed :=  *selfcheck_tests_failed + 1;
        #
        fun assert bool    =  if bool   test_passed ();
                              else      test_failed ();
                              fi;                               
        #
        fun test_stats  ()
            =
            { passed => *selfcheck_tests_passed,
              failed => *selfcheck_tests_failed
            };


        fun kill_plaid_app ()
            =
            {
                kill_task  { success => TRUE,  task => (the *app_task) };
            };

        fun wait_for_app_task_done ()
            =
            {
                task =  the  *app_task;
                #
                task_finished' =  task_done__mailop  task;

                block_until_mailop_fires  task_finished';

                assert (get_task's_state  task  ==  state::SUCCESS);
            };


        # This maildrop gives the selfcheck code
        # access to the main drawing window:
        #
        my  drawing_window_oneshot:  Oneshot_Maildrop( xc::Window )
            =
            make_oneshot_maildrop ();

        empty_box
            =
            { col  => 0,
              row  => 0,
              wide => 0,
              high => 0
            };


        # Center given box on given point:
        #
        fun center_box
            ( { wide, high, ... }: g2d::Box,
              { col, row }
            )
            = 
            { wide,
              high,
              col => col - (wide / 2),
              row => row - (high / 2)
            };


        fun make_plaid_widgettree  root_window
            =
            {   bounds = wg::make_tight_size_preference (300, 200);

                sz::make_loose_size_preference_wrapper
                    (wg::make_widget
                      {
                        root_window,
                        size_preference_thunk_of =>  {. bounds; },
                        args                     =>  {. { background => NULL }; },
                        realize_widget
                      }
                    );
            }
            where
                screen = wg::screen_of root_window;

                pen = xc::make_pen [
                                     xc::p::FOREGROUND (xc::rgb8_from_int 0xFF0000),    # Was xc::rgb8_color1
                                     xc::p::FUNCTION   xc::OP_XOR
                                   ];
                idle_pen = pen;

                timeout' =  timeout_in' 0.025;

                fun realize_widget { window, window_size, kidplug }
                    =
                    {   make_thread  "plaid"  {.  do_active window_size;  };
                        #
                        ();
                    }
                    where
                        put_in_oneshot (drawing_window_oneshot, window);
                        #
                        drawwin     = xc::drawable_of_window  window;

                        autodrawwin = xc::make_unbuffered_drawable  drawwin;

                        idle_fill = xc::fill_boxes drawwin     idle_pen;
                        fill      = xc::fill_boxes autodrawwin idle_pen;

                        (xc::ignore_keyboard  kidplug)
                            ->
                            xc::KIDPLUG { from_mouse', from_other', ... };

                        fun do_active (size as { wide, high } )
                            =
                            start_over ()
                            where
                                my midpoint as ({ col=>midx, row=>midy } )
                                    = 
                                    g2d::box::midpoint (g2d::box::make (g2d::point::zero, size));


                                # Given a point (x,y) with a velocity (dx,dy),
                                # make it bounce off walls to stay within
                                #    0 < x < wide
                                #    0 < y < high
                                # by appropriately adjusting point and velocity
                                # whenever it strays outside that area: 
                                #
                                fun bounce_if_outside_box
                                    (arg as ( { col=>x,  row=>y },              # Position.
                                              { col=>dx, row=>dy }              # Velocity.
                                    )       )
                                    =
                                    if   (x < 0)     bounce_if_outside_box ({ col=> -x,             row=>  y             }, { col=> -dx, row=>  dy } );
                                    elif (x >= wide) bounce_if_outside_box ({ col=> 2*wide - x - 2, row=>  y             }, { col=> -dx, row=>  dy } );
                                    elif (y < 0)     bounce_if_outside_box ({ col=> x,              row=> -y             }, { col=>  dx, row=> -dy } );
                                    elif (y >= high) bounce_if_outside_box ({ col=> x,              row=> 2*high - y - 2 }, { col=>  dx, row=> -dy } );
                                    else arg;
                                    fi;


                                # Step point by one velocity increment,
                                # bouncing off any wall(s) encountered:
                                #
                                fun step_point (point, velocity)
                                    =
                                    {   point = g2d::point::add (point, velocity);              # Move point one increment.
                                        #
                                        my (point, velocity)
                                            =
                                            bounce_if_outside_box (point, velocity);

                                        (point, velocity);
                                    };
                
                                # Step point, bouncing off walls,
                                # and on odd cycles draw to display:
                                #
                                fun step_state { point, velocity, last_box, odd_cycle }
                                    =
                                    {   # Move the point per velocity,
                                        # bouncing off walls appropriately:
                                        #       
                                        (step_point (point, velocity))
                                            ->
                                            ( point  as  { col, row },
                                              velocity
                                            );
                                            

                                        # Map 'point' into the fourth quadrant, then
                                        # define a box with that as one corner and
                                        # (0,0) as the other:
                                        #
                                        box =    { col  => 0,
                                                   row  => 0,
                                                   #
                                                   wide => 2 * abs(col - midx),
                                                   high => 2 * abs(row - midy)
                                                 };


                                        # Center above box on midpoint of drawing_window:
                                        #
                                        box = center_box (box, midpoint);

                                        if odd_cycle
                                            #
                                            fill (g2d::box::xor (box, last_box));
                                        fi;

                                        { point,
                                          velocity,
                                          last_box  =>  box,
                                          odd_cycle =>  not odd_cycle
                                        };
                                    };


                                fun do_mom (xc::ETC_REDRAW _)                              =>  start_over ();
                                    do_mom (xc::ETC_RESIZE ({ wide, high, ... }: g2d::Box)) =>  do_active ({ wide, high } );
                                    do_mom _                                               =>  ();
                                end

                                also
                                fun active_loop state
                                    = 
                                    do_one_mailop [
                                        #
                                        timeout'     ==>  {.  active_loop (step_state state);  },
                                        from_other'  ==>  do_mom o xc::get_contents_of_envelope,
                                        from_mouse'  ==>  (\\ mail =  case (xc::get_contents_of_envelope  mail)
                                                                          #
                                                                          xc::MOUSE_FIRST_DOWN _ =>  do_idle      size;
                                                                          _                      =>  active_loop  state;
                                                                      esac
                                                          )
                                    ]

                                also
                                fun start_over ()
                                    =
                                    {   xc::clear_drawable  drawwin;
                                        #
                                        active_loop
                                          {
                                            point     =>  midpoint,
                                            last_box  =>  empty_box,
                                            odd_cycle =>  FALSE,
                                            velocity  =>  { col=>1, row=>1 }
                                          };
                                    };
                            end                 # fun do_active

                        also
                        fun do_idle (size as { wide, high } )
                            =
                            idle_loop ()
                            where

                                fun redraw ()
                                    =
                                    {
                                        xc::clear_drawable  drawwin;
                                        redraw_loop 1;
                                    }
                                    where       
                                        bound = int::min (wide, high) / 2;

                                        fun redraw_loop i
                                            = 
                                            if (i <= bound)
                                                #
                                                idle_fill
                                                  [
                                                    { col=>i,            row=>i,            wide=>1,          high=>high-(2*i) },
                                                    { col=>wide - i - 1, row=>i,            wide=>1,          high=>high-(2*i) },
                                                    { col=>i,            row=>i,            wide=>wide-(2*i), high=>1 },
                                                    { col=>i,            row=>high - i - 1, wide=>wide-(2*i), high=>1 }
                                                  ];

                                                redraw_loop (i+2);
                                            fi;

                                    end;


                                fun do_mom (xc::ETC_REDRAW _)                              =>  redraw ();
                                    do_mom (xc::ETC_RESIZE ({ wide, high, ... } )) =>  do_idle ({ wide, high } );
                                    do_mom _                                               =>  ();
                                end;


                                fun idle_loop ()
                                    = 
                                    do_one_mailop [
                                        #
                                        from_other' ==>  idle_loop  o  do_mom  o  xc::get_contents_of_envelope,
                                        #
                                        from_mouse' ==>  (\\ envelope = case (xc::get_contents_of_envelope  envelope)
                                                                             #
                                                                             xc::MOUSE_FIRST_DOWN _ =>  do_active size;
                                                                             _                      =>  idle_loop ();
                                                                         esac

                                                         )

                                    ];
                            end;                                        # fun do_idle
                    end;                                                # fun realize_widget
            end;                                                        # fun make_plaid_widgettree


        # Thread to exercise the app by simulating user
        # mouseclicks and verifying their effects:
        #
        fun make_selfcheck_thread  { hostwindow, widgettree }
            =
            {   xtr::make_thread  "plaid-app selfcheck"  selfcheck;
                #
                ();
            }   
            where
                # Figure midpoint of window and also
                # a small box centered on the midpoint:
                #
                fun midwindow window
                    =
                    {
                        # Get size of drawing window:
                        #
                        (xc::get_window_site  window)
                            ->
                            { row, col, high, wide };

                        # Define midpoint of drawing window,
                        # and a 9x9 box enclosing it:
                        #
                        stipulate
                            row =  high / 2;
                            col =  wide / 2;
                        herein
                            midpoint =  { row, col };
                            midbox   =  { row => row - 4, col => col - 4, high => 9, wide => 9 };
                        end;

                        (midpoint, midbox);
                    };

                fun selfcheck ()
                    =
                    {   # Wait until the widgettree is realized and running:
                        # 
                        get_from_oneshot  (wg::get_''gui_startup_complete''_oneshot_of  widgettree);

                        drawing_window =   get_from_oneshot  drawing_window_oneshot;

                        # Fetch from X server the center pixels
                        # over which we are about to draw:
                        #
                        (midwindow      drawing_window) ->  (_, drawing_window_midbox);
                        #
                        antedraw_midwindow_image
                            =
                            xc::make_clientside_pixmap_from_window (drawing_window_midbox, drawing_window);

                        # Give the drawing thread time to
                        # draw over the window center:
                        #
                        sleep_for 0.5;

                        # Re-fetch center pixels, verify
                        # that new result differs from original result.
                        #
                        # Strictly speaking we have a race condition
                        # here, but I think this is good enough for
                        # the purpose -- this isn't flight control:
                        #
                        postdraw_midwindow_image
                            =
                            xc::make_clientside_pixmap_from_window (drawing_window_midbox, drawing_window);
                        #
                        assert (not (xc::same_cs_pixmap (antedraw_midwindow_image, postdraw_midwindow_image)));

                        # All done -- shut everything down:
                        #
                        (xc::xsession_of_window  (wg::window_of widgettree)) ->  xsession;

                        xc::close_xsession  xsession;

                        sleep_for 0.2;                          # I think close_xsession returns before everything has shut down. Need something cleaner here. XXX SUCKO FIXME.

                        kill_plaid_app ();

#                       shut_down_thread_scheduler  winix__premicrothread::process::success;                    # We did this prior to 6.3
                    };
            end;                                                # fun make_selfcheck_thread

        fun start_up_plaid_app_threads  root_window
            =
            {   name = wy::make_view
                         { name    =>   wy::style_name [],
                           aliases => [ wy::style_name [] ]
                         };

                style = wg::style_from_strings (root_window, []);

                view = (name, style);

                widgettree =  make_plaid_widgettree  root_window;

                args = [ (wa::title,     wa::STRING_VAL "Plaid"),
                         (wa::icon_name, wa::STRING_VAL "Plaid")
                       ];

                hostwindow
                    =
                    top::hostwindow  (root_window, view, args)  widgettree;

                top::start_widgettree_running_in_hostwindow  hostwindow;

                close_window' =  top::get_''close_window''_mailop  hostwindow;

                make_thread  "window closer"  {.
                    #
                    do_one_mailop [
                        #
                        close_window'
                            ==>
                           {.   hostwindow::destroy  hostwindow;
                                #
                                sleep_for 0.2;          # Give previous time to complete. Need something cleaner here. XXX SUCKO FIXME.
                                #
                                kill_plaid_app ();
                                #
#                               shut_down_thread_scheduler  winix__premicrothread::process::success;    # This is what we did pre-6.3.
                            }
                    ];  
                };


                if *run_selfcheck
                    #
                    make_selfcheck_thread  { hostwindow, widgettree };
                    ();
                fi;


                ();
            };                                                  # fun start_up_plaid_app_threads

        fun set_up_plaid_app_task  root_window
            =
            # Here we arrange that all the threads
            # for the application run as a task "plaid app",
            # so that later we can shut them all down with
            # a simple kill_task().  We explicitly create one
            # root thread within the task; the rest then implicitly
            # inherit task membership:
            #
            {   plaid_app_task =   (the *app_task);
                #
                xtr::make_thread' [ THREAD_NAME "plaid app",
                                    THREAD_TASK  plaid_app_task
                                  ]
                                  start_up_plaid_app_threads
                                  root_window;
                ();
            };

        fun do_it' (debug_flags, server)
            =
            {   xlogger::init debug_flags;
                #
                if write_tracelog   set_up_tracing ();   fi;

                plaid_app_task =   make_task  "plaid app"  [];
                app_task      :=   THE  plaid_app_task;

                rx::run_in_x_window_old'  set_up_plaid_app_task  [ rx::DISPLAY server ];                        # if (server=="") it will be ignored.

                sleep_for 0.5;

                wait_for_app_task_done ();
            };


        fun do_it ()
            =
            {   if write_tracelog   set_up_tracing ();   fi;
                #
                plaid_app_task =   make_task  "plaid app"  [];
                app_task      :=   THE  plaid_app_task;

                rx::run_in_x_window_old  set_up_plaid_app_task;

                wait_for_app_task_done ();
            };


        fun selfcheck ()
            =
            {
                reset_global_mutable_state ();                                          # Don't depend on load-time state initialization -- we might get run multiple times interactively, say.
                #
                run_selfcheck :=  TRUE;

                do_it' ([], "");

                test_stats ();
            };  


        fun main (program ! server ! _, _)
                =>
                do_it' ([], server);

            main _
                =>
                do_it ();
        end;
    };                          # package plaid
end;


Comments and suggestions to: bugs@mythryl.org

PreviousUpNext