PreviousUpNext

15.4.1441  src/lib/x-kit/widget/gui/guiboss-imp.pkg

## guiboss-imp.pkg
#
# For the big picture see the imp dataflow diagrams in
#
#     src/lib/x-kit/xclient/src/window/xclient-ximps.pkg
#
# The vision here is to implement a simple, flexible, easy-to-customize
# GUI widget infrastructure portable to various rendering layers like
# X, OpenGL and javascript.  The allow small teams to efficiently develop
# (for example) GUI-driven custom scientific, stock-trading and programming
# support apps.  As such, the emphasis is on simplicity, portability,
# cleanliness, smooth integration with Mythryl facilities such as the
# type system, garbage collector and package system.  Competing with
# commercial GUI toolkits for glitter factor is NOT a priority.
#
# guiboss_imp is the master imp responsible for starting up and shutting
# down running GUIs.
#
# Most of its major types and supporting code for handling them is in
#     src/lib/x-kit/widget/gui/guiboss-types.pkg
#
# guiboss_imp GUIs divide into three types of spaces:
#     widgetspace, for conventional row/column widget layout.
#     objectspace, for draw and paint functionality and also
#                  free-form drop-and-drag knob-and-tube GUIs.
#     spritespace, for 2D (and eventually 3D) animation.
#
# At the moment (2014-11-20) only widgetspace is at all well developed.
#
# guiboss_imp delegates management of these three kinds of spaces
# (in particular widget layout) to
#     src/lib/x-kit/widget/space/widget/widgetspace-imp.pkg
#     src/lib/x-kit/widget/space/sprite/spritespace-imp.pkg
#     src/lib/x-kit/widget/space/object/objectspace-imp.pkg
#  
# guiboss_imp is designed to be portable, but at the moment the only
# rendering layer implemented is for X, using the interface exported by
#     src/lib/x-kit/widget/xkit/app/guishim-imp-for-x.pkg
#  
# We refer to mouse-sensitive controls as "gadgets".
# Each of our three spaces has its own flavor of gadget:
#     widgetspace: Widgets, base implementation being   src/lib/x-kit/widget/xkit/theme/widget/default/look/widget-imp.pkg
#     objectsapce: Objects, base implementation being   src/lib/x-kit/widget/xkit/theme/widget/default/look/object-imp.pkg
#     spritespace: Sprites, base implementation being   src/lib/x-kit/widget/xkit/theme/widget/default/look/sprite-imp.pkg
#     

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


stipulate
    include package   threadkit;                                # threadkit                             is from   src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg
    #
#   package ap  =  client_to_atom;                              # client_to_atom                        is from   src/lib/x-kit/xclient/src/iccc/client-to-atom.pkg
#   package au  =  authentication;                              # authentication                        is from   src/lib/x-kit/xclient/src/stuff/authentication.pkg
#   package cpm =  cs_pixmap;                                   # cs_pixmap                             is from   src/lib/x-kit/xclient/src/window/cs-pixmap.pkg
#   package cpt =  cs_pixmat;                                   # cs_pixmat                             is from   src/lib/x-kit/xclient/src/window/cs-pixmat.pkg
#   package dy  =  display;                                     # display                               is from   src/lib/x-kit/xclient/src/wire/display.pkg
#   package fil =  file__premicrothread;                        # file__premicrothread                  is from   src/lib/std/src/posix/file--premicrothread.pkg
#   package fti =  font_index;                                  # font_index                            is from   src/lib/x-kit/xclient/src/window/font-index.pkg
#   package r2k =  xevent_router_to_keymap;                     # xevent_router_to_keymap               is from   src/lib/x-kit/xclient/src/window/xevent-router-to-keymap.pkg
#   package mtx =  rw_matrix;                                   # rw_matrix                             is from   src/lib/std/src/rw-matrix.pkg
#   package rop =  ro_pixmap;                                   # ro_pixmap                             is from   src/lib/x-kit/xclient/src/window/ro-pixmap.pkg
#   package rw  =  root_window;                                 # root_window                           is from   src/lib/x-kit/widget/lib/root-window.pkg
#   package rwv =  rw_vector;                                   # rw_vector                             is from   src/lib/std/src/rw-vector.pkg
#   package sep =  client_to_selection;                         # client_to_selection                   is from   src/lib/x-kit/xclient/src/window/client-to-selection.pkg
#   package shp =  shade;                                       # shade                                 is from   src/lib/x-kit/widget/lib/shade.pkg
#   package sj  =  socket_junk;                                 # socket_junk                           is from   src/lib/internet/socket-junk.pkg
#   package x2s =  xclient_to_sequencer;                        # xclient_to_sequencer                  is from   src/lib/x-kit/xclient/src/wire/xclient-to-sequencer.pkg
#   package tr  =  logger;                                      # logger                                is from   src/lib/src/lib/thread-kit/src/lib/logger.pkg
#   package tsr =  thread_scheduler_is_running;                 # thread_scheduler_is_running           is from   src/lib/src/lib/thread-kit/src/core-thread-kit/thread-scheduler-is-running.pkg
#   package u1  =  one_byte_unt;                                # one_byte_unt                          is from   src/lib/std/one-byte-unt.pkg
#   package v1u =  vector_of_one_byte_unts;                     # vector_of_one_byte_unts               is from   src/lib/std/src/vector-of-one-byte-unts.pkg
#   package v2w =  value_to_wire;                               # value_to_wire                         is from   src/lib/x-kit/xclient/src/wire/value-to-wire.pkg
#   package wg  =  widget;                                      # widget                                is from   src/lib/x-kit/widget/old/basic/widget.pkg
#   package wi  =  window;                                      # window                                is from   src/lib/x-kit/xclient/src/window/window.pkg
#   package wme =  window_map_event_sink;                       # window_map_event_sink                 is from   src/lib/x-kit/xclient/src/window/window-map-event-sink.pkg
#   package wpp =  client_to_window_watcher;                    # client_to_window_watcher              is from   src/lib/x-kit/xclient/src/window/client-to-window-watcher.pkg
#   package wy  =  widget_style;                                # widget_style                          is from   src/lib/x-kit/widget/lib/widget-style.pkg
#   package xc  =  xclient;                                     # xclient                               is from   src/lib/x-kit/xclient/xclient.pkg
#   package xj  =  xsession_junk;                               # xsession_junk                         is from   src/lib/x-kit/xclient/src/window/xsession-junk.pkg
#   package xtr =  xlogger;                                     # xlogger                               is from   src/lib/x-kit/xclient/src/stuff/xlogger.pkg
    #

    #
    package gtr =  translate_guiplan_to_guipane;                # translate_guiplan_to_guipane          is from   src/lib/x-kit/widget/gui/translate-guiplan-to-guipane.pkg
    package rtx =  translate_guipane_to_guipith;                # translate_guipane_to_guipith          is from   src/lib/x-kit/widget/gui/translate-guipane-to-guipith.pkg

    package ged =  guiboss_event_dispatch;                      # guiboss_event_dispatch                is from   src/lib/x-kit/widget/gui/guiboss-event-dispatch.pkg
    package evt =  gui_event_types;                             # gui_event_types                       is from   src/lib/x-kit/widget/gui/gui-event-types.pkg
    package gts =  gui_event_to_string;                         # gui_event_to_string                   is from   src/lib/x-kit/widget/gui/gui-event-to-string.pkg
    package gt  =  guiboss_types;                               # guiboss_types                         is from   src/lib/x-kit/widget/gui/guiboss-types.pkg
    package gtj =  guiboss_types_junk;                          # guiboss_types_junk                    is from   src/lib/x-kit/widget/gui/guiboss-types-junk.pkg
    package gpj =  guiboss_popup_junk;                          # guiboss_popup_junk                    is from   src/lib/x-kit/widget/gui/guiboss-popup-junk.pkg
    package gwl =  guiboss_widget_layout;                       # guiboss_widget_layout                 is from   src/lib/x-kit/widget/gui/guiboss-widget-layout.pkg

    package a2r =  windowsystem_to_xevent_router;               # windowsystem_to_xevent_router         is from   src/lib/x-kit/xclient/src/window/windowsystem-to-xevent-router.pkg

    package gd  =  gui_displaylist;                             # gui_displaylist                       is from   src/lib/x-kit/widget/theme/gui-displaylist.pkg

    package pp  =  standard_prettyprinter;                      # standard_prettyprinter                is from   src/lib/prettyprint/big/src/standard-prettyprinter.pkg

    package err =  compiler::error_message;                     # compiler                              is from   src/lib/core/compiler/compiler.pkg
                                                                # error_message                         is from   src/lib/compiler/front/basics/errormsg/error-message.pkg

    package bt  =  gui_to_sprite_theme;                         # gui_to_sprite_theme                   is from   src/lib/x-kit/widget/theme/sprite/gui-to-sprite-theme.pkg
    package ct  =  gui_to_object_theme;                         # gui_to_object_theme                   is from   src/lib/x-kit/widget/theme/object/gui-to-object-theme.pkg
    package wt  =  widget_theme;                                # widget_theme                          is from   src/lib/x-kit/widget/theme/widget/widget-theme.pkg

    package boi =  spritespace_imp;                             # spritespace_imp                       is from   src/lib/x-kit/widget/space/sprite/spritespace-imp.pkg
    package cai =  objectspace_imp;                             # objectspace_imp                       is from   src/lib/x-kit/widget/space/object/objectspace-imp.pkg
    package pai =  widgetspace_imp;                             # widgetspace_imp                       is from   src/lib/x-kit/widget/space/widget/widgetspace-imp.pkg

    #    
    package gtg =  guiboss_to_guishim;                          # guiboss_to_guishim                    is from   src/lib/x-kit/widget/theme/guiboss-to-guishim.pkg

    package b2s =  spritespace_to_sprite;                       # spritespace_to_sprite                 is from   src/lib/x-kit/widget/space/sprite/spritespace-to-sprite.pkg
    package c2o =  objectspace_to_object;                       # objectspace_to_object                 is from   src/lib/x-kit/widget/space/object/objectspace-to-object.pkg

    package s2s =  sprite_to_spritespace;                       # sprite_to_spritespace                 is from   src/lib/x-kit/widget/space/sprite/sprite-to-spritespace.pkg
    package o2o =  object_to_objectspace;                       # object_to_objectspace                 is from   src/lib/x-kit/widget/space/object/object-to-objectspace.pkg

    package g2p =  gadget_to_pixmap;                            # gadget_to_pixmap                      is from   src/lib/x-kit/widget/theme/gadget-to-pixmap.pkg

#   package frm =  frame;                                       # frame                                 is from   src/lib/x-kit/widget/leaf/frame.pkg

    package idm =  id_map;                                      # id_map                                is from   src/lib/src/id-map.pkg
    package im  =  int_red_black_map;                           # int_red_black_map                     is from   src/lib/src/int-red-black-map.pkg
#   package is  =  int_red_black_set;                           # int_red_black_set                     is from   src/lib/src/int-red-black-set.pkg
    package sm  =  string_map;                                  # string_map                            is from   src/lib/src/string-map.pkg

    package r8  =  rgb8;                                        # rgb8                                  is from   src/lib/x-kit/xclient/src/color/rgb8.pkg
    package r64 =  rgb;                                         # rgb                                   is from   src/lib/x-kit/xclient/src/color/rgb.pkg
    package g2d =  geometry2d;                                  # geometry2d                            is from   src/lib/std/2d/geometry2d.pkg
    package g2j =  geometry2d_junk;                             # geometry2d_junk                       is from   src/lib/std/2d/geometry2d-junk.pkg

    package mbi =  millboss_imp;                                # millboss_imp                          is from   src/lib/x-kit/widget/edit/millboss-imp.pkg
    package e2g =  millboss_to_guiboss;                         # millboss_to_guiboss                   is from   src/lib/x-kit/widget/edit/millboss-to-guiboss.pkg
    package g2c =  guiboss_to_compileimp;                       # guiboss_to_compileimp                 is from   src/lib/x-kit/widget/edit/guiboss-to-compileimp.pkg
    package a2c =  app_to_compileimp;                           # app_to_compileimp                     is from   src/lib/x-kit/widget/edit/app-to-compileimp.pkg
    package ci  =  compile_imp;                                 # compile_imp                           is from   src/lib/x-kit/widget/edit/compile-imp.pkg

#   package tbi =  textmill;                                    # textmill                              is from   src/lib/x-kit/widget/edit/textmill.pkg


    tracefile   =  "widget-unit-test.trace.log";

    nb = log::note_on_stderr;                                   # log                                   is from   src/lib/std/src/log.pkg

herein

    package guiboss_imp
    :       Guiboss_Imp                                                                                                 # Guiboss_Imp           is from   src/lib/x-kit/widget/gui/guiboss-imp.api
    {
        Client_To_Guiboss                                                                                               # The 'client' is the microthread starting up guiboss, who will typically then just do   block_until_mailop_fires  client_to_guiboss.guiboss_done';   -- see e.g.   src/lib/x-kit/widget/gui/run-guiplan-on-x.pkg
          =
          { id:                 Id,                                                                                     # Unique id to facilitate storing guiboss instances in indexed datastructures like red-black trees.
            #
            get_sprite_theme:   Void -> bt::Gui_To_Sprite_Theme,
            get_object_theme:   Void -> ct::Gui_To_Object_Theme,
            get_widget_theme:   Void -> wt::Widget_Theme,
            #
            make_hostwindow:     gtg::Hostwindow_Hints                      ->  gtg::Guiboss_To_Hostwindow,             # 
            #
            start_gui:          (gtg::Guiboss_To_Hostwindow, gt::Guiplan)   -> (Void -> gt::Client_To_Guiwindow),       # Calling return value will block microthread until gui-plan    startup is complete.
            #
            guiboss_done':      End_Gun                                                                                 # Something to block on in   src/lib/x-kit/widget/gui/run-guiplan-on-x.pkg
          };

        Guiboss_Option
          #
          =  MICROTHREAD_NAME   String                                                                                  # 
          |  ID                 Id                                                                                      # Stable, unique id for imp.
          ;     

        Guiboss_Arg =  List(Guiboss_Option);                                                                            # Currently no required component.

        Imports = {                                                                                                     # Ports we use, provided by other imps.
                    int_sink:                   Int -> Void,
                    guiboss_to_guishim:         gtg::Guiboss_To_Guishim,        
                    gui_to_sprite_theme:        bt::Gui_To_Sprite_Theme,
                    gui_to_object_theme:        ct::Gui_To_Object_Theme,
                    theme:                      wt::Widget_Theme
                  };


        Me_Slot = Mailslot( { imports:          Imports,
                              me:               gt::Guiboss_State,
                              guiboss_arg:      Guiboss_Arg,
                              run_gun':         Run_Gun,
                              end_gun':         End_Gun
                            }
                          );

        Exports = {                                                                                                     # Ports we provide for use by other imps.
                    client_to_guiboss:  Client_To_Guiboss
                  };


        Guiboss_Egg =  Void -> (Exports,   (Imports, Run_Gun, End_Gun) -> Void);


        Runstate =    {                                                                                                 # These values will be statically globally visible throughout the code body for the imp.
                        id:                     Id,
                        me:                     gt::Guiboss_State,                                                      # 
                        guiboss_arg:            Guiboss_Arg,
                        imports:                Imports,                                                                # Imps to which we send requests.
                        guiboss_to_millboss:    mbi::Guiboss_To_Millboss,
                        guiboss_to_compileimp:  g2c::Guiboss_To_Compileimp,
                        app_to_compileimp:      a2c::App_To_Compileimp,
                        to:                     Replyqueue,                                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                        end_gun':               End_Gun,                                                                # We shut down the microthread when this fires.
                        fire__guiboss_done:     Void -> Void                                                            # Fire Client_To_Guiboss.guiboss_done' mailop.  Callers block on this, e.g.     src/lib/x-kit/widget/gui/run-guiplan-on-x.pkg
                      };

        Guiboss_Q    = Mailqueue( Runstate -> Void );


        Globals =  Ref( sm::Map(Crypt) );

        globals__global =  (REF sm::empty): Globals;                                                                    # For Gadget_To_Guiboss.note_global, .find_global and .drop_global.   Holds  global values whose types we don't want to import into guiboss_imp.
                                                                                                                        # Putting it here (instead of in gt::Guiboss_State) lets find_global() run in client microthread, reducing risk of deadlock.

        fun shut_down_guiboss' (runstate: Runstate)
            =
            {   runstate.fire__guiboss_done ();
                #
                thread_exit { success => TRUE };                                                                        # Will not return.
            };

        fun run ( guiboss_q:                    Guiboss_Q,                                                              # 
                  #
                  runstate as
                  {                                                                                                     # These values will be statically globally visible throughout the code body for the imp.
                    id:                         Id,
                    me:                         gt::Guiboss_State,                                                      # 
                    guiboss_arg:                Guiboss_Arg,
                    imports:                    Imports,                                                                # Imps to which we send requests.
                    guiboss_to_millboss:        mbi::Guiboss_To_Millboss,
                    guiboss_to_compileimp:      g2c::Guiboss_To_Compileimp,
                    app_to_compileimp:          a2c::App_To_Compileimp,
                    to:                         Replyqueue,                                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                    end_gun':                   End_Gun,                                                                # 
                    fire__guiboss_done:         Void -> Void                                                            # Fire Client_To_Guiboss.guiboss_done' mailop.  Callers block on this, e.g.     src/lib/x-kit/widget/gui/run-guiplan-on-x.pkg
                  }
                )
            =
            {   loop ();
            }
            where


                #
                fun loop ()                                                                                             # Outer loop for the imp.
                    =
                    {   do_one_mailop' to [
                            #
                            end_gun'                       ==>  shut_down_guiboss_imp',
                            take_from_mailqueue' guiboss_q ==>  do_guiboss_plea
                        ];

                        loop ();
                    }   
                    where
                        fun do_guiboss_plea  thunk
                            =
                            thunk runstate;
                        #
                        fun shut_down_guiboss_imp' ()
                            =
                            shut_down_guiboss'  runstate;
                    end;
            end;        

        #
        fun kill_gui'
              (
                runstate as
                { id:                           Id,
                  me:                           gt::Guiboss_State,                                                      # 
                  guiboss_arg:                  Guiboss_Arg,
                  imports:                      Imports,                                                                # Imps to which we send requests.
                  guiboss_to_millboss:          mbi::Guiboss_To_Millboss,
                  guiboss_to_compileimp:        g2c::Guiboss_To_Compileimp,
                  app_to_compileimp:            a2c::App_To_Compileimp,
                  to:                           Replyqueue,                                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                  end_gun':                     End_Gun,                                                                # 
                  fire__guiboss_done:           Void -> Void                                                            # Fire Client_To_Guiboss.guiboss_done' mailop.  Callers block on this, e.g.     src/lib/x-kit/widget/gui/run-guiplan-on-x.pkg
                }:                              Runstate,
                ( guipane:                      gt::Guipane,
                  hostwindow_info:              gt::Hostwindow_Info,
                  redraw_window_when_done:      Bool
                )
              )
            =
            {   # Recursively kill off all running guis
                # which are children of current running gui:
                #
                case guipane.subwindow_info
                    #
                    gt::SUBWINDOW_DATA r
                        =>
                        apply kill_one_subgui *r.popups
                        where
                            fun kill_one_subgui (subwindow_info: gt::Subwindow_Data)
                                =
                                case subwindow_info
                                    #
                                    gt::SUBWINDOW_DATA r
                                        =>
                                        case *r.guipane
                                            #
                                            THE guipane => kill_gui' (runstate, (guipane, hostwindow_info, FALSE));

                                            NULL => ();
                                        esac;
                                esac;
                        end;
                esac;
                

                gpj::kill__guipane__imps        (guipane, me);
                gpj::free__guipane__resources   (guipane, me);

                case guipane.subwindow_info
                    #
                    gt::SUBWINDOW_DATA r
                        =>
                        {   # If we have a parent, remove ourself from parent's list of active popups:
                            #   
                            case r.parent
                                #
                                THE parent_subwindow_info
                                    =>
                                    # We do have a parent -- remove ourself from parent's list of active popups:
                                    #   
                                    case parent_subwindow_info
                                        #
                                        gt::SUBWINDOW_DATA q
                                            =>
                                            q.popups := list::remove  is_us  *q.popups
                                                        where
                                                            fun is_us (bp: gt::Subwindow_Data)
                                                                =
                                                                case bp
                                                                    #
                                                                    gt::SUBWINDOW_DATA r'
                                                                        =>
                                                                        same_id ((*r.pixmap).id, (*r'.pixmap).id);
                                                                esac;
                                                        end;
                                    esac;

                                NULL => ();
                            esac;
                        };
                esac;

                case guipane.subwindow_info                                                                             # If we're the toplevel gui, remember we no longer have a gui running on this hostwindow.
                    #
                    gt::SUBWINDOW_DATA r
                        =>
                        case r.parent
                            #
                            NULL  =>    hostwindow_info.subwindow_info :=  NULL;                                                # We're killing the toplevel gui for this hostwindow so remember that we no longer have a gui running on this hostwindow.
                            THE _ =>    ();                                                                             # We're pausing a secondary popup gui for this hostwindow.
                        esac;
                esac;

                case *hostwindow_info.subwindow_info                                                                    # Redraw the window if there are any running guis left to redraw.
                    #
                    THE subwindow_info
                        =>              
                        {
                            gwl::redraw_all_guipanes
                              (
                                subwindow_info,                                                                         # This provides redraw_all_guipanes an entrypoint into the remaining Subwindow_Or_View tree. Any Subwindow_Or_View in the tree would do.
                                hostwindow_info.guiboss_to_hostwindow
                              );
                        };

                    NULL =>                                                                                             # No running guis left on window so clear it to black.
                        case guipane.subwindow_info
                            #
                            gt::SUBWINDOW_DATA r
                                =>
                                {   entire_window =  g2d::box::make  (g2d::point::zero,  (*r.pixmap).size);
                                    #   
                                    midpoint      =  g2d::box::midpoint entire_window;

                                    text          =  [ gd::PUT_TEXT (gd::CENTERED_ON_POINT, [ gd::TEXT (midpoint, "No GUI running.") ]) ];
                                    text          =  [ gd::FONT ([ "-*-courier-bold-r-*-*-20-*-*-*-*-*-*-*", "9x15" ], text) ];

                                    hostwindow_info.guiboss_to_hostwindow.draw_displaylist
                                      [
                                        gd::COLOR (r64::black,  [ gd::FILLED_BOXES [ entire_window ]] ),
                                        gd::COLOR (r64::white,  text)
                                      ];
                                };
                        esac;
                esac;
            };


        fun position_subwindow_entirely_within_parent                                                                   # Do nothing unless required, change only upperleft if possible, change size only as a last resort.
              {                                                                                                         # 
                parent_size:                    g2d::Size,                                                              # Parent upperleft is { row => 0, col => 0 } for our purposes here -- i.e., we're working in parent window coordinate system.
                old_upperleft:                  g2d::Point,                                                             # In parent coordinates.
                old_size:                       g2d::Size
              }
            =
            {   old_upperleft -> { row, col };
                old_size      -> { high, wide };

                row = max (row, 0);                                                                                     # Let's start by ensuring that popup upperleft is not left of or above origin of parent coordinate system..
                col = max (col, 0);                                                                                     #

                high = max (high, 1);                                                                                   # Next let's make sure the requested size is positive...
                wide = max (wide, 1);                                                                                   #

                high = min (high, parent_size.high);                                                                    # Now let's ensure that popup can in fact fit within parent.
                wide = min (wide, parent_size.wide);                                                                    # 

                row  = min (row, parent_size.high - high);                                                              # Now slide the popup left and/or up as necessary to make it actually fit within parent.
                col  = min (col, parent_size.wide - wide);                                                              #

                { new_upperleft =>  { row, col },
                  new_size      =>  { high, wide }                                                                      # That should do it!
                };
            };

        fun size_subwindow_entirely_within_parent                                                                       # Do nothing unless required, change only size if reasonable, change upperleft only as a last resort.
              {                                                                                                         # 
                parent_size:                    g2d::Size,                                                              # Parent upperleft is { row => 0, col => 0 } for our purposes here -- i.e., we're working in parent window coordinate system.
                old_upperleft:                  g2d::Point,                                                             # In parent coordinates.
                old_size:                       g2d::Size
              }
            =
            {
                old_upperleft -> { row, col };
                old_size      -> { high, wide };
                parent_size   -> { high => mom_high, wide => mom_wide };         

                row = min (row, mom_high - 20);                                                                         # First let's ensure that popup upperleft is not right of or below parent window.
                col = min (col, mom_wide - 20);                                                                         #

                row = max (row, 0);                                                                                     # Now   let's ensure that popup upperleft is not left of or above origin of parent coordinate system..
                col = max (col, 0);                                                                                     #

                high = max (high, 1);                                                                                   # Next let's make sure the requested size is positive...
                wide = max (wide, 1);                                                                                   #

                high = min (high, mom_high - row);                                                                      # Now shrink the popup as necessary to make it actually fit within parent.
                wide = min (wide, mom_wide - col);                                                                      #

                { new_upperleft =>  { row, col },
                  new_size      =>  { high, wide }                                                                      # That should do it!
                };
            };

        fun make_subwindow_info_for_popup                                                                               # Create a g2p::Gadget_To_Rw_Pixmap instance, wrap it in a gt::SUBWINDOW_INFO, and enter the latter into the tree of SUBWINDOW_INFO.
              (
                make_rw_pixmap:                 g2d::Size -> g2p::Gadget_To_Rw_Pixmap,
                next_stacking_order:            Ref(Int),
                parent:                         gt::Subwindow_Data,
                site:                           g2d::Box
              ) 
            =
            {   p = case parent     gt::SUBWINDOW_DATA p =>  p;
                    esac;
 

                (g2d::box::upperleft_and_size site)
                    ->
                    ( old_upperleft as { row,  col  },
                      old_size      as { high, wide }
                    ); 

                parent_size = (*p.pixmap).size;

                my { new_upperleft, new_size }                                                                          # Select actual site for popup.  We need it to fit entirely within parent.
                    =
                    position_subwindow_entirely_within_parent
                      {
                        parent_size,
                        old_upperleft,
                        old_size
                      };

                pixmap =  make_rw_pixmap  new_size;                                                                     # We're blocking for a round-trip here, which is not great.  We probably should implement imports.guiboss_to_guishim.pass_fresh_rw_pixmap to allow this to be nonblocking. XXX SUCKO FIXME

                stacking_order = *next_stacking_order;
                #
                next_stacking_order := stacking_order + 1;

                new_subwindow_info
                    =
                    gt::SUBWINDOW_DATA
                      { id              =>  issue_unique_id (),
                        guipane         =>  REF NULL,
                        pixmap          =>  REF pixmap,                                                                 # Main backing store for this running gui.
                        popups          =>  REF ([]: List( gt::Subwindow_Data)),
                        parent          =>  THE parent,
                        upperleft       =>  REF new_upperleft,
                        #
                        stacking_order
                      };

                p.popups :=  new_subwindow_info ! *p.popups;                                                            # Remember that parent subwindow has a new child subwindow.

                site = g2d::box::make (new_upperleft, new_size);

                (site, new_subwindow_info);
            };

        fun start_gui'
              (
                ( runstate
                  as
                  { id:                         Id,
                    me:                         gt::Guiboss_State,                                                      # 
                    guiboss_arg:                Guiboss_Arg,
                    imports:                    Imports,                                                                # Imps to which we send requests.
                    guiboss_to_millboss:        mbi::Guiboss_To_Millboss,
                    guiboss_to_compileimp:      g2c::Guiboss_To_Compileimp,
                    app_to_compileimp:          a2c::App_To_Compileimp,
                    to:                         Replyqueue,                                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                    end_gun':                   End_Gun,                                                                # We shut down the guiboss microthread when end_gun' fires.
                    fire__guiboss_done:         Void -> Void                                                            # Fire Client_To_Guiboss.guiboss_done' mailop.  Callers block on this, e.g.     src/lib/x-kit/widget/gui/run-guiplan-on-x.pkg
                  }
                ):                              Runstate,
                #       
                hostwindow_for_gui:     gtg::Guiboss_To_Hostwindow,
                subwindow_info:         gt::Subwindow_Data,
                guiplan:                gt::Guiplan,

                gui_startup_complete':  Oneshot_Maildrop( gt::Client_To_Guiwindow ),
                guiboss_q:              Guiboss_Q,
                kill_gui:               (gt::Guipane, gt::Hostwindow_Info) -> Void
              )
            =
            {
                case subwindow_info
                    #
                    gt::SUBWINDOW_DATA r
                        =>                                                                                              # guipane is from
                        {   r.guipane := THE guipane;                                                                   # 
                            #                                                                                           # below 'where' clause. so as to be available in make_popup().
                            case r.parent
                                #
                                NULL  =>    hostwindow_info.subwindow_info :=  THE subwindow_info;                      # We're starting a toplevel gui for this hostwindow so remember that we now have a gui running on this hostwindow.
                                THE _ =>    ();                                                                         # We're starting a secondary popup gui for this hostwindow.
                            esac;
                        };
                esac;

                fire_run_gun ();

                window_site = hostwindow_for_gui.get_window_site ();

                resite_and_redraw (me, window_site, subwindow_info, guipane, hostwindow_info);

                client_to_guiwindow
                  =
                  { id          =>  issue_unique_id (),
                    kill_gui    =>  {.  kill_gui (guipane, hostwindow_info); }
                  }
                  : gt::Client_To_Guiwindow
                  ;

                put_in_oneshot (gui_startup_complete', client_to_guiwindow);
            }
            where

                we_are_a_popup_gui
                    =
                    case subwindow_info
                        #
                        gt::SUBWINDOW_DATA r
                            =>
                            {   case r.parent
                                    #
                                    NULL  =>    FALSE;                                                                  # We're starting the primary (toplevel) gui  for this hostwindow.
                                    THE _ =>    TRUE;                                                                   # We're starting a secondary popup gui for this hostwindow.
                                esac;
                            };
                    esac;

                (make_run_gun ()) ->   { run_gun', fire_run_gun };                                                      # Run gun to start up widgets. We don't use an end_gun with them because they wander between guipane instances which start/stop independently, so it would be a mess.

                hostwindow_info = idm::get_or_raise_exception_not_found (*me.hostwindows, hostwindow_for_gui.id)
                                 except
                                     NOT_FOUND = {   printf                "*me.hostwindows contains no entry for hostwindow %d?!   -- restart_gui' in guiboss-imp.pkg\n" (id_to_int hostwindow_for_gui.id);
                                                     log::fatal (sprintf "*me.hostwindows contains no entry for hostwindow %d?!   -- restart_gui' in guiboss-imp.pkg" (id_to_int hostwindow_for_gui.id));
                                                     raise exception NOT_FOUND;                                         # Execution will never reach this point, but the compiler doesn't know that log::fatal doesn't return.
                                                 };
                current_frame_number            =  hostwindow_info.current_frame_number;
                seconds_per_frame               =  hostwindow_info.seconds_per_frame;

                done_extra_redraw_request_this_frame
                                                =  hostwindow_info.done_extra_redraw_request_this_frame;

                # We expect the following fns to capture the above values:
                # that is why we define them here rather than more globally.

                #


                fun set__needs_redraw_request__flag (i: gt::Gadget_Imp_Info)
                    =
                    {
                        i.needs_redraw_request :=  TRUE;
                    };

                #################################################################################
                # space_to_gui interface fns:

                fun note_widget_site'
                      {
                        id:                     Id,
                        subwindow_or_view:      gt::Subwindow_Or_View,                                                  # A widget can be located either directly on a subwindow, or via a scrollport (which is ultimately visible on a subwindow, possibly via aother scrollports).
                        site:                   g2d::Box,
                        me:                     gt::Guiboss_State
                      }                                                                                                 # PUBLIC.
                    =
                    {
                        case (idm::get (*me.gadget_imps,  id))
                            #
                            THE i =>    {   if (site != *i.site                                                         # Has the window site of this widget changed?
                                            or   (not (same_id ( gtj::subwindow_or_view_id_of    subwindow_or_view,
                                                                 gtj::subwindow_or_view_id_of *i.subwindow_or_view
                                                ) )   )        )
                                                #                                                                       # Yes.
                                                i.site :=  site;                                                        # Note site for widget.

                                                i.subwindow_or_view :=  subwindow_or_view;                              # Note pixmap for widget.

                                                set__needs_redraw_request__flag i;
                                            fi;
                                        };
                            NULL => ();                                                                                 # We'll assume this was from a queued (stale) message from a now-dead widget, and silently ignore it.
                        esac;
                    };

                #
                fun note_widget_site                                                                                    # PUBLIC.
                      {
                        id:                     Id,
                        subwindow_or_view:      gt::Subwindow_Or_View,                                                  # A widget can be located either directly on a subwindow, or via a scrollport (which is ultimately visible on a subwindow, possibly via aother scrollports).
                        site:                   g2d::Box
                      }
                    =   
                    # This fn is called by
                    #
                    #   src/lib/x-kit/widget/space/sprite/spritespace-imp.pkg
                    #   src/lib/x-kit/widget/space/object/objectspace-imp.pkg
                    #   src/lib/x-kit/widget/space/widget/widgetspace-imp.pkg  
                    #
                    # when they assign a widget a new site in response to our call
                    #
                    #     guiboss_to_widgetspace.pass_re_siting_done_flag
                    #
                    put_in_mailqueue  (guiboss_q,
                        #
                        \\ ({ me, ... }: Runstate)
                            =
                            note_widget_site' { id, subwindow_or_view, site, me }
                    );

                space_to_gui =        { id => hostwindow_for_gui.id,                                                    # Since each hostwindow has a unique id and we will have only one space_to_gui per hostwindow,
                                        #                                                                               # using hostwindow_for_gui.id here ensures a unique id per space_to_gui.
                                        note_widget_site
                                      };



                #################################################################################
                # resite_and_redraw
                #

                fun resite_and_redraw
                     (
                        me:                     gt::Guiboss_State,
                        window_site:            g2d::Window_Site,
                        subwindow_info:         gt::Subwindow_Data,
                        guipane:                gt::Guipane,
                        hostwindow_info:                gt::Hostwindow_Info
                     )
                    =
                    {   window_site ->  ({ size => { high => hostwindow_high, wide => hostwindow_wide }, ... }: g2d::Window_Site);
                        #
                        my (high, wide)
                            =
                            case subwindow_info
                                #
                                gt::SUBWINDOW_DATA r
                                    =>
                                    {   (*r.pixmap).size -> { high, wide };
                                        (high, wide);
                                    };
                            esac;

                        site =  { col => 0,  high,                                                                      # Allocate all of window pixel area to widgets in guipane.rg_widget widget-tree.
                                  row => 0,  wide
                                }
                                : g2d::Box;

                        apply  note_hint  (idm::keyvals_list  widget_layout_hints)                                      # Add them to our global collection of layout hints.
                            where
                                widget_layout_hints                                                                     # Collect gt:::Widget_Layout_Hint values for all widgets in new guipane.
                                    =
                                    gwl::gather_widget_layout_hints { me, guipane };

                                fun note_hint
                                      ( id:             Id,
                                        hint:           gt::Widget_Layout_Hint
                                      )
                                    =
                                    me.widget_layout_hints :=  idm::set (*me.widget_layout_hints, id, hint);
                            end;

                        sites = gwl::lay_out_guipane                                                                    # Assign to each widget in given widget-tree a pixel-rectangle on which to draw itself, in window coordinates.
                                  {
                                    me,
                                    site,                                                                               # This is the available window rectangle to divide between our widgets.
                                    rg_widget           => guipane.rg_widget,                                           # This is the tree of widgets -- possibly a single leaf widget.
                                    subwindow_info      => guipane.subwindow_info,
                                    widget_layout_hints => *me.widget_layout_hints
                                  };

                        apply   do_site (idm::vals_list sites)
                                where
                                    fun do_site (widget_site_info: gwl::Widget_Site_Info)
                                        =
                                        {   widget_site_info ->  { id, subwindow_or_view, site     };
                                            note_widget_site'    { id, subwindow_or_view, site, me };                   # Sets 'needs_redraw_request' flag for widget if its site has changed.
                                        };
                                end;

                        gtj::guipane_apply                                                                              # If a view pixmap is too small to fill its scrollport there will be undefined pixels showing in the scrollport.
                          (                                                                                             # By setting the origin to its default 0,0 we trigger the logic to black out these undefined areas.
                            guipane,                                                                                    # Does doing so result in a double-draw of views at GUI startup?  If so, that might someday prove problematic: XXX QUERO FIXME
                            [ gtj::RG_SCROLLPORT_FN
                                (\\ (arg: gt::Rg_Scrollport)
                                    =
                                    {   (*arg.scroller).set_scrollport_upperleft
                                            { row => 0, col => 0 };
                                    }
                                )
                            ]
                          );
                    };

                #################################################################################
                # widget_to_guiboss interface fns:
                #

                fun note_widget_layout_hint
                      {
                        id:                     Id,
                        widget_layout_hint:     gt::Widget_Layout_Hint
                      }
                    :                           Void
                    =
                    put_in_mailqueue  (guiboss_q,
                        #
                        \\ ({ me, ... }: Runstate)
                            =
                            {   case (idm::get (*me.gadget_imps,  id))
                                    #
                                    THE gadget_imp_info
                                        =>
                                        {
                                            need_re_layout_and_redraw
                                                =
                                                case  (idm::get (*me.widget_layout_hints, id))
                                                    #
                                                    NULL                =>  TRUE;
                                                    THE old_layout_hint =>  widget_layout_hint != old_layout_hint;
                                                esac;

                                            if need_re_layout_and_redraw
                                                #
                                                me.widget_layout_hints
                                                    :=
                                                    idm::set   (*me.widget_layout_hints, id, widget_layout_hint);

                                                case (gtj::find__guipane__containing_gadget  gadget_imp_info)
                                                    #
                                                    THE guipane
                                                        =>
                                                        guipane.needs_layout_and_redraw := TRUE;

                                                    NULL => ();                                                         # We'll assume this was a queued (stale) message from a now-dead gadget, and silently ignore it.
                                                esac;
                                            fi;
                                        };

                                    NULL => ();
                                esac;
                            }
                    );


                #################################################################################
                # gadget_to_guiboss interface fns:
                #
                fun needs_redraw_gadget_request                                                                         # PUBLIC.
                      (
                        id:             Id
                      )
                    =   
                    # The point of this call is to alert us that the
                    # GUI display needs refreshing.
                    # If no widget calls this, we can stop the frame-
                    # redisplay cycle to conserve CPU cycles:
                    #
                    {
                        put_in_mailqueue  (guiboss_q,
                            #
                            \\ (runstate as { me, imports, ... }:       Runstate)
                                =
                                case (idm::get (*me.gadget_imps,  id))
                                    #
                                    THE i =>    if *done_extra_redraw_request_this_frame
                                                    #
                                                    set__needs_redraw_request__flag i;                                          # This is the "normal" codepath -- we just remember that this gadget needs a redraw. We'll send it a redraw_gadget_request next time "frameclock" nudges us to run display_one_frame().
                                                else
                                                    #                                                                           # This is a special codepath intended to reduce user-input response latency in the common case of only one mouseclick or such per frametime (10-100 ms).  For background see Note[3].
                                                    done_extra_redraw_request_this_frame := TRUE;                               # Throttle this codepath to at most once per "frameclock" tick, to prevent runaway gadgets from overwhelming the CPU+GPU with redraw_gadget() calls.
                                                    #
                                                    i -> { guiboss_to_gadget, site, gadget_mode, needs_redraw_request, ... };

                                                    guiboss_to_gadget.redraw_gadget_request                                     # Give the gadget an instant redraw_gadget_request() in response to its needs_redraw_gadget_request() call to us, instead of making it wait until start of next frame (== next time "frameclock" microthread wakes up).
                                                      {
                                                        frame_number        =>  *current_frame_number,
                                                        site                =>  *site,
                                                        duration_in_seconds =>  0.0,
                                                        gadget_mode         =>  *gadget_mode,
                                                        theme               =>  imports.theme,
                                                        popup_nesting_depth =>  gpj::popup_nesting_depth_of_gadget (id, me)
                                                      };
                                                fi;
                                    NULL => ();                                                                                 # We'll assume this was a queued (stale) message from a now-dead gadget, and silently ignore it.
                                esac
                        );
                    };

                fun draw_gadget (clip_box, gui_displaylist, gadget_imp_info: gt::Gadget_Imp_Info)
                    =
                    {
                        gui_displaylist = [ gd::CLIP_TO (clip_box, gui_displaylist) ];                                  # Clip gadget's displaylist to its assigned site so it won't traspass on neighboring gadgets if it gets sloppy.
                        #
                        gadget_to_rw_pixmap
                                =
                                gtj::gadget_to_rw_pixmap__of  *gadget_imp_info.subwindow_or_view;                       # Find gadget's assigned off-screen backing pixmap.

                        gadget_to_rw_pixmap.draw_displaylist  gui_displaylist;                                          # Draw updated gadget appearance into its off-screen backing-pixmap home site.

                                                                                                                        # Now to update on-screen image of gadget (if it is visible).
                                                                                                                        #
                                                                                                                        # We'll update the gadget on the visible window by doing a rectangular blit
                                                                                                                        # from offscreen backing pixmap to on-screen pixel refresh buffer.
                                                                                                                        #
                                                                                                                        # Just redrawing gui_displaylist a second time would be another strategy.
                                                                                                                        # The blit has the advantage that the worst case is pretty fast, whereas
                                                                                                                        # the worst case for redrawing gui_displaylist can be arbitrarily slow.
                                                                                                                        #
                                                                                                                        # Also, draw-offscreen-and-blit completely eliminates redraw flicker
                                                                                                                        # where "flicker" == partly-redrawn widget being visible momentarily:
                                                                                                                        # it is essentially a primitive form of double-buffering.

                        from_box =  *gadget_imp_info.site;                                                              # Where should we copy pixels from, on gadget's home pixmap?  We initialize this to the full site for the gadget; later it may get clipped by scrollports.
                        pixmap   =  *gadget_imp_info.subwindow_or_view;                                                 
                        #               
                        gpj::update_offscreen_parent_pixmaps_and_then_hostwindow
                            #
                            (pixmap, from_box, hostwindow_for_gui);
                    };

                #
                fun redraw_gadget                                                                                       # Update gadget appearance in response to a guiboss_to_gadget.redraw_gadget_request {...} call.
                      {
                        id:                     Id,
                        site:                   g2d::Box,                                                               # This should be the 'site' value handed to Guiboss_To_Gadget.redraw_gadget_request: guiboss_imp uses this value to detect (and discard) stale redraw_gadget messages.
                        displaylist:            gd::Gui_Displaylist,
                        point_in_gadget:        Null_Or( g2d::Point -> Bool )                                           # Optional function deciding if (e.g.) a mouseclick location is within the gadget. This allows more geometric accuracy than a simple bounding box or such.
                      }
                    =
                    # The point of this call is to update the appearance
                    # of the gadget.  This call is normally made in response
                    # to  guiboss_to_gadget.redraw_gadget_request {}                                                    # Guiboss_To_Gadget     is from   src/lib/x-kit/widget/gui/guiboss-types.pkg
                    #
                    {
#     fg = pp::prettyprint_to_string [] {. gd::prettyprint_gui_displaylist #pp displaylist; };
#     print ("\narrowbutton: foreground:\n" + fg + "\n");
#
# { t = pp::prettyprint_to_string [] {. gd::prettyprint_gui_displaylist #pp displaylist; };
#   nb {. ("redraw_gadget displaylist: " + t); };
# };

                        put_in_mailqueue  (guiboss_q,
                            #
                            \\ ({ me, ... }: Runstate)
                                =
                                case (idm::get (*me.gadget_imps,  id))
                                    #
                                    THE i =>    {   i.point_in_gadget := point_in_gadget; 

                                                    if (site == *i.site)                                                # The point of this test is to discard stale redraws to sites no longer valid, to keep them from corrupting the display. 
                                                        #                                                               # This can happen (for example) if we do a re-layout with some redraws for the old layout in our input mailqueue.
                                                        draw_gadget (*i.site, displaylist, i);
                                                    fi;
                                                };
                                    NULL => ();                                                                         # We'll assume this was a queued (stale) message from a now-dead gadget, and silently ignore it.
                                esac
                        );
                    };



                fun pass_guipane_size
                        #
                        (id:            Id)
                        (replyqueue:    Replyqueue)
                        (reply_handler: g2d::Size -> Void)
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( g2d::Size );
                        #
                        put_in_mailqueue  (guiboss_q,
                            #
                            \\ ({ me, ... }: Runstate)
                                =
                                case (idm::get (*me.gadget_imps,  id))
                                    #
                                    THE i =>    case  (gtj::find__guipane__containing_gadget  i)
                                                    #
                                                    THE guipane =>
                                                        {
                                                            subwindow_info =  gtj::subwindow_info_of_subwindow_data  guipane.subwindow_info;
                                                            #
                                                            guipane_size =  (*subwindow_info.pixmap).size;

                                                            put_in_oneshot (reply_oneshot, guipane_size);
                                                        };  

                                                    NULL => {   msg = "pass_guipane_size: find__guipane__containing_gadget returned NULL.";
                                                                log::fatal msg;
                                                                raise exception DIE msg;
                                                            };
                                                esac;
                                    NULL => ();                                                                         # We'll assume this was a queued (stale) message from a now-dead gadget, and silently ignore it.
                                esac
                        );

                        put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
                    };

                fun set_guipane_size                                                                                                                            # Gadget request to  change  value of size for guipane containing gadget.
                      (
                        id:                     Id,
                        requested_size:         g2d::Size
                      ) 
                    =
                    put_in_mailqueue  (guiboss_q,
                        #
                        \\ ({ me, imports, ... }: Runstate)
                            =
                            case (idm::get (*me.gadget_imps,  id))
                                #
                                THE i =>        case  (gtj::find__guipane__containing_gadget  i)
                                                #
                                                THE guipane =>
                                                    {
                                                        subwindow_info =  gtj::subwindow_info_of_subwindow_data  guipane.subwindow_info;
                                                        #
                                                        old_size      = (*subwindow_info.pixmap).size;
                                                        old_upperleft =  *subwindow_info.upperleft;

                                                        case subwindow_info.parent
                                                            #
                                                            THE (gt::SUBWINDOW_DATA parent_subwindow_info)
                                                                =>
                                                                {   parent_size = (*parent_subwindow_info.pixmap).size;
                                                                    #
                                                                    parent_upperleft_in_basewindow_coordinates
                                                                        =
                                                                        gtj::subwindow_info_upperleft_in_base_window_coordinates
                                                                            #
                                                                            parent_subwindow_info;
                                                                    #
                                                                    my { new_upperleft, new_size }                                                                              # Select actual site for popup.  We need it to fit entirely within parent.
                                                                        =
                                                                        size_subwindow_entirely_within_parent
                                                                          {
                                                                            parent_size,
                                                                            old_upperleft,
                                                                            old_size    =>  requested_size
                                                                          };

                                                                    if (new_size != (*subwindow_info.pixmap).size)
                                                                        #
                                                                        old_pixmap = *subwindow_info.pixmap;
                                                                        new_pixmap = imports.guiboss_to_guishim.make_rw_pixmap new_size;                                        # XXX SUCKO FIXME blocking here sucks. Probably guiboss_to_guishim should have a pass_new_rw_pixmap() or such.

                                                                        subwindow_info.pixmap := new_pixmap;
                                                                        old_pixmap.free_rw_pixmap ();                                                                           # XXX QUERO FIXME Does this block also?

                                                                        guipane.needs_layout_and_redraw := TRUE;

                                                                        # We also need to redraw any background exposed by the
                                                                        # guipane shrinking along one/both axes during resize:
                                                                        #
                                                                        newly_exposed_background                                                                                # This list may be empty.  That's ok.
                                                                            =
                                                                            g2d::box::subtract_box_b_from_box_a
                                                                              {
                                                                                a =>  g2d::box::make (parent_upperleft_in_basewindow_coordinates + old_upperleft, old_size),
                                                                                b =>  g2d::box::make (parent_upperleft_in_basewindow_coordinates + new_upperleft, new_size)
                                                                              }; 

                                                                        apply  do_exposed_background_box  newly_exposed_background
                                                                            where
                                                                                fun do_exposed_background_box (box: g2d::Box)
                                                                                    =
                                                                                    gpj::refresh_hostwindow_rectangle (hostwindow_info, box);
                                                                            end;
                                                                    fi;
                                                                };

                                                            NULL =>
                                                                {   msg = "Cannot move basewindow around on hostwindow! -- setguipane_upperleft in guiboss-imp.pkg";
                                                                    log::fatal msg;
                                                                    raise exception DIE msg;
                                                                };
                                                        esac;
                                                    };  

                                                NULL => {   msg = "set_guipane_upperleft: find__guipane__containing_gadget returned NULL.";                                     # Should we be silently ignoring this one too...?
                                                            log::fatal msg;
                                                            raise exception DIE msg;
                                                        };
                                            esac;
                                NULL => ();                                                                                                                                     # We'll assume this was a queued (stale) message from a now-dead gadget, and silently ignore it.
                            esac
                    );

                fun pass_guipane_upperleft
                        #
                        (id:            Id)
                        (replyqueue:    Replyqueue)
                        (reply_handler: g2d::Point -> Void)
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( g2d::Point );
                        #
                        put_in_mailqueue  (guiboss_q,
                            #
                            \\ ({ me, ... }: Runstate)
                                =
                                case (idm::get (*me.gadget_imps,  id))
                                    #
                                    THE i =>    case  (gtj::find__guipane__containing_gadget  i)
                                                    #
                                                    THE guipane =>
                                                        {
                                                            subwindow_info =  gtj::subwindow_info_of_subwindow_data  guipane.subwindow_info;
                                                            #
                                                            guipane_upperleft =  *subwindow_info.upperleft;

                                                            put_in_oneshot (reply_oneshot, guipane_upperleft);
                                                        };  

                                                    NULL => {   msg = "pass_guipane_upperleft: find__guipane__containing_gadget returned NULL.";
                                                                log::fatal msg;
                                                                raise exception DIE msg;
                                                            };
                                                esac;
                                    NULL => ();                                                                                 # We'll assume this was a queued (stale) message from a now-dead gadget, and silently ignore it.
                                esac
                        );

                        put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
                    };

                fun set_guipane_upperleft                                                                                       # Gadget request to  change  value of Subwindow_Info.upperleft for guipane containing gadget.
                      (
                        id:             Id,
                        old_upperleft:  g2d::Point
                      ) 
                    =
                    put_in_mailqueue  (guiboss_q,
                        #
                        \\ (runstate: Runstate)
                            =
                            case (idm::get (*me.gadget_imps,  id))
                                #
                                THE i =>    case  (gtj::find__guipane__containing_gadget  i)
                                                #
                                                THE guipane =>
                                                    {
                                                        subwindow_info =  gtj::subwindow_info_of_subwindow_data  guipane.subwindow_info;
                                                        #
                                                        old_size =  (*subwindow_info.pixmap).size;

                                                        case subwindow_info.parent
                                                            #
                                                            THE (gt::SUBWINDOW_DATA parent_subwindow_info)
                                                                =>
                                                                {   parent_size = (*parent_subwindow_info.pixmap).size;
                                                                    #
                                                                    parent_upperleft_in_basewindow_coordinates
                                                                        =
                                                                        gtj::subwindow_info_upperleft_in_base_window_coordinates
                                                                            #
                                                                            parent_subwindow_info;

                                                                    my { new_upperleft, new_size }                                                                              # Select actual site for popup.  We want it to fit entirely within parent.
                                                                        =
                                                                        position_subwindow_entirely_within_parent
                                                                          {
                                                                            parent_size,
                                                                            old_upperleft,
                                                                            old_size
                                                                          };

                                                                    if (new_size != (*subwindow_info.pixmap).size)
                                                                        #
                                                                        msg = "Fatal: subwindow does not fit in parent?!  -- set_guipane_upperleft in guiboss-imp.pkg.";
                                                                        log::fatal msg;
                                                                        raise exception DIE msg;
                                                                    fi;

                                                                    old_upperleft             = *subwindow_info.upperleft;
                                                                    subwindow_info.upperleft :=  new_upperleft;

                                                                    old_site = g2d::box::make (parent_upperleft_in_basewindow_coordinates + old_upperleft, old_size);
                                                                    new_site = g2d::box::make (parent_upperleft_in_basewindow_coordinates + new_upperleft, new_size);

                                                                    affected_rectangle
                                                                        =
                                                                        g2d::bounding_box
                                                                          #
                                                                          (     (g2d::box::to_points old_site)                                                                  # We need to redraw both where the subwindow was and where it now is.
                                                                          @ (g2d::box::to_points new_site)
                                                                          );

                                                                    gpj::refresh_hostwindow_rectangle (hostwindow_info, affected_rectangle);
                                                                };

                                                            NULL =>
                                                                {   msg = "Cannot move basewindow around on hostwindow! -- setguipane_upperleft in guiboss-imp.pkg";
                                                                    log::fatal msg;
                                                                    raise exception DIE msg;
                                                                };
                                                        esac;
                                                    };  

                                                NULL => {   msg = "set_guipane_upperleft: find__guipane__containing_gadget returned NULL.";
                                                            log::fatal msg;
                                                            raise exception DIE msg;
                                                        };
                                            esac;
                                NULL => ();                                                                                                                                     # We'll assume this was a queued (stale) message from a now-dead gadget, and silently ignore it.
                            esac
                    );



        
                #
                fun request_keyboard_focus                                                                                                                                      # At any given time at most one gadget has the keyboard focus.  This call lets gadgets request the keyboard focus.
                      (
                        id:             Id                                                                                                                                      # Id of gadget requesting keyboard focus.
                      )
                    =   
                    {   put_in_mailqueue  (guiboss_q,
                            #
                            \\ ({ me, imports, ... }: Runstate)
                                =
                                case (idm::get (*me.gadget_imps,  id))
                                    #
                                    THE new =>  {   case *me.keyboard_focus                                                                                                     # If another gadget had the keyboard focus, tell it that it has lost the keyboard focus.
                                                        #
                                                        THE old                                                                                                                 # 'old' previously             had the keyboard focus.
                                                            =>
                                                            if  (not (same_id ( new.guiboss_to_gadget.id,                                                                       # Ignore calls which just set keyboard focus to value it already has.
                                                                                     old.guiboss_to_gadget.id
                                                                )    )             )
                                                                #
                                                                old.guiboss_to_gadget.note_keyboard_focus  (FALSE, imports.theme);                                              # Tell 'old' that it no longer has the keyboard focus.
                                                                new.guiboss_to_gadget.note_keyboard_focus  (TRUE,  imports.theme);                                              # Tell 'new' that it now       has the keyboard focus.
                                                                #                                                                                                               #
                                                                me.keyboard_focus :=  THE new;                                                                                  # Remember which gadget now    has the keyboard focus.
                                                            fi;

                                                        NULL =>                                                                                                                 # No gadget                    had the keyboard focus.
                                                            {   new.guiboss_to_gadget.note_keyboard_focus  (TRUE,  imports.theme);                                              # Tell 'new' that it now       has the keyboard focus.
                                                                #                                                                                                               #
                                                                me.keyboard_focus :=  THE new;                                                                                  # Remember which gadget now    has the keyboard focus.
                                                            };
                                                    esac;
                                                };
                                    NULL => ();                                                                                                                                 # We'll assume this was a queued (stale) message from a now-dead gadget, and silently ignore it.
                                esac
                        );
                    };                                                                                                                                                          # XXX SUCKO FIXME We need some mechanism to do   me.keyboard_focus := NULL;   if/when we close down that gadget.


                #
                fun release_keyboard_focus                                                                                                                                      # At any given time at most one gadget has the keyboard focus.  This call lets gadgets relinquish the keyboard focus.
                      (
                        id:             Id                                                                                                                                      # Id of gadget relinquishing keyboard focus.
                      )
                    =   
                    {   put_in_mailqueue  (guiboss_q,
                            #
                            \\ ({ me, imports, ... }: Runstate)
                                =
                                case (idm::get (*me.gadget_imps,  id))
                                    #
                                    THE new =>  {   case *me.keyboard_focus                                                                                                     # 
                                                        #
                                                        THE old                                                                                                                 # We think 'old'               has the keyboard focus.
                                                            =>
                                                            if  (same_id ( new.guiboss_to_gadget.id,                                                                    # If gadget does not have the keyboard focus, ignore the call.
                                                                                     old.guiboss_to_gadget.id
                                                                )             )
                                                                #
                                                                old.guiboss_to_gadget.note_keyboard_focus  (FALSE, imports.theme);                                              # Tell it    that it no longer has the keyboard focus.
                                                                me.keyboard_focus :=  NULL;                                                                                     # Remember no gadget now       has the keyboard focus.
                                                            fi;

                                                        NULL => ();                                                                                                             # No gadget                    had the keyboard focus.
                                                    esac;
                                                };
                                    NULL => ();                                                                                                                                 # We'll assume this was a queued (stale) message from a now-dead gadget, and silently ignore it.
                                esac
                        );
                    };                                                                                                                                                          # XXX SUCKO FIXME We need some mechanism to do   me.keyboard_focus := NULL;   if/when we close down that gadget.


                #
                fun note_changed_gadget_activity                                                                                                                                # PUBLIC.
                      {
                        id:             Id,
                        is_active:      Bool
                      }
                    =   
                    # The point of this call is to mark gadget as not
                    # eligible for user input.  It will often be drawn grayed-out.
                    #
                    {   put_in_mailqueue  (guiboss_q,
                            #
                            \\ ({ me, ... }: Runstate)
                                =
                                case (idm::get (*me.gadget_imps,  id))
                                    #
                                    THE i =>    { (*i.gadget_mode) -> { is_active => _, has_mouse_focus, has_keyboard_focus };
                                                    i.gadget_mode  := { is_active,      has_mouse_focus, has_keyboard_focus };
                                                };
                                    NULL => ();                                                                                                                                 # We'll assume this was a queued (stale) message from a now-dead gadget, and silently ignore it.
                                esac
                        );
                    };


                fun wake_me                                                                                                                                                     # Used to schedule guiboss_to_gadget.wakeup calls.
                      {
                        id:             Id,
                        options:        List( gt::Wake_Me_Option )
                      }
                    =
                    # The point of this call is to set up (or cancel)
                    # wakeup calls to a given widget.
                    #
                    {
                        put_in_mailqueue  (guiboss_q,
                            #
                            \\ (runstate: Runstate)
                                =
                                case (idm::get (*me.gadget_imps,  id))
                                    #
                                    THE i =>    apply  do_option  options
                                                    where
                                                        fun do_option (option: gt::Wake_Me_Option)
                                                            =
                                                            case option
                                                                #
                                                                gt::AT_FRAME_N      NULL                        =>  i.at_frame_n        :=  NULL;
                                                                gt::EVERY_N_FRAMES  NULL                        =>  i.every_n_frames    :=  NULL;
                                                                #
                                                                gt::AT_FRAME_N     (THE (at_frame, wakeup_fn))  =>  i.at_frame_n        :=  THE { at_frame, wakeup_fn };
                                                                gt::EVERY_N_FRAMES (THE (n,        wakeup_fn))  =>  i.every_n_frames    :=  THE { n,        wakeup_fn, next => REF (*current_frame_number + n) };
                                                            esac;
                                                    end;
                                    NULL => ();                                                                                         # We'll assume this was a queued (stale) message from a now-dead gadget, and silently ignore it.
                                esac
                        );
                    };


                fun shut_down_guiboss ():       Void
                    =
                    {
                        put_in_mailqueue  (guiboss_q,
                            #
                            \\ (runstate: Runstate)
                                =
                                shut_down_guiboss' runstate
                        );
                    };

                fun get_guipiths ()                                                                                                     # See Note[1] in src/lib/x-kit/widget/gui/translate-guipane-to-guipith.pkg
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( (Int, idm::Map( gt::Xi_Hostwindow_Info )) );
                        #
                        put_in_mailqueue  (guiboss_q,
                            #
                            \\ ({ me, ... }: Runstate)
                                =
                                {   guipiths =  rtx::guipanes_to_guipiths  me;                                                          # Could take awhile; possibly this should be  pass_guipiths  instead of  get_guipiths...
                                    version  =  *me.gui_update_count;   
                                    #
                                    put_in_oneshot (reply_oneshot, (version, guipiths));
                                }
                        );

result =
                        get_from_oneshot  reply_oneshot;
# nb {. "========================================="; };
# nb {. "get_guipiths pprinting original guipanes:"; };
# gtj::pprint_hostwindows (me, *me.hostwindows);
result;
                    };

                fun resite_and_redraw_all_hostwindows
                      (
                        me:     gt::Guiboss_State
                      )
                    =
                    apply  do_hostwindow  (idm::vals_list *me.hostwindows)
                    where
                        fun do_hostwindow
                              (
                                hostwindow_info:                gt::Hostwindow_Info
                              )
                            =
                            {
                                hostwindow_info
                                  ->
                                  { guiboss_to_hostwindow:                      gtg::Guiboss_To_Hostwindow,
                                    subwindow_info:                             Ref( Null_Or( gt::Subwindow_Data ) ),
                                    ...
                                  };

                                fun do_subwindow_data (subwindow_data: gt::Subwindow_Data)
                                    =
                                    {   subwindow_data -> gt::SUBWINDOW_DATA subwindow_info;
                                        #
                                        subwindow_info -> { id:                 Id,
                                                            guipane:            Ref( Null_Or( gt::Guipane ) ),
                                                            pixmap:             Ref( g2p::Gadget_To_Rw_Pixmap ),                        # Main backing store for this running gui.
                                                            popups:             Ref(List(gt::Subwindow_Data)),                          # These will all be SUBWINDOW_INFO, so 'Ref(List(Subwindow_Info))' would be a better type here.
                                                            parent:             Null_Or( gt::Subwindow_Data ),                          # For popups this points to the parent; for the original non-popup window it is NULL.
                                                            stacking_order:     Int,                                                    # Assigned in increasing order starting at 1;  these determine who overlies who visually on the screen in case of overlaps. (Popups must be entirely within parent, but sibling popups can overlap.)
                                                            upperleft:          Ref(g2d::Point)                                         # If we have a parent, this gives our location on it. Note that pixmap.size gives our size.
                                                          };

                                        window_site =  guiboss_to_hostwindow.get_window_site ();

                                        case *guipane
                                            #
                                            THE guipane
                                                =>
                                                resite_and_redraw (me, window_site, subwindow_data, guipane, hostwindow_info);

                                            NULL => ();
                                        esac;

                                        apply do_subwindow_data *popups;
                                    };

                                case *subwindow_info
                                    #
                                    THE subwindow_data
                                        =>
                                        do_subwindow_data  subwindow_data;

                                    NULL => ();
                                esac;
                            };
                    end;


                fun install_updated_guipiths
                      (
                        version:                        Int,
                        updated_guipiths:               idm::Map( gt::Xi_Hostwindow_Info )                                              # Update guiboss_imp's running gui per supplied Guipith, which should be a suitably edited version of return value from get_guipiths. See Note[1] in src/lib/x-kit/widget/gui/translate-guipane-to-guipith.pkg
                      ) 
                    =                                                                                                                   # See Note[1] in src/lib/x-kit/widget/gui/translate-guipane-to-guipith.pkg
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Bool );
                        #
                        put_in_mailqueue  (guiboss_q,
                            #
                            \\ ({ me, imports, ... }: Runstate)
                                =
                                {
                                    if (version != *me.gui_update_count)
                                        #
                                        put_in_oneshot (reply_oneshot, FALSE);                                                          # Some other client updated the GUI topology first;  client will have to retry its  get_guipiths -> mutate ->  install_updated_guipiths  sequence.
                                    else
# nb {. "====================================================="; };
# nb {. "install_updated_guipiths pprinting original guipanes:"; };
# gtj::pprint_hostwindows (me, *me.hostwindows);                                                                                                # I'm leaving this debug call in place (commented out) to save myself the effort of rememberhing how to do it next time I need it.

                                        guipanes =      rtx::guipiths_to_guipanes                                                       # This call also shuts down all widget (etc) imps used in *me.hostwindows but not new guipanes, and drops them from *me.gadget_imps, *me.widget_layout_hints, *me.objectspace_imps, *me.spritespace_imps, *me.widgetspace_imps.
                                                      (
                                                        me,
                                                        updated_guipiths,
                                                        imports.guiboss_to_guishim,
                                                        gpj::clear_box_in_pixmap,
                                                        gpj::update_offscreen_parent_pixmaps_and_then_hostwindow
                                                      )
                                                    :
                                                    idm::Map( gt::Hostwindow_Info );

                                        me.hostwindows :=  guipanes;
# nb {. "===================================================="; };
# nb {. "install_updated_guipiths pprinting updated guipanes:"; };
# gtj::pprint_hostwindows (me, *me.hostwindows);

                                        resite_and_redraw_all_hostwindows  me;                                                          # Obviously, we can add logic here to redraw only changed popups, or only changed parts of them, if performance becomes a problem.
                                                                                                                                        # Until it does, I'm sticking with simple here.
                                        put_in_oneshot (reply_oneshot, TRUE);                                                           # Notify caller that GUI update was successful.

                                        me.gui_update_count := *me.gui_update_count + 1;
                                    fi;
                                }
                        );

                        get_from_oneshot  reply_oneshot;
                    };


                fun make_popup                                                                                                          # PUBLIC. Create popup pane on given window in given site.  Given site is adjusted to lie entirely within parent (if necessary) and returned.
                      (
                        requested_site:         g2d::Box,
                        guiplan:                gt::Guiplan
                      )
                    : (
                        g2d::Box,
                        gt::Client_To_Guiwindow
                      )
                    =
                    {   gui_startup_complete' =  make_oneshot_maildrop():  Oneshot_Maildrop( gt::Client_To_Guiwindow );
                        #
                        actual_site_and_subwindow_info'
                            =
                            make_oneshot_maildrop():  Oneshot_Maildrop( (g2d::Box, gt::Subwindow_Data) );
                        #
                        put_in_mailqueue  (guiboss_q,                                                                                   # make_popup() is intended to be called by widget code, so our first task is to transition from caller's microthread to the guiboss-imp microthread.
                            #
                            \\ (runstate as { me, imports, ... }: Runstate)                                                             # Once into the body of this fn we are running in the guiboss-imp microthread.
                                =
                                {
                                    (make_subwindow_info_for_popup                                                                      # Make a new rw_pixmap, wrap it in a SUBWINDOW_DATA, enter latter into the SUBWINDOW_DATA popup hierarchy for this gui.
                                      (
                                        imports.guiboss_to_guishim.make_rw_pixmap,                                                      # To allocate the actual rw_pimap for the Subwindow_Or_View.
                                        hostwindow_info.next_stacking_order,                                                            # To allocate a 'stacking_order' value for Subwindow_Or_View.
                                        subwindow_info,                                                                                 # Our parent Subwindow_Or_View.
                                        requested_site                                                                                  # Where to put popup on parent Subwindow_Or_View.
                                      )
                                    ) -> (actual_site, subwindow_info);

                                    put_in_oneshot                                                                                      # Pass actual site of popup back to calling microthread, plus backing pixmap for popup GUI.
                                      (
                                        actual_site_and_subwindow_info',
                                        (actual_site, subwindow_info)
                                      );

                                    start_gui'
                                      (
                                        runstate:                       Runstate,
                                        #       
                                        hostwindow_for_gui:             gtg::Guiboss_To_Hostwindow,
                                        subwindow_info:                 gt::Subwindow_Data,
                                        guiplan:                        gt::Guiplan,

                                        gui_startup_complete':          Oneshot_Maildrop( gt::Client_To_Guiwindow ),
                                        guiboss_q:                      Guiboss_Q,
                                        kill_gui:                       (gt::Guipane, gt::Hostwindow_Info) -> Void
                                      );
                                }
                        );

                        (get_from_oneshot  actual_site_and_subwindow_info')
                            ->
                            (actual_site, subwindow_info);                                                                              # Read actual site of popup back from guiboss-imp microthread, also backing pixmap for popup GUI.

                        client_to_guiwindow =  get_from_oneshot  gui_startup_complete';                                                 # Wait until popup startup is complete.  We do this in caller's thread to reduce risk of lockup -- caller is typically a button that can afford to sleep a bit.

                        ( actual_site,
                          client_to_guiwindow                                                                                           # Return port to the running popup.
                        );
                    };


                guipaneref = REF (NULL: Null_Or(gt::Guipane));                                                                          # Another skanky little hack to resolve cyclic dependencies.  We set guipaneref just below, immediately after creating guipane, and never change it thereafter.
                #
                fun kill_popup ()
                    =
                    case *guipaneref            
                        #
                        THE guipane =>  kill_gui (guipane, hostwindow_info);
                        #
                        NULL    =>  {   msg = "guipaneref NULL in kill_gui! -- guiboss-imp.pkg";
                                        log::fatal msg;
                                        raise exception DIE msg;
                                    };
                    esac;



                ###################################################################################
                # These next three allow guiboss clients to use us as a
                # blackboard to publish arbitrary values, at the cost
                # of some typesafety.  Used for example in
                #
                #     src/lib/x-kit/widget/edit/millboss-imp.pkg

                fun note_global (global: Crypt)
                    =
                    {   put_in_mailqueue  (guiboss_q,
                            #
                            \\ ({ me, imports, ... }: Runstate)
                                =
                                globals__global :=   sm::set (*globals__global, global.type, global)
                        );
                    };

                fun find_global (type: String)                                                                  # 'type' should be the Crypt.type string for the desired Crypt.
                    =
                    sm::get (*globals__global, type);                                                           # Doing this in client microthread reduces risk of deadlock. There's no obvious synchronization risk since we do a single read and get a value that we could have gotten via the usual in-imp-microthread approach.

                fun drop_global (type: String)                                                                  # 'type' should be the Crypt.type string for the Crypt to be dropped.
                    =
                    {
                        put_in_mailqueue  (guiboss_q,
                            #
                            \\ ({ me, imports, ... }: Runstate)
                                =
                                globals__global :=   sm::drop (*globals__global, type)
                        );
                    };




                gadget_to_guiboss =       { id => hostwindow_for_gui.id,                                                                        # Since each hostwindow has a unique id and we will have only one gadget_to_guiboss per hostwindow, using hostwindow_for_gui.id
                                            #                                                                                           # here ensures a unique id per gadget_to_guiboss. It also makes gadget_to_guiboss.id stable across gui stop/restart cycles.
                                            needs_redraw_gadget_request,
                                            #
                                            redraw_gadget,
                                            request_keyboard_focus,
                                            release_keyboard_focus,
                                            note_changed_gadget_activity,
                                            wake_me,
                                            #
                                            shut_down_guiboss,
                                            app_to_compileimp,
                                            #
                                            get_guipiths,
                                            install_updated_guipiths,
                                            #
                                            pass_guipane_upperleft,
                                            set_guipane_upperleft,
                                            #
                                            pass_guipane_size,
                                            set_guipane_size,
                                            #
                                            note_global,
                                            find_global,
                                            drop_global,
                                            #
                                            make_popup,
                                            kill_popup
                                          };    

                widget_to_guiboss =       { id =>  hostwindow_for_gui.id,                                                               # Since each hostwindow has a unique id and we will have only one widget_to_guiboss per hostwindow,
                                            #                                                                                           # using hostwindow_for_gui.id here ensures a unique id per widget_to_guiboss.
                                            g  =>  gadget_to_guiboss,
                                            note_widget_layout_hint
                                          };






                #################################################################################
                # frameclock microthread -- wakes us up 10 times/sec to draw a frame.
                #

                guipane =   gtr::guiplan_to_guipane                                                                                     # Starts up all widget-imps plus the object- sprite- and widgetspace imps, and
                                {                                                                                                       # populates our spritespace_imps, objectspace_imps, widgetspace_imps and gadget_imps maps.
                                  run_gun',
                                  subwindow_info,
                                  me,
                                  widget_to_guiboss,
                                  gadget_to_guiboss,
                                  guiboss_to_guishim                                    =>  imports.guiboss_to_guishim,
                                  hostwindow_for_gui,
                                  space_to_gui,
                                  clear_box_in_pixmap                                   =>  gpj::clear_box_in_pixmap,
                                  update_offscreen_parent_pixmaps_and_then_hostwindow   =>  gpj::update_offscreen_parent_pixmaps_and_then_hostwindow
                                }
                                guiplan;

                guipaneref :=  THE guipane;

                if (not we_are_a_popup_gui)                                                                                             # One frameclock microthread per hostwindow is quite sufficient, so we avoid starting extra ones up each time we start up a secondary (popup) gui.
                    #
                    make_thread'  [ THREAD_NAME "frameclock" ]  frameclock  end_gun'                                                    # Start up frameclock thread which tells us when it is time to draw a new frame.
                        where
                            fun display_one_frame ({ me, imports, to, guiboss_to_millboss, ... }: Runstate)                             # THIS FUNCTION RUNS IN THE REGULAR GUIBOSS_IMP MICROTHREAD, NOT THE "frameclock" MICROTHREAD.
                                =
                                {
                                    current_frame_number := *current_frame_number + 1;
                                    #
                                    done_extra_redraw_request_this_frame        := FALSE;                                               # See Note[3].

                                    guiboss_to_millboss.do_one_frame  *current_frame_number;

                                    imps =  idm::vals_list  *me.gadget_imps;

                                    apply' imps  {.
                                        #
                                        #imp -> { guiboss_to_gadget,
                                                  site,
                                                  gadget_mode,
                                                  needs_redraw_request,
                                                  sent__initialize_gadget,
                                                  subwindow_or_view,
                                                  pixmaps,
                                                  ...
                                                };

                                        if (not *sent__initialize_gadget  and  *site != g2d::box::zero)                                 # If *site==g2d::box::zero then widgetspace_imp has not yet done layout and we cannot yet call initialize_gadget or redraw_gadget_request, since both require a valid site.
                                            #
                                            fun make_rw_pixmap (size: g2d::Size):  g2p::Gadget_To_Rw_Pixmap
                                                =
                                                {   rw_pixmap = imports.guiboss_to_guishim.make_rw_pixmap( size );
                                                    #
                                                    subwindow_info
                                                        =
                                                        gtj::subwindow_info_of_subwindow_or_view
                                                            #
                                                            *subwindow_or_view;

                                                    pixmaps :=  im::set ( *pixmaps,
                                                                          id_to_int  rw_pixmap.id,
                                                                          rw_pixmap
                                                                        );
                                                    #
                                                    rw_pixmap;  
                                                };

                                            guiboss_to_gadget.initialize_gadget   { site                => *site,
                                                                                    theme               =>  imports.theme,
                                                                                    get_font            =>  hostwindow_for_gui.get_font,
                                                                                    pass_font           =>  hostwindow_for_gui.pass_font,
                                                                                    make_rw_pixmap
                                                                                  };

                                            sent__initialize_gadget := TRUE;
                                            needs_redraw_request    := TRUE;
                                        fi;
                                    };

                                    apply' imps  {.
                                        #imp -> { guiboss_to_gadget, site, gadget_mode, needs_redraw_request, at_frame_n, every_n_frames, ... };
                                            #

                                        if (*site != g2d::box::zero)                                                                    # If *site==g2d::box::zero then widgetspace_imp has not yet done layout and we cannot yet call redraw_gadget_request because it requires a valid site.
                                            #
                                            case *at_frame_n
                                                #
                                                THE { at_frame:     Int,
                                                      wakeup_fn:    gt::Wakeup_Arg -> Void
                                                    }
                                                    =>
                                                    if (*current_frame_number == at_frame)
                                                        #
                                                        guiboss_to_gadget.wakeup
                                                          {
                                                            wakeup_arg  =>  { frame_number => *current_frame_number },
                                                            wakeup_fn
                                                          };

                                                    elif (*current_frame_number  > at_frame)
                                                        #
                                                        at_frame_n := NULL;
                                                    fi;

                                                NULL => ();
                                            esac;

                                            case *every_n_frames
                                                #
                                                THE { n:            Int,
                                                      next:         Ref(Int),
                                                      wakeup_fn:    gt::Wakeup_Arg -> Void
                                                    }
                                                    =>
                                                    if  (*current_frame_number >= *next)
                                                        #
                                                        guiboss_to_gadget.wakeup
                                                          {
                                                            wakeup_arg => { frame_number =>  *current_frame_number },
                                                            wakeup_fn
                                                          };

                                                        next := *current_frame_number + n;
                                                    fi;

                                                NULL => ();
                                            esac;

                                            if (*needs_redraw_request)
                                                #
                                                guiboss_to_gadget.redraw_gadget_request
                                                  {
                                                    frame_number        =>  *current_frame_number,
                                                    site                =>  *site,
                                                    #
                                                    duration_in_seconds =>  0.0,
                                                    gadget_mode         =>  *gadget_mode,
                                                    theme               =>  imports.theme,
                                                    popup_nesting_depth =>  gpj::popup_nesting_depth_of_gadget (guiboss_to_gadget.id, me)
                                                  };

                                                needs_redraw_request := FALSE;
                                            fi;
                                        fi;
                                    };

                                    # Start re-layout-and-redraw of any running guis which need one:
                                    #
                                    apply' (idm::vals_list *me.hostwindows)
                                        #
                                        (\\ (hostwindow_info:  gt::Hostwindow_Info) = {
                                            #
                                            gtj::all_guipanes_on_hostwindow_apply  hostwindow_info
                                                #
                                                (\\ (guipane: gt::Guipane) = {
                                                    #
                                                    if(*guipane.needs_layout_and_redraw)
                                                        guipane.needs_layout_and_redraw := FALSE;

                                                        my { high, wide }
                                                            =
                                                            case guipane.subwindow_info
                                                                #
                                                                gt::SUBWINDOW_DATA r
                                                                    =>
                                                                    (*r.pixmap).size;
                                                            esac;

                                                        site =  { col => 0,  high,                                                                      # Allocate all of window pixel area to widgets in guipane.rg_widget widget-tree.
                                                                  row => 0,  wide
                                                                }
                                                                : g2d::Box;


                                                        sites = gwl::lay_out_guipane                                                                    # Assign to each widget in given widget-tree a pixel-rectangle on which to draw itself, in window coordinates.
                                                                  {
                                                                    me,
                                                                    site,                                                                               # This is the available window rectangle to divide between our widgets.
                                                                    rg_widget           => guipane.rg_widget,                                           # This is the tree of widgets -- possibly a single leaf widget.
                                                                    subwindow_info      => guipane.subwindow_info,
                                                                    widget_layout_hints => *me.widget_layout_hints
                                                                  };

                                                        apply   do_site (idm::vals_list sites)
                                                                where
                                                                    fun do_site (widget_site_info: gwl::Widget_Site_Info)
                                                                        =
                                                                        {   widget_site_info ->  { id, subwindow_or_view, site     };
                                                                            note_widget_site'    { id, subwindow_or_view, site, me };                   # Sets 'needs_redraw_request' flag for widget if its site has changed.
                                                                        };
                                                                end;
                                                    fi;
                                                });
                                        });
                                };


# XXX SUCKO FIXME we should probably just use the 50HZ timeslicing clock.
                            # This fn provides the body for a little microthread which just
                            # loops ten times a second telling our main thread to disply frame:
                            #
                            fun frameclock end_gun'                                                                                                     # THIS FUNCTION RUNS IN THE "frameclock" MICROTHREAD.
                                =
                                loop ()
                                where
                                    count = REF 19;
                                    #
                                    fun loop ()
                                        =
                                        {
                                            do_one_mailop [
                                                #
                                                end_gun'
                                                    ==>
                                                    {.
                                                        thread_exit { success => TRUE };
                                                    },
                                                #
                                                timeout_in' *seconds_per_frame
                                                    ==>
                                                    {.
                                                        put_in_mailqueue  (guiboss_q, display_one_frame);
                                                    }
                                            ];

                                            loop ();
                                        };
                                end;
                        end;
                    ();
                fi;
            end;                                                                                                                                                # fun restart_gui'

        #
        fun startup   (id: Id,   reply_oneshot:  Oneshot_Maildrop( (Me_Slot, Exports) ))   ()                                   # Root fn of imp microthread.  Note currying.
            =
            {   me_slot  =  make_mailslot  ()   :  Me_Slot;
                #
                (make_end_gun ()) ->  { end_gun'     =>  guiboss_done',
                                        fire_end_gun =>  fire__guiboss_done
                                      };

                client_to_guiboss =   { id,
                                        make_hostwindow,
                                        start_gui,
                                        get_sprite_theme,
                                        get_object_theme,
                                        get_widget_theme,
                                        guiboss_done'
                                      };

                millboss_to_guiboss = { id,                                                                                     # Exported interface for use by    src/lib/x-kit/widget/edit/millboss-imp.pkg
                                        shut_down_guiboss
                                      };

                to          =  make_replyqueue();
                #
                put_in_oneshot (reply_oneshot, (me_slot, { client_to_guiboss }));                                               # Return value from guiboss_egg'().

                (take_from_mailslot  me_slot)                                                                                   # Imports from guiboss_egg'().
                    ->
                    { me, guiboss_arg, imports, run_gun', end_gun' };



                compileimp_egg = ci::make_compileimp_egg [];                                                                    # Set up millboss_imp, our delegate responsible for central
                #                                                                                                               # coordination of emacs-flavored text editing functionality,
                (compileimp_egg())                                                                                              # such as tracking all active textmills, editpanes etc.
                    ->                                                                                                          #
                    (compileimp_exports, compileimp_egg');                                                                      # We should probably have a more generic mechanism (like an Option IMP_TO_START?) for starting up things like millboss, rather than this special-case hack.
                #                                                                                                               # 
                compileimp_egg' ({ }, run_gun', end_gun');

                compileimp_exports ->  { app_to_compileimp, guiboss_to_compileimp };                                            # 
 


                millboss_egg = mbi::make_millboss_egg [];                                                                       # Set up millboss_imp, our delegate responsible for central
                #                                                                                                               # coordination of emacs-flavored text editing functionality,
                (millboss_egg())                                                                                                # such as tracking all active textmills, editpanes etc.
                    ->                                                                                                          #
                    (millboss_exports, millboss_egg');                                                                          # We should probably have a more generic mechanism (like an Option IMP_TO_START?) for starting up things like millboss, rather than this special-case hack.
                #                                                                                                               # 
                millboss_egg' ({ millboss_to_guiboss, app_to_compileimp }, run_gun', end_gun');

                millboss_exports ->  { guiboss_to_millboss };                                                                   # 
 


                block_until_mailop_fires  run_gun';                                                                             # Wait for the starting gun.

                run ( guiboss_q,                                                                                                # Will not return.
                      { id,
                        me,
                        guiboss_arg,
                        imports,
                        guiboss_to_millboss,
                        guiboss_to_compileimp,
                        app_to_compileimp,
                        to,
                        end_gun',
                        fire__guiboss_done
                      }
                );
            }
            where
                guiboss_q     =  make_mailqueue (get_current_microthread()):  Guiboss_Q;



                #################################################################################
                # guiboss interface fns::
                #
                #

                fun make_guievent_sink
                      (
                        hostwindow_info:                gt::Hostwindow_Info,
                        saved_events:           Ref (List( (a2r::Envelope_Route, evt::x::Event) ) )                             # Somewhere for initial_guievent_sink to save any events it gets.
                      )
                    =   
                    guievent_sink
                    where
                        fun guievent_sink (arg as (route: a2r::Envelope_Route, event: evt::x::Event))                           # The production Gui_Event handling routine, which locks in the value of 'hostwindow_info'.
                            =                                                                                                   # This will run in client's thread.
                            put_in_mailqueue  (guiboss_q,                                                                       # Incoming Gui_Event values MUST be run through the guiboss_q to guarantee mutual exclusion on access to internal guiboss datastructures.
                                #
                                \\ (runstate as { me, imports, ... }: Runstate)                                                 # Now we're running in our own thread, with mutual exclusion and access to our core datastructures.
                                    =
                                    {
                                        case *hostwindow_info.subwindow_info
                                            #
                                            NULL => {   saved_events := arg ! *saved_events;                                    # When no GUI is running, all we can do is save user input for later processing. (Or maybe discard it?)
printf "guievent_sink()/guiboss: received '%s' Gui_Event but saved it because *hostwindow_info.guipane is NULL.    -- guiboss-imp.pkg\n"
    (gts::gui_event_to_string event);
                                                    };
                                            THE (gt::SUBWINDOW_DATA r)
                                                =>
                                                case *r.guipane
                                                    #
                                                    NULL => {   saved_events := arg ! *saved_events;                                    # When no GUI is running, all we can do is save user input for later processing. (Or maybe discard it?)
printf "guievent_sink()/guiboss: received '%s' Gui_Event but saved it because *hostwindow_info.guipane is NULL.    -- guiboss-imp.pkg\n"
            (gts::gui_event_to_string event);
                                                            };

                                                    THE guipane
                                                         =>     {
                                                                case *saved_events
                                                                    #
                                                                    [] =>   ged::dispatch_event (arg, me, imports.theme, hostwindow_info);

                                                                    _  =>   {                                                                           # Woops, we have prior events that came in before we were ready to process them.
                                                                                saved_events := arg ! *saved_events;                                    # Add latest event to saved-events list.
                                                                                events = reverse *saved_events;                                         # Reverse saved-events list so we process them in first-in-first-out order.
                                                                                saved_events := [];                                                     # Clear saved-events so  we don't process any of them twice.
                                                                                apply guievent_sink events;                                             # Recursively process all saved events in order.  NB: Any new events that arrive during this will just accumulate on guiboss_q. That's fine.
                                                                            };                                                                          # 
                                                                esac;
                                                            };

                                                esac;
                                        esac;
                                    }                                                                                                                   # In-private-thread part of fun guievent_sink.
                            );                                                                                                                          # fun guievent_sink
                    end;

                fun shut_down_guiboss ():       Void                                                                                                    # PUBLIC.
                    =
                    {
                        put_in_mailqueue  (guiboss_q,
                            #
                            \\ (runstate: Runstate)
                                =
                                shut_down_guiboss' runstate
                        );
                    };

                fun make_hostwindow  (hints:    gtg::Hostwindow_Hints)                                                                                  # PUBLIC.
                    :                           gtg::Guiboss_To_Hostwindow
                    =                                                                                                                                   # This will run in client's thread.
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( gtg::Guiboss_To_Hostwindow );
# reply_oneshot is NOT locked into guievent_sink.
                        #
                        put_in_mailqueue  (guiboss_q,
                            #
                            \\ ({ me, imports, ... }: Runstate)                                                                                         # Now we're running in our own thread, with mutual exclusion and access to our core datastructures.
                                =
                                {   # We have to do an awkward little dance here because we
                                    # must hand a guievent_sink() TO make_hostwindow() but we
                                    # want guievent_sink() to lock in the 'hostwindow' result
                                    # FROM make_hostwindow():

# saved_events IS directly locked into guievent_sink 
                                    saved_events = REF ([]: List( (a2r::Envelope_Route, evt::x::Event) ) );                                             # Somewhere for initial_guievent_sink to save any events it gets.

# guievent_sink_fn is NOT directly locked into  guievent_sink()  (but it is indirectly locked via hostwindow_info -> guiboss_to_hostwindow).
                                    guievent_sink_fn
                                        =
                                        REF initial_guievent_sink
                                        where
                                            fun initial_guievent_sink (arg as (route: a2r::Envelope_Route, event: evt::x::Event))                       # An initial version which just saves events in 'saved_events' until we're ready to process them, since we don't yet have 'hostwindow' available.
                                                =                                                                                                       # This will run in client's thread.
                                                saved_events :=  arg ! *saved_events;
                                        end;


                                    guiboss_to_hostwindow
                                        =
                                        imports.guiboss_to_guishim.make_hostwindow                                                                      # XXX SUCKO FIXME we're blocking until we get the result from guishim.
                                            #
                                            (hints, guievent_sink_wrapper)
                                            where
                                                fun guievent_sink_wrapper (arg as (route: a2r::Envelope_Route, event: evt::x::Event))                   # A wrapper which initially calls initial_guievent_sink but almost immediately switches to calling the production guievent_sink.
                                                    =                                                                                                   # This will run in client's thread.
                                                    *guievent_sink_fn  arg;
                                            end;


                                    hostwindow_info       =    { guiboss_to_hostwindow,                                                                 # Remember our handle for the hostwindow.
                                                                subwindow_info                          =>   REF NULL,                                  # Remember that we do not yet have a GUI running on the hostwindow.
                                                                #
                                                                current_frame_number                    =>   REF 1,                                     # We count frames for convenience of widgets and debugging.
                                                                seconds_per_frame                       =>   REF 0.01,                                  # Let's initially assume a nominal ten frames per second. Passed to widgets primarily so widgets can do motion blurring if they wish.
                                                                #
                                                                done_extra_redraw_request_this_frame    =>   REF FALSE,
                                                                #
                                                                next_stacking_order                     =>   REF 2
                                                              };

                                    me.hostwindows                                                                                                      # Remember that we have a new hostwindow to manage.
                                        :=
                                        idm::set( *me.hostwindows,
                                                  guiboss_to_hostwindow.id,
                                                  hostwindow_info
                                                );

                                    guievent_sink
                                        =
                                        make_guievent_sink (hostwindow_info, saved_events);

                                    guievent_sink_fn  :=  guievent_sink;                                                                                # Switch guievent_sink_wrapper() over from using initial_guievent_sink() to using guievent_sink().


                                    put_in_oneshot (reply_oneshot, guiboss_to_hostwindow);

                                    imports.theme.guiboss_to_hostwindow :=  THE guiboss_to_hostwindow;                                                  # So widget-theme-imp.pkg can make  guiboss_to_hostwindow.get_font()  calls.

                                }                                                                                                                       # In-private-thread part of fun make_hostwindow.
                        );                                                                                                                              # put_in_mailqueue call.

                        get_from_oneshot  reply_oneshot;
                    };                                                                                                                                  # fun make_hostwindow

                #
                fun kill_gui
                      (
                        guipane:                gt::Guipane,                                                                                    # Private
                        hostwindow_info:                gt::Hostwindow_Info
                      )
                      : Void
                    =   
                    {
                        put_in_mailqueue  (guiboss_q,
                            #
                            \\ (runstate: Runstate)
                                =
                                kill_gui' (runstate, (guipane, hostwindow_info, TRUE))
                        );

                        ();
                    };
                #
                fun start_gui                                                                                                                           # PUBLIC.
                      (
                        hostwindow_for_gui:     gtg::Guiboss_To_Hostwindow,
                        guiplan:                gt::Guiplan
                      )
                    : (Void -> gt::Client_To_Guiwindow)
                    =   
                    {   gui_startup_complete' =  make_oneshot_maildrop():  Oneshot_Maildrop( gt::Client_To_Guiwindow );
                        #
                        put_in_mailqueue  (guiboss_q,
                            #
                            \\ (runstate: Runstate)
                                =
                                {   subwindow_info =  gtj::make_base_subwindow_data  hostwindow_for_gui.subwindow_or_view;
                                    #
                                    start_gui' (runstate, hostwindow_for_gui, subwindow_info, guiplan, gui_startup_complete', guiboss_q, kill_gui);
                                }
                        );

                        {.   get_from_oneshot  gui_startup_complete';   };
                    };

                #
                fun get_sprite_theme ()                                                                                                                 # PUBLIC.
                    =   
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( bt::Gui_To_Sprite_Theme );
                        #
                        put_in_mailqueue  (guiboss_q,
                            #
                            \\ ({ imports, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, imports.gui_to_sprite_theme)
                        );

                        get_from_oneshot reply_oneshot;
                    };
                #
                fun get_object_theme ()                                                                                                                 # PUBLIC.
                    =   
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( ct::Gui_To_Object_Theme );
                        #
                        put_in_mailqueue  (guiboss_q,
                            #
                            \\ ({ imports, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, imports.gui_to_object_theme)
                        );

                        get_from_oneshot reply_oneshot;
                    };
                #
                fun get_widget_theme ()                                                                                                                 # PUBLIC.
                    =   
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( wt::Widget_Theme );
                        #
                        put_in_mailqueue  (guiboss_q,
                            #
                            \\ ({ imports, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, imports.theme)
                        );

                        get_from_oneshot reply_oneshot;
                    };
            end;

        #
        fun process_options (options: List(Guiboss_Option), { name, id })
            =
            {   my_name                 =  REF name;
                my_id                   =  REF id;
                #
                apply  do_option  options
                where
                    fun do_option (MICROTHREAD_NAME     n) =>   my_name                 :=  n;
                        do_option (ID                   i) =>   my_id                   :=  i;
                    end;
                end;

                { name                  =>  *my_name,
                  id                    =>   *my_id
                };
            };


        ##########################################################################################
        # PUBLIC.
        #
        fun make_guiboss_egg
              (guiboss_arg:             Guiboss_Arg)                                                                                                    # PUBLIC. PHASE 1: Construct our state and initialize from 'options'.
            =
            {   guiboss_arg ->  (guiboss_options);                                                                                                      # Currently no guiboss_needs component, so this is a no-op.
                #
                (process_options
                  ( guiboss_options,
                    { name              => "guiboss",
                      id                =>  id_zero
                    }
                ) )
                    ->
                    { name,
                      id
                    };
        
                my (id, guiboss_options)
                    =
                    if (id_to_int(id) == 0)
                        id = issue_unique_id();                                                                                                         # Allocate unique imp id.
                        (id, ID id ! guiboss_options);                                                                                                  # Make our id stable across stop/restart cycles.
                    else
                        (id, guiboss_options);
                    fi;

                guiboss_arg = (guiboss_options);                                                                                                        # Currently no guiboss_needs component, so this is a no-op.

                me =  {
                        gui_update_count        =>   REF 0,
                        hostwindows             =>   REF idm::empty,                                                                                    # Track all hostwindows created by our make_hostwindow() entrypoint.
                        mouse_is                =>   REF gt::CROSSING_NONGADGET,                                                                        # Mouse is not currently dragging, and in fact not currently known to be on any particular widget.
                        last_button_changed     =>   REF evt::button1,                                                                                  # Track last button clicked, so we can pass it to drag_fn clients. (evt::Motion_Xevtinfo contain no 'mouse_button' field, unlike evt::Button_Xevtinfo values.) Cnoice of initial value does not matter.
                        keyboard_focus          =>   REF (NULL:  Null_Or( gt::Gadget_Imp_Info )),                                                       # Gadget currently holding keyboard focus, if any.
                        #
                        spritespace_imps        =>  (REF idm::empty): gt::Spritespace_Imps,                                                             # Holds our gt::Guiboss_To_Spritespace instances.
                        objectspace_imps        =>  (REF idm::empty): gt::Objectspace_Imps,                                                             # Holds our gt::Guiboss_To_Objectspace instances.
                        widgetspace_imps        =>  (REF idm::empty): gt::Widgetspace_Imps,                                                             # Holds our gt::Guiboss_To_Widgetspace instances.
                        gadget_imps             =>  (REF idm::empty): gt::Gadget_Imps,                                                                  # 
                        widget_layout_hints     =>  (REF idm::empty): gt::Widget_Layout_Hints                                                           # 
                      };

                \\ () = {   reply_oneshot = make_oneshot_maildrop():  Oneshot_Maildrop( (Me_Slot, Exports) );                                           # PUBLIC. PHASE 2: Start our microthread and return our Exports to caller.
                            #
                            xlogger::make_thread  name  (startup  (id, reply_oneshot));                                                                 # Note that startup() is curried.

                            (get_from_oneshot  reply_oneshot) -> (me_slot, exports);

                            fun phase3                                                                                                                  # PUBLIC. PHASE 3: Accept our Imports, then wait for Run_Gun to fire.
                                (
                                  imports:      Imports,
                                  run_gun':     Run_Gun,        
                                  end_gun':     End_Gun
                                )
                                =
                                {
                                    put_in_mailslot  (me_slot, { me, guiboss_arg, imports, run_gun', end_gun' });
                                };

                            (exports, phase3);
                        };
            };
    };

end;


##########################################################################
# Note[2]  Pop-up Design Considerations:
#
# o As a practical matter we expect typically just one
#   popup at a time, either a tooltip or a dialog, so
#   we try to avoid adding too much semantic and implementation
#   complexity in service of the remaining rarer cases.
#
#   Also, we don't worry much about the efficiency of the
#   complex cases which we expect to be vanishingly rare:
#   we use simple O(N**2) algorithms for our overlapping-
#   update cases rather than implementing and using
#   sophisticated spatial datastructures.
#
# o For simplicity and portability we do popups entirely
#   within guiboss-imp, as opposed to using one separate
#   X window per popup.  This leaves the door open to
#   ports to non-X substrates which do not naturally
#   support extra windows, perhaps OpenGL or simple
#   hardware framebuffers.
#
# o We support multiple simultaneous popups on a given
#   hostwindow because this has the simplest, most natural
#   semantics:  on a big GUI on a 30" monitor there is no
#   reason that a popup or tooltip in one corner should
#   block a popup or tooltip in another corner.
#
# o We expect each popup to fit entirely within its parent,
#   but it seems unnatural and inconvenient to insist that
#   sibling popups sharing a parent not overlap, so for
#   simplest semantics we allow such overlaps.  
#
# o For similar considerations of clean semantics we support
#   popups on popups:  We want each popup to support the same
#   GUI semantics as the underlying parent gui window.
#
# o We resolve sibling overlaps via a global stacking order,
#   with younger windows overlaying older ones.  At some point
#   we may want to support popping windows to the top of the
#   stacking order:  In that case we'll have to make stacking_order
#   fields mutuable and trigger a redraw after updating them.
#
# o I do not see any need to have pop-ups belong to viewables and
#   thus be partially visible through a scrollport.  The point
#   of a popup is to be attention-getting and visible, so having
#   hidden popups seems counterproductive.  Consequently we do not
#   (further!) complicate the implementation problem by requiring
#   support for that.

##########################################################################
# Note[3]  Gadget Redraw Protocol Design Considerations:
#
# A core idea here is that if a gadget updates its state say 100,000
# times a second (counting incoming ethernet packets, perhaps),
# we do NOT want it redrawing that often because it would overwhelm
# the rendering subsystem (and anyhow waste a lot of CPU + CPU time).
#
# Rather than have each gadget (most likely FAIL to) include redraw
# frequency throttling logic, we centralize this functionality here
# in guiboss_imp:
#
#  o  When a gadget updates its state, it calls                                                 # See for example   src/lib/x-kit/widget/leaf/arrowbutton.pkg
#         gadget_to_guiboss.needs_redraw_gadget_request                                         #                   
#     which results in guiboss_imp setting the gadget's
#         gadget_imp_info.needs_redraw_request := TRUE;
#
#  o  Each time guiboss_imp's   "frameclock"  thread wakes up                                   #   As a special tweak, once per frame guiboss_imp will send a
#     (10-100 times per second, say), it sends a                                                #       guiboss_to_gadget.redraw_gadget_request
#         guiboss_to_gadget.redraw_gadget_request                                               #   immediately upon receiving a
#     all to each gadget with gadget_imp_info.needs_redraw_request == TRUE;                             #       gadget_to_guiboss.needs_redraw_gadget_request
#                                                                                               #   The intention here is to reduce GUI user-response latency
#  o  When (and only when) a gadget receives a redraw_gadget_request                            #   by ten to a hundred milliseconds in the common case of only
#     call, it does a                                                                           #   one user mouselick (or other input) per frameclock tick,
#         gadget_to_guiboss.redraw_gadget                                                       #   without risking runaway redraws.
#     call to actually redraw itself.                                                           #   This mechanism is implemented via
#                                                                                               #       done_extra_redraw_request_this_frame:   Ref(Bool),
# In general it is the gadget's responsibility to call
#     gadget_to_guiboss.needs_redraw_gadget_request
# when it needs to be redrawn.  There are exactly two
# situations in which guiboss_imp will send a
#     guiboss_to_gadget.redraw_gadget_request
# without such prompting:
#
#  1) At GUI startup guiboss_imp sends a redraw_gadget_request
#     to every gadget in the GUI, to establish the initial
#     visual appearance of the GUI.
#
#  2) When a widget's assigned site changes (say, due to the                                    # Window resizing is actually not supported as of 2014-11-28
#     user resizing the window).

# Originally, to be helpful, guiboss_imp would spontaneously send a
# a redraw_gadget_request() to any gadget receiving a mouse event
# or such, but I eventually concluded that this was more confusing
# than helpful, and retreated to the above simple, easy-to-remember
# policy.  (Also, spontaneous redraw_gadget_requests() could cause
# needless redraws, which for some gadgets might be quite expensive.)
#
# Note that there is no actual logic or interlock in place to
# prevent a gadget from ignoring the 
#     gadget_to_guiboss.needs_redraw_gadget_request
#     guiboss_to_gadget.redraw_gadget_request
# handshake and simply sending
#     gadget_to_guiboss.redraw_gadget
# There may be widget implementation problems for which this is
# actually a good solution, but it should be done only occasionally,
# after careful consideration of the alternatives and risks.
#
#                                      -- CrT 2014-11-28



Comments and suggestions to: bugs@mythryl.org

PreviousUpNext