PreviousUpNext

15.4.1452  src/lib/x-kit/widget/leaf/button.pkg

## button.pkg
#
# See also:
#     src/lib/x-kit/widget/leaf/button.pkg
#     src/lib/x-kit/widget/leaf/diamondbutton.pkg
#     src/lib/x-kit/widget/leaf/roundbutton.pkg

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





###                    "If you have ambition, you might not achieve anything,
###                     but without ambition, you are almost certain
###                     not to achieve anything."
###
###                                              -- Whitfield Diffie 


# This package gets used in:
#
#     

stipulate
    include package   threadkit;                                                # threadkit                     is from   src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg
    include package   geometry2d;                                               # geometry2d                    is from   src/lib/std/2d/geometry2d.pkg
    #
    package evt =  gui_event_types;                                             # gui_event_types               is from   src/lib/x-kit/widget/gui/gui-event-types.pkg
    package g2p =  gadget_to_pixmap;                                            # gadget_to_pixmap              is from   src/lib/x-kit/widget/theme/gadget-to-pixmap.pkg
    package gd  =  gui_displaylist;                                             # gui_displaylist               is from   src/lib/x-kit/widget/theme/gui-displaylist.pkg
    package gt  =  guiboss_types;                                               # guiboss_types                 is from   src/lib/x-kit/widget/gui/guiboss-types.pkg
    package wt  =  widget_theme;                                                # widget_theme                  is from   src/lib/x-kit/widget/theme/widget/widget-theme.pkg
    package wti =  widget_theme_imp;                                            # widget_theme_imp              is from   src/lib/x-kit/widget/xkit/theme/widget/default/widget-theme-imp.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 wi  =  widget_imp;                                                  # widget_imp                    is from   src/lib/x-kit/widget/xkit/theme/widget/default/look/widget-imp.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 mtx =  rw_matrix;                                                   # rw_matrix                     is from   src/lib/std/src/rw-matrix.pkg
    package pp  =  standard_prettyprinter;                                      # standard_prettyprinter        is from   src/lib/prettyprint/big/src/standard-prettyprinter.pkg
    package gtg =  guiboss_to_guishim;                                          # guiboss_to_guishim            is from   src/lib/x-kit/widget/theme/guiboss-to-guishim.pkg

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

    package button
    :       Button                                                              # Button                        is from   src/lib/x-kit/widget/leaf/button.api
    {
        package p {                                                             # "p" for "position".
            #
            Text_Position       = TEXT_AT_LEFT
                                | TEXT_AT_RIGHT
                                | TEXT_IN_CENTER
                                ;
        };
        package t {                                                             # "t" for "type".
            #
            Button_Type         = MOMENTARY_CONTACT
                                | PUSH_ON_PUSH_OFF
                                | IGNORE_MOUSECLICKS
                                ;
        };

        App_To_Button
          =
          { id:                         Id,
            # 
            get_active:                 Void -> Bool,
            get_state:                  Void -> Bool,
            #
            get_button_relief:          Void -> wt::Relief,                     # 
            get_button_type:            Void -> t::Button_Type,                 # 
            #
            get_button_text:            Void -> Null_Or(String),
            get_button_on_text:         Void -> Null_Or(String),
            get_button_off_text:        Void -> Null_Or(String),
            #
            get_button_image:           Void -> Null_Or(mtx::Rw_Matrix( r8::Rgb8 )),
            get_button_on_image:        Void -> Null_Or(mtx::Rw_Matrix( r8::Rgb8 )),
            get_button_off_image:       Void -> Null_Or(mtx::Rw_Matrix( r8::Rgb8 )),

            set_button_text:            Null_Or(String) -> Void,
            set_button_on_text:         Null_Or(String) -> Void,
            set_button_off_text:        Null_Or(String) -> Void,
            #
            set_button_image:           Null_Or(mtx::Rw_Matrix( r8::Rgb8 )) -> Void,
            set_button_on_image:        Null_Or(mtx::Rw_Matrix( r8::Rgb8 )) -> Void,
            set_button_off_image:       Null_Or(mtx::Rw_Matrix( r8::Rgb8 )) -> Void,
            #
            set_active_to:              Bool -> Void,
            set_state_to:               Bool -> Void,                           # Also calls gadget_to_guiboss.needs_redraw_gadget_request(id);
            set_button_relief_to:       wt::Relief -> Void                      # Also calls gadget_to_guiboss.needs_redraw_gadget_request(id);
          };


        Redraw_Fn_Arg
            =
            REDRAW_FN_ARG
              {
                id:                             Id,                             # Unique Id for widget.
                doc:                            String,                         # Human-readable description of this widget, for debug and inspection.
                frame_number:                   Int,                            # 1,2,3,... Purely for convenience of widget, guiboss-imp makes no use of this.
                frame_indent_hint:              gt::Frame_Indent_Hint,
                site:                           g2d::Box,                       # Window rectangle in which to draw.
                popup_nesting_depth:            Int,                            # 0 for gadgets on basewindow, 1 for gadgets on popup on basewindow, 2 for gadgets on popup on popup, etc.
                #
                duration_in_seconds:            Float,                          # If state has changed look-imp should call note_changed_gadget_foreground() before this time is up. Also useful for motionblur.
                widget_to_guiboss:              gt::Widget_To_Guiboss,
                gadget_mode:                    gt::Gadget_Mode,
                #
                theme:                          wt::Widget_Theme,
                do:                             (Void -> Void) -> Void,         # Used by widget subthreads to execute code in main widget microthread.
                to:                             Replyqueue,                     # Used to call 'pass_*' methods in other imps.
                palette:                        wt::Gadget_Palette,
                #
                default_redraw_fn:              Redraw_Fn,
                #
                button_state:                   Bool,                           # Is the button ON or OFF?
                button_type:                    t::Button_Type,                 # Is the button push-on-push-off or momentary-contact?
                button_relief:                  wt::Relief,                     # Is the button outline a slope, a ridge, or a flat band?

                image:                          Null_Or(mtx::Rw_Matrix( r8::Rgb8 )),
                on_image:                       Null_Or(mtx::Rw_Matrix( r8::Rgb8 )),
                off_image:                      Null_Or(mtx::Rw_Matrix( r8::Rgb8 )),

                text_position:                  Null_Or(p::Text_Position),
                text:                           Null_Or(String),
                #
                fonts:                          List(String),
                font_weight:                    Null_Or(wt::Font_Weight),
                font_size:                      Null_Or(Int),

                no_box:                         Bool,
                margin:                         Int,
                thick:                          Int
              }
        withtype
        Redraw_Fn
          =
          Redraw_Fn_Arg
          ->
          { displaylist:                gd::Gui_Displaylist,
            point_in_gadget:            Null_Or(g2d::Point -> Bool),            # 
            pixels_high_min:            Int,
            pixels_wide_min:            Int
          }
          ;



        Mouse_Click_Fn_Arg
            =
            MOUSE_CLICK_FN_ARG                                                  # Needs to be a sumtype because of recursive reference in default_mouse_click_fn.
              { id:                             Id,                             # Unique Id for widget.
                doc:                            String,                         # Human-readable description of this widget, for debug and inspection.
                event:                          gt::Mousebutton_Event,          # MOUSEBUTTON_PRESS or MOUSEBUTTON_RELEASE.
                button:                         evt::Mousebutton,               # Which mousebutton was pressed/released.
                point:                          g2d::Point,                     # Where the mouse was.
                widget_layout_hint:             gt::Widget_Layout_Hint,
                frame_indent_hint:              gt::Frame_Indent_Hint,
                site:                           g2d::Box,                       # Widget's assigned area in window coordinates.
                modifier_keys_state:            evt::Modifier_Keys_State,       # State of the modifier keys (shift, ctrl...).
                mousebuttons_state:             evt::Mousebuttons_State,        # State of mouse buttons as a bool record.
                widget_to_guiboss:              gt::Widget_To_Guiboss,
                theme:                          wt::Widget_Theme,
                do:                             (Void -> Void) -> Void,         # Used by widget subthreads to execute code in main widget microthread.
                to:                             Replyqueue,                     # Used to call 'pass_*' methods in other imps.
                #
                default_mouse_click_fn:         Mouse_Click_Fn,
                #
                button_state:                   Bool,                           # Is the button ON or OFF?
                button_type:                        t::Button_Type,             # Is the button push-on-push-off or momentary-contact?
                button_relief:                  Ref(wt::Relief),                # Is the button outline a slope, a ridge, or a flat band?
                #
                initial_state:                  Bool,                           # Original state of button.
                note_state:                     Bool -> Void,                   # Change state of button. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
                needs_redraw_gadget_request:    Void -> Void                    # Notify guiboss-imp that this button needs to be redrawn (i.e., sent a redraw_gadget_request()).
              }
        withtype
        Mouse_Click_Fn = Mouse_Click_Fn_Arg -> Void;



        Mouse_Drag_Fn_Arg
            =
            MOUSE_DRAG_FN_ARG
              {
                id:                             Id,                             # Unique Id for widget.
                doc:                            String,                         # Human-readable description of this widget, for debug and inspection.
                event_point:                    g2d::Point,
                start_point:                    g2d::Point,
                last_point:                     g2d::Point,
                widget_layout_hint:             gt::Widget_Layout_Hint,
                frame_indent_hint:              gt::Frame_Indent_Hint,
                site:                           g2d::Box,                       # Widget's assigned area in window coordinates.
                phase:                          gt::Drag_Phase, 
                button:                         evt::Mousebutton,
                modifier_keys_state:            evt::Modifier_Keys_State,       # State of the modifier keys (shift, ctrl...).
                mousebuttons_state:             evt::Mousebuttons_State,        # State of mouse buttons as a bool record.
                widget_to_guiboss:              gt::Widget_To_Guiboss,
                theme:                          wt::Widget_Theme,
                do:                             (Void -> Void) -> Void,         # Used by widget subthreads to execute code in main widget microthread.
                to:                             Replyqueue,                     # Used to call 'pass_*' methods in other imps.
                #
                default_mouse_drag_fn:          Mouse_Drag_Fn,
                #
                button_state:                   Bool,                           # Is the button ON or OFF?
                button_type:                        t::Button_Type,             # Is the button push-on-push-off or momentary-contact?
                button_relief:                  Ref(wt::Relief),                # Is the button outline a slope, a ridge, or a flat band?
                #
                initial_state:                  Bool,                           # Original state of button.
                note_state:                     Bool -> Void,                   # Change state of button. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
                needs_redraw_gadget_request:    Void -> Void                    # Notify guiboss-imp that this button needs to be redrawn (i.e., sent a redraw_gadget_request()).
              }
        withtype
        Mouse_Drag_Fn =  Mouse_Drag_Fn_Arg -> Void;



        Mouse_Transit_Fn_Arg                                                    # Note that buttons are always all up in a mouse-transit event -- otherwise it is a mouse-drag event.
            =
            MOUSE_TRANSIT_FN_ARG
              {
                id:                             Id,                             # Unique Id for widget.
                doc:                            String,                         # Human-readable description of this widget, for debug and inspection.
                event_point:                    g2d::Point,
                widget_layout_hint:             gt::Widget_Layout_Hint,
                frame_indent_hint:              gt::Frame_Indent_Hint,
                site:                           g2d::Box,                       # Widget's assigned area in window coordinates.
                transit:                        gt::Gadget_Transit,             # Mouse is entering (CAME) or leaving (LEFT) widget, or moving (MOVE) across it.
                modifier_keys_state:            evt::Modifier_Keys_State,       # State of the modifier keys (shift, ctrl...).
                widget_to_guiboss:              gt::Widget_To_Guiboss,
                theme:                          wt::Widget_Theme,
                do:                             (Void -> Void) -> Void,         # Used by widget subthreads to execute code in main widget microthread.
                to:                             Replyqueue,                     # Used to call 'pass_*' methods in other imps.
                #
                default_mouse_transit_fn:       Mouse_Transit_Fn,
                #
                button_state:                   Bool,                           # Is the button ON or OFF?
                button_type:                        t::Button_Type,             # Is the button push-on-push-off or momentary-contact?
                button_relief:                  Ref(wt::Relief),                # Is the button outline a slope, a ridge, or a flat band?
                #
                initial_state:                  Bool,                           # Original state of button.
                note_state:                     Bool -> Void,                   # Change state of button. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
                needs_redraw_gadget_request:    Void -> Void                    # Notify guiboss-imp that this button needs to be redrawn (i.e., sent a redraw_gadget_request()).
              }
        withtype
        Mouse_Transit_Fn =  Mouse_Transit_Fn_Arg -> Void;



        Key_Event_Fn_Arg
            =
            KEY_EVENT_FN_ARG
              {
                id:                             Id,                             # Unique Id for widget.
                doc:                            String,                         # Human-readable description of this widget, for debug and inspection.
                keystroke:                      gt::Keystroke_Info,             # Keystring etc for event.
                widget_layout_hint:             gt::Widget_Layout_Hint,
                frame_indent_hint:              gt::Frame_Indent_Hint,
                site:                           g2d::Box,                       # Widget's assigned area in window coordinates.
                widget_to_guiboss:              gt::Widget_To_Guiboss,
                guiboss_to_widget:              gt::Guiboss_To_Widget,          # Used by textpane.pkg keystroke-macro stuff to synthesize fake keystroke events to widget.
                theme:                          wt::Widget_Theme,
                do:                             (Void -> Void) -> Void,         # Used by widget subthreads to execute code in main widget microthread.
                to:                             Replyqueue,                     # Used to call 'pass_*' methods in other imps.
                #
                default_key_event_fn:           Key_Event_Fn,
                #
                button_state:                   Bool,                           # Is the button ON or OFF?
                button_type:                        t::Button_Type,             # Is the button push-on-push-off or momentary-contact?
                button_relief:                  Ref(wt::Relief),                # Is the button outline a slope, a ridge, or a flat band?
                #
                initial_state:                  Bool,                           # Original state of button.
                note_state:                     Bool -> Void,                   # Change state of button. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
                needs_redraw_gadget_request:    Void -> Void                    # Notify guiboss-imp that this button needs to be redrawn (i.e., sent a redraw_gadget_request()).
              }
        withtype
        Key_Event_Fn =  Key_Event_Fn_Arg -> Void;



        Option  = PIXELS_SQUARE         Int
                #
                | PIXELS_HIGH_MIN       Int
                | PIXELS_WIDE_MIN       Int
                #
                | PIXELS_HIGH_CUT       Float
                | PIXELS_WIDE_CUT       Float
                #
                | INITIAL_STATE         Bool
                | INITIALLY_ACTIVE      Bool
                #
                | MOMENTARY_CONTACT                                             # State is non-default (opposite of INITIAL_STATE) only between mouse downclick and upclick.
                | PUSH_ON_PUSH_OFF                                              # Mouse downclicks toggle state between TRUE and FALSE.
                | IGNORE_MOUSECLICKS                                            # Mouseclicks to not affect state.
                #
                | BODY_COLOR                            rgb::Rgb
                | BODY_COLOR_WITH_MOUSEFOCUS            rgb::Rgb
                | BODY_COLOR_WHEN_ON                    rgb::Rgb
                | BODY_COLOR_WHEN_ON_WITH_MOUSEFOCUS    rgb::Rgb
                #
                | ID                    Id
                | DOC                   String
                #
                | RELIEF                wt::Relief                              # Should button boundary be drawn flat, raised, sunken, ridged or grooved?
                | MARGIN                Int                                     # How many pixels to inset button relative to its assigned window site.  Default is 4.
                | THICK                 Int                                     # Thickness of lines (well, polygons) forming button.  Default is 5.
                | NO_BOX                                                        # Do not draw a box around button.
                #
                | TEXT_AT_LEFT
                | TEXT_AT_RIGHT
                | TEXT_IN_CENTER
                #
                | TEXT                  String                                  # Text to draw inside button.  Default is "".
                | ON_TEXT               String                                  # Text to draw inside button when switch is ON.   Default is TEXT else "".
                | OFF_TEXT              String                                  # Text to draw inside button when switch is OFF.  Default is TEXT else "".
                #
                | FONT_SIZE             Int                                     # Show any text in this pointsize.  Default is 12.
                | FONTS                 List(String)                            # Override theme font:  Font to use for text label, e.g. "-*-courier-bold-r-*-*-20-*-*-*-*-*-*-*".  We'll use the first font in list which is found on X server, else "9x15" (which X guarantees to have).
                #
                | ROMAN                                                         # Show any text in plain  font from widget-theme.  This is the default.
                | ITALIC                                                        # Show any text in italic font from widget-theme.
                | BOLD                                                          # Show any text in bold   font from widget-theme.  NB: Text is either bold or italic, not both.
                #
                | IMAGE                 mtx::Rw_Matrix( r8::Rgb8 )              # Image to show to left of text (if any).                    Default is to draw nothing.
                | ON_IMAGE              mtx::Rw_Matrix( r8::Rgb8 )              # Image to show to left of text (if any) when switch is ON.  Default is to use IMAGE value if specified, else to draw nothing.
                | OFF_IMAGE             mtx::Rw_Matrix( r8::Rgb8 )              # Image to show to left of text (if any) when switch is ONF. Default is to use IMAGE value if specified, else to draw nothing.
                #
                | REDRAW_FN             Redraw_Fn                               # Application-specific handler for widget redraw.
                | MOUSE_CLICK_FN        Mouse_Click_Fn                          # Application-specific handler for mousebutton clicks.
                | MOUSE_DRAG_FN         Mouse_Drag_Fn                           # Application-specific handler for mouse drags.
                | MOUSE_TRANSIT_FN      Mouse_Transit_Fn                        # Application-specific handler for mouse crossings.
                | KEY_EVENT_FN          Key_Event_Fn                            # Application-specific handler for keyboard input.
                #
                | BOOL_OUT              (Bool -> Void)                          # Widget's current state              will be sent to these fns each time state changes.
                | PORTWATCHER           (Null_Or(App_To_Button) -> Void)        # Widget's app port                   will be sent to these fns at widget startup.
                | SITEWATCHER           (Null_Or((Id,g2d::Box)) -> Void)        # Widget's site in window coordinates will be sent to these fns each time it changes.
                ;                                                               # To help prevent deadlock, watcher fns should be fast and nonblocking, typically just setting a var or entering something into a mailqueue.
                
        fun process_options
            ( options: List(Option),
              #
              { button_type,
                #
                body_color,
                body_color_with_mousefocus,
                body_color_when_on,
                body_color_when_on_with_mousefocus,
                #
                widget_id,
                widget_doc,
                #
                relief,
                margin,
                thick,
                no_box,
                #
                text_position,
                text,
                on_text,
                off_text,
                #
                fonts,
                font_weight,
                font_size,
                #
                image,
                on_image,
                off_image,
                #
                redraw_fn,
                mouse_click_fn,
                mouse_drag_fn,
                mouse_transit_fn,
                key_event_fn,
                #
                initial_state,
                initially_active,
                #
                widget_options,
                #
                portwatchers,
                bool_outs,
                sitewatchers
              }
            )
            =
            {   my_button_type                          =  REF  button_type;
                #
                my_body_color                           =  REF body_color;
                my_body_color_with_mousefocus           =  REF body_color_with_mousefocus;
                my_body_color_when_on                   =  REF body_color_when_on;
                my_body_color_when_on_with_mousefocus   =  REF body_color_when_on_with_mousefocus;
                #
                my_widget_id                            =  REF  widget_id;
                my_widget_doc                           =  REF  widget_doc;
                #
                my_relief                               =  REF  relief;
                my_margin                               =  REF  margin;
                my_thick                                =  REF  thick;
                my_no_box                               =  REF  no_box;
                #
                my_text_position                        =  REF  text_position;
                my_text                                 =  REF  text;
                my_on_text                              =  REF  on_text;
                my_off_text                             =  REF  off_text;
                #
                my_fonts                                =  REF  fonts;
                my_font_weight                          =  REF  font_weight;
                my_font_size                            =  REF  font_size;
                #
                my_image                                =  REF  image;
                my_on_image                             =  REF  on_image;
                my_off_image                            =  REF  off_image;
                #
                my_redraw_fn                            =  REF  redraw_fn;
                my_mouse_click_fn                       =  REF  mouse_click_fn;
                my_mouse_drag_fn                        =  REF  mouse_drag_fn;
                my_mouse_transit_fn                     =  REF  mouse_transit_fn;
                my_key_event_fn                         =  REF  key_event_fn;
                #
                my_initial_state                        =  REF  initial_state;
                my_initially_active                     =  REF  initially_active;
                #
                my_widget_options                       =  REF  widget_options;
                #
                my_portwatchers                         =  REF  portwatchers;
                my_bool_outs                            =  REF  bool_outs;
                my_sitewatchers                         =  REF  sitewatchers;
                #

                apply  do_option  options
                where
                    fun do_option (INITIAL_STATE                        b) =>   my_initial_state        :=  b;
                        do_option (INITIALLY_ACTIVE                     b) =>   my_initially_active     :=  b;
                        #
                        do_option (MOMENTARY_CONTACT                     ) =>   my_button_type          :=  t::MOMENTARY_CONTACT;
                        do_option (PUSH_ON_PUSH_OFF                      ) =>   my_button_type          :=  t::PUSH_ON_PUSH_OFF;
                        do_option (IGNORE_MOUSECLICKS                    ) =>   my_button_type          :=  t::IGNORE_MOUSECLICKS;
                        #
                        do_option (BODY_COLOR                           c) =>   my_body_color                           :=  THE c;
                        do_option (BODY_COLOR_WITH_MOUSEFOCUS           c) =>   my_body_color_with_mousefocus           :=  THE c;
                        do_option (BODY_COLOR_WHEN_ON                   c) =>   my_body_color_when_on                   :=  THE c;
                        do_option (BODY_COLOR_WHEN_ON_WITH_MOUSEFOCUS   c) =>   my_body_color_when_on_with_mousefocus   :=  THE c;
                        #
                        do_option (ID                                   i) =>   my_widget_id            :=  THE i;
                        do_option (DOC                                  d) =>   my_widget_doc           :=      d;
                        #
                        do_option (RELIEF                               r) =>   my_relief               :=  r;
                        do_option (MARGIN                               i) =>   my_margin               :=  i;
                        do_option (THICK                                i) =>   my_thick                :=  i;
                        do_option (NO_BOX                                ) =>   my_no_box               :=  TRUE;
                        #
                        do_option (TEXT_AT_LEFT                          ) =>   my_text_position        :=  THE p::TEXT_AT_LEFT;
                        do_option (TEXT_AT_RIGHT                         ) =>   my_text_position        :=  THE p::TEXT_AT_RIGHT;
                        do_option (TEXT_IN_CENTER                        ) =>   my_text_position        :=  THE p::TEXT_IN_CENTER;
                        #
                        do_option (TEXT                                 t) =>   my_text                 :=  THE t;
                        do_option (ON_TEXT                              t) =>   my_on_text              :=  THE t;
                        do_option (OFF_TEXT                             t) =>   my_off_text             :=  THE t;
                        #
                        do_option (FONTS                                t) =>   my_fonts                :=  t;
                        #
                        do_option (ROMAN                                 ) =>   my_font_weight          :=  THE wt::ROMAN_FONT;
                        do_option (ITALIC                                ) =>   my_font_weight          :=  THE wt::ITALIC_FONT;
                        do_option (BOLD                                  ) =>   my_font_weight          :=  THE wt::BOLD_FONT;
                        #
                        do_option (FONT_SIZE                            i) =>   my_font_size            :=  THE i;
                        #
                        do_option (IMAGE                                i) =>   my_image                :=  THE i;
                        do_option (ON_IMAGE                             i) =>   my_on_image             :=  THE i;
                        do_option (OFF_IMAGE                            i) =>   my_off_image            :=  THE i;
                        #
                        do_option (REDRAW_FN                            f) =>   my_redraw_fn            :=      f;
                        do_option (MOUSE_CLICK_FN                       f) =>   my_mouse_click_fn       :=      f;
                        do_option (MOUSE_DRAG_FN                        f) =>   my_mouse_drag_fn        :=  THE f;
                        do_option (MOUSE_TRANSIT_FN                     f) =>   my_mouse_transit_fn     :=      f;
                        do_option (KEY_EVENT_FN                         f) =>   my_key_event_fn         :=  THE f;
                        #
                        do_option (PORTWATCHER                          c) =>   my_portwatchers         :=  c ! *my_portwatchers;
                        do_option (BOOL_OUT                             c) =>   my_bool_outs            :=  c ! *my_bool_outs;
                        do_option (SITEWATCHER                          c) =>   my_sitewatchers         :=  c ! *my_sitewatchers;
                        #
                        #
                        do_option (PIXELS_HIGH_MIN                      i) =>   my_widget_options       :=  (wi::PIXELS_HIGH_MIN i) ! *my_widget_options;
                        do_option (PIXELS_WIDE_MIN                      i) =>   my_widget_options       :=  (wi::PIXELS_WIDE_MIN i) ! *my_widget_options;
                        #
                        do_option (PIXELS_HIGH_CUT                      f) =>   my_widget_options       :=  (wi::PIXELS_HIGH_CUT f) ! *my_widget_options;
                        do_option (PIXELS_WIDE_CUT                      f) =>   my_widget_options       :=  (wi::PIXELS_WIDE_CUT f) ! *my_widget_options;
                        #
                        do_option (PIXELS_SQUARE                        i) =>   my_widget_options       :=  (wi::PIXELS_HIGH_MIN   i)
                                                                                                        !   (wi::PIXELS_WIDE_MIN   i)
                                                                                                        !   (wi::PIXELS_HIGH_CUT 0.0)
                                                                                                        !   (wi::PIXELS_WIDE_CUT 0.0)
                                                                                                        !   *my_widget_options;
                    end;
                end;

                { button_type                           =>  *my_button_type,
                  #
                  body_color                            =>  *my_body_color,
                  body_color_with_mousefocus            =>  *my_body_color_with_mousefocus,
                  body_color_when_on                    =>  *my_body_color_when_on,
                  body_color_when_on_with_mousefocus    =>  *my_body_color_when_on_with_mousefocus,
                  #
                  widget_id                             =>  *my_widget_id,
                  widget_doc                            =>  *my_widget_doc,
                  #
                  relief                                =>  *my_relief,
                  margin                                =>  *my_margin,
                  thick                                 =>  *my_thick,
                  no_box                                =>  *my_no_box,
                  #
                  text_position                         =>  *my_text_position,
                  text                                  =>  *my_text,
                  on_text                               =>  *my_on_text,
                  off_text                              =>  *my_off_text,
                  #
                  fonts                                 =>  *my_fonts,
                  font_weight                           =>  *my_font_weight,
                  font_size                             =>  *my_font_size,
                  #
                  image                                 =>  *my_image,
                  on_image                              =>  *my_on_image,
                  off_image                             =>  *my_off_image,
                  #
                  redraw_fn                             =>  *my_redraw_fn,
                  mouse_click_fn                        =>  *my_mouse_click_fn,
                  mouse_drag_fn                         =>  *my_mouse_drag_fn,
                  mouse_transit_fn                      =>  *my_mouse_transit_fn,
                  key_event_fn                          =>  *my_key_event_fn,
                  #
                  initial_state                         =>  *my_initial_state,
                  initially_active                      =>  *my_initially_active,
                  #
                  widget_options                        =>  *my_widget_options,
                  #
                  portwatchers                          =>  *my_portwatchers,
                  bool_outs                             =>  *my_bool_outs,
                  sitewatchers                          =>  *my_sitewatchers
                };
            };


        fun default_redraw_fn (REDRAW_FN_ARG a)                                                 # Handle a guiboss request to redraw ourself.
            =
            {   button_state    =  a.button_state;
                font_size       =  a.font_size;
                font_weight     =  a.font_weight;
                fonts           =  a.fonts;
                image           =  a.image;
                margin          =  a.margin;
                off_image       =  a.off_image;
                on_image        =  a.on_image;
                palette         =  a.palette;
                site            =  a.site;
                text            =  a.text;
                text_position   =  a.text_position;
                theme           =  a.theme;
                thick           =  a.thick;

                background_box  =  site;
                background      =  [ gd::COLOR (palette.surround_color,  [ gd::FILLED_BOXES [ background_box ]]) ];

                inner_box = g2d::box::make_nested_box (background_box, margin);                 # 

                fun pick_image_to_use ()
                    =
                    if button_state
                        #
                        case on_image
                            #
                            THE i =>  THE i;                                                    # Button is ON so use "ON" image.
                            NULL  =>  image;                                                    # Button is ON but no "ON" image so use plain image (or none).
                        esac;
                    else
                        case off_image
                            #
                            THE i =>  THE i;                                                    # Button is OFF so use "OFF" image.
                            NULL  =>  image;                                                    # Button is OFF but no "OFF" image so use plain image (or none).
                        esac;
                    fi;

                image_to_use =  pick_image_to_use ();

                fun pick_text_position_to_use ()
                    =
                    case text_position
                        #
                        THE p => p;                                                             # If programmer explicitly specified a position, use that.
                        #
                        NULL => case image_to_use
                                    #
                                    THE _ =>  p::TEXT_AT_LEFT;                                  # If we have an image, position it at the left and text right next to it.
                                    NULL  =>  p::TEXT_IN_CENTER;                                # By default, center the text on the button.
                                esac;       
                    esac;

                text_position_to_use =  pick_text_position_to_use ();

                fun image_displaylist (image:  mtx::Rw_Matrix(r8::Rgb8))
                    =
                    {   # Figure where to place 'image' within 'inner_box'.
                        # If there is no text we'll center the image within inner_box,
                        # otherwise we'll left-justify it to make room for text to its right:
                        #
                        to_point
                            =
                            case text
                                #
                                THE _ =>    {   col  = inner_box.col
                                                     + thick                                                            # Indent image by thickness of box.
                                                     + 10                                                               # Indent by an extra 10 to provide some esthetic whitespace.
                                                     ; 
                                                row  = inner_box.row + ((inner_box.high - image.rows) / 2);             # 
                                                #
                                                { row, col };
                                            };

                                NULL =>     {   col  = inner_box.col + ((inner_box.wide - image.cols) / 2);             # 
                                                row  = inner_box.row + ((inner_box.high - image.rows) / 2);             # 
                                                #
                                                { row, col };
                                            };
                            esac;

                        ilist = [ gd::IMAGE { from_box =>   NULL,                               # Use entire image.
                                              from     =>   image,
                                              to_point
                                            }
                                ];

                        ilist;
                    };

                fun get_fontnames ()
                    =
                    {   font_size_to_use
                            =
                            case font_size      THE i => i;
                                                NULL  => *theme.default_font_size;
                            esac;

                        fontname_to_use
                            =
                            case font_weight    THE wt::ROMAN_FONT  =>  *theme.get_roman_fontname  font_size_to_use;
                                                THE wt::ITALIC_FONT =>  *theme.get_italic_fontname font_size_to_use;
                                                THE wt::BOLD_FONT   =>  *theme.get_bold_fontname   font_size_to_use;
                                                NULL            =>  *theme.get_roman_fontname  font_size_to_use;
                            esac;

                        fontnames =  fonts  @  [ fontname_to_use, "9x15" ];

                        fontnames;
                    };


                fun get_text_dimensions (text: String)
                    =
                    {   g =  wti::get__guiboss_to_hostwindow  theme;
                        #
                        font = g.get_font (get_fontnames ());

                        { font_ascent      =>  font.font_height.ascent,
                          font_descent     =>  font.font_height.descent,
                          length_in_pixels =>  font.string_length_in_pixels text
                        };
                    };

                fun text_displaylist
                      (
                        text:           String,
                        text_box:       g2d::Box
                      )
                    =
                    {   text_dimensions =  get_text_dimensions  text;
                        #
                        fontnames =  get_fontnames ();

                        case text_position_to_use
                            #
                            p::TEXT_IN_CENTER
                                =>
                                {   (g2d::box::midpoint text_box)
                                        ->
                                        { row, col };

                                    row =  row - text_dimensions.font_descent + ((text_dimensions.font_ascent + text_dimensions.font_descent) / 2); 

                                    draw_point = { row, col };

                                    [ gd::COLOR ( a.palette.text_color, 
                                                  [ gd::FONT ( fontnames,
                                                               [ gd::PUT_TEXT   ( gd::CENTERED_ON_POINT,
                                                                                  [ gd::TEXT (draw_point, text) ]
                                                                                )
                                                               ]
                                                             )
                                                  ]
                                                )
                                    ];
                                };

                            p::TEXT_AT_LEFT
                                =>
                                {   box_corners =   g2d::box::box_corners  text_box;
                                    #
                                    (g2d::point::mean [ box_corners.upper_left, box_corners.lower_left ])
                                        ->
                                        { row, col };

                                    # Indent text a bit from image and also
                                    # center it properly vertically -- most
                                    # fonts have ascent > descent:
                                    #
                                    col =  col + 10;
                                    row =  row - text_dimensions.font_descent + ((text_dimensions.font_ascent + text_dimensions.font_descent) / 2); 
                                    #
                                    draw_point = { row, col };

                                    [ gd::COLOR ( a.palette.text_color, 
                                                  [ gd::FONT ( fontnames,
                                                               [ gd::PUT_TEXT   ( gd::TO_RIGHT_OF_POINT,
                                                                                  [ gd::TEXT (draw_point, text) ]
                                                                                )
                                                               ]
                                                             )
                                                  ]
                                                )
                                    ];
                                };

                            p::TEXT_AT_RIGHT                                                                    # 
                                =>
                                {   box_corners =   g2d::box::box_corners  text_box;
                                    #
                                    (g2d::point::mean [ box_corners.upper_right, box_corners.lower_right ])
                                        ->
                                        { row, col };

                                    # Indent text a bit from image and also
                                    # center it properly vertically -- most
                                    # fonts have ascent > descent:
                                    #
                                    col =  col - 10 - text_dimensions.length_in_pixels;
                                    row =  row - text_dimensions.font_descent + ((text_dimensions.font_ascent + text_dimensions.font_descent) / 2); 
                                    #
                                    draw_point = { row, col };

                                    [ gd::COLOR ( a.palette.text_color, 
                                                  [ gd::FONT ( fontnames,
                                                               [ gd::PUT_TEXT   ( gd::TO_RIGHT_OF_POINT,
                                                                                  [ gd::TEXT (draw_point, text) ]
                                                                                )
                                                               ]
                                                             )
                                                  ]
                                                )
                                    ];
                                };
                        esac;
                    };


                foreground =  [ gd::COLOR (a.palette.body_color, [ gd::FILLED_POLYGON (g2d::box::to_points inner_box) ]) ];                                     # Interior of button. We draw this first because 3D outline occupies same bounding box:

                foreground =    case a.no_box   FALSE =>  foreground @ *a.theme.pictureframe a.palette { box => inner_box, thick, relief => a.button_relief };  # 3-D outline for button.
                                                TRUE  =>  foreground;
                                esac;   

                foreground =    case image_to_use       THE i =>  foreground @ (image_displaylist i);                                                           # Maybe incorporate image into button foreground:

                                                        NULL  =>  foreground;
                                esac;

                text_indent =   case image_to_use       THE i =>  i.cols + 10;
                                                        NULL  =>            0;
                                esac;

                text_box =    { row  =>  inner_box.row,
                                col  =>  inner_box.col  + text_indent,
                                high =>  inner_box.high,
                                wide =>  inner_box.wide - text_indent
                              };

                # Maybe incorporate text into button foreground:
                #
                foreground
                    =
                    case a.text
                        #
                        NULL  => foreground;
                        #
                        THE t =>    {
                                        foreground @ (text_displaylist (t, text_box));
                                    };
                    esac;

                fun point_in_gadget (point: g2d::Point)
                    =
                    g2d::point::in_box (point, inner_box);

                point_in_gadget = THE point_in_gadget;


                { displaylist => background @ foreground,
                  point_in_gadget,
                  pixels_high_min => 0,
                  pixels_wide_min => 0
                };
            };

        fun default_mouse_click_fn (MOUSE_CLICK_FN_ARG a)
            =
            if (a.button              == evt::button1 
            and a.modifier_keys_state == evt::no_modifier_keys_were_down)
                #
                button_state                    =  a.button_state;
                button_type                     =  a.button_type;
                event                           =  a.event;
                initial_state                   =  a.initial_state;
                needs_redraw_gadget_request     =  a.needs_redraw_gadget_request;
                note_state                      =  a.note_state;
                #
                case event
                    #
                    gt::MOUSEBUTTON_PRESS
                        =>
                        if (button_type != t::IGNORE_MOUSECLICKS)       
                            #
                            note_state  (not button_state);
                            needs_redraw_gadget_request ();
                        fi;

                    gt::MOUSEBUTTON_RELEASE
                        =>
                        if (button_type == t::MOMENTARY_CONTACT)
                            #
                            note_state  initial_state;
                            needs_redraw_gadget_request ();
                        fi;
                esac;

                ();
            fi;

        fun default_mouse_transit_fn (MOUSE_TRANSIT_FN_ARG a)
            =
            case a.transit
                #
                gt::CAME =>  a.needs_redraw_gadget_request ();                                          # So button will lighten when mouse enters it.
                gt::LEFT =>  a.needs_redraw_gadget_request ();                                          # So button will revert  when mosue leaves it.
                _            =>  ();
            esac;

        fun with (options: List(Option))                                                                # PUBLIC.  The point of the 'with' name is that GUI coders can write 'button::with { this => that, foo => bar, ... }.'
            =
            {
                widget_oneshot  = make_oneshot_maildrop():  Oneshot_Maildrop( wi::Widget );

                reliefref       = REF wt::RAISED;                                                       # 
                #
                textref         = REF (NULL: Null_Or(String));
                ontextref       = REF (NULL: Null_Or(String));
                offtextref      = REF (NULL: Null_Or(String));
                #
                imageref        = REF (NULL: Null_Or(mtx::Rw_Matrix( r8::Rgb8 )));
                onimageref      = REF (NULL: Null_Or(mtx::Rw_Matrix( r8::Rgb8 )));
                offimageref     = REF (NULL: Null_Or(mtx::Rw_Matrix( r8::Rgb8 )));

                (process_options
                  (
                    options,
                    #
                    { button_type       =>      t::PUSH_ON_PUSH_OFF,
                      # 
                      body_color                         =>  NULL,
                      body_color_with_mousefocus         =>  NULL,
                      body_color_when_on                 =>  NULL,
                      body_color_when_on_with_mousefocus =>  NULL,
                      #
                      widget_id         =>  NULL,
                      widget_doc        =>  "<button>",
                      # 
                      relief            =>  *reliefref, 
                      margin            =>  4,
                      thick             =>  5,
                      no_box            =>  FALSE,
                      #
                      text_position     =>  (NULL: Null_Or(p::Text_Position)),
                      text              =>  *textref,
                      on_text           =>  *ontextref,
                      off_text          =>  *offtextref,
                      #
                      fonts             =>  [],
                      font_weight       =>  (NULL: Null_Or(wt::Font_Weight)),
                      font_size         =>  (NULL: Null_Or(Int)),
                      #
                      image             =>  *imageref,
                      on_image          =>  *onimageref,
                      off_image         =>  *offimageref,
                      #
                      redraw_fn         =>  default_redraw_fn,
                      mouse_click_fn    =>  default_mouse_click_fn,
                      mouse_drag_fn     =>  NULL,
                      mouse_transit_fn  =>  default_mouse_transit_fn,
                      key_event_fn      =>  NULL,
                      #
                      initial_state     =>  FALSE,
                      initially_active  =>  TRUE,
                      #
                      widget_options    =>  [],
                      #
                      portwatchers      =>  [],
                      bool_outs         =>  [],
                      sitewatchers      =>  []
                    }
                ) )
                    ->
                    {                                                                                           # These values are globally visible to the subsequenc fns, which can lock them in as needed.
                      button_type,
                      #
                      body_color,
                      body_color_with_mousefocus,
                      body_color_when_on,
                      body_color_when_on_with_mousefocus,
                      #
                      widget_id,
                      widget_doc,
                      # 
                      relief,
                      margin,
                      thick,
                      no_box,
                      #
                      text_position,
                      text,
                      on_text,
                      off_text,
                      #
                      fonts,
                      font_weight,
                      font_size,
                      #
                      image,
                      on_image,
                      off_image,
                      #
                      redraw_fn,
                      mouse_click_fn,
                      mouse_drag_fn,
                      mouse_transit_fn,
                      key_event_fn,
                      #
                      initial_state,
                      initially_active,
                      #
                      widget_options,
                      #
                      portwatchers,
                      bool_outs,
                      sitewatchers
                    };

                reliefref       := relief;
                #
                textref         := text;
                ontextref       := on_text;
                offtextref      := off_text;
                #
                imageref        := image;
                onimageref      := on_image;
                offimageref     := off_image;

                #######################################
                # Top of per-imp state variable section
                #

                widget_to_guiboss__global
                    =
                    REF (NULL:  Null_Or((gt::Widget_To_Guiboss, Id)));

                fun note_changed_gadget_activity (is_active: Bool)
                    =
                    case (*widget_to_guiboss__global)
                        #
                        THE (widget_to_guiboss, id)     =>  widget_to_guiboss.g.note_changed_gadget_activity { id, is_active };
                        NULL                            =>  ();
                    esac;

                fun needs_redraw_gadget_request ()
                    =
                    case (*widget_to_guiboss__global)
                        #
                        THE (widget_to_guiboss, id)     =>  widget_to_guiboss.g.needs_redraw_gadget_request(id);
                        NULL                            =>  ();
                    esac;


                last_known_site
                    =
                    REF ( { col => -1,  wide => -1,
                            row => -1,  high => -1
                          }:                            g2d::Box
                        );

                button_state  =  REF initial_state;


                button_active
                    =
                    REF initially_active;


                exception SAVED_STATE { last_known_site:        g2d::Box,                                       # Here we're doing the usual hack of using Exception as an extensible datatype -- nothing to do with actually raising or trapping exceptions.
                                        button_state:           Bool,
                                        button_active:          Bool
                                      };        


                fun note_site  (id: Id,  site: g2d::Box)
                    =
                    if(*last_known_site != site)
                        last_known_site := site;
                        #
                        apply tell_watcher sitewatchers
                            where
                                fun tell_watcher sitewatcher
                                    =
                                    sitewatcher (THE (id,site));
                            end;
                    fi;

                fun note_state (state: Bool)
                    =
                    if(*button_state != state)
                        button_state := state;
                        #
                        apply tell_watcher bool_outs
                            where
                                fun tell_watcher bool_out
                                    =
                                    bool_out state;
                            end;
                    fi;

                #
                # End of state variable section
                ###############################


                #####################
                # Top of port section
                #
                # Here we implement our App_To_Button port:

                fun set_active_to (is_active: Bool)
                    =
                    {   button_active :=  is_active;
                        #
                        note_changed_gadget_activity  is_active;
                    };

                fun set_state_to (state: Bool)
                    =
                    {   note_state state;
                        #
                        needs_redraw_gadget_request ();
                    };

                fun set_button_relief_to (relief: wt::Relief)
                    =
                    {
                        reliefref := relief;
                        #
                        needs_redraw_gadget_request ();
                    };

                fun get_active ()
                    =
                    *button_active;

                fun get_state ()
                    =
                    *button_state;

                fun get_button_relief ()
                    =
                    *reliefref;

                fun get_button_type ()
                    =
                    button_type;


                fun get_button_text      () =  *textref;
                fun get_button_on_text   () =  *ontextref;
                fun get_button_off_text  () =  *offtextref;

                fun get_button_image     () =  *imageref;
                fun get_button_on_image  () =  *onimageref;
                fun get_button_off_image () =  *offimageref;

                fun set_button_text      t  =   {   textref     := t;   needs_redraw_gadget_request (); };
                fun set_button_on_text   t  =   {   ontextref   := t;   needs_redraw_gadget_request (); };
                fun set_button_off_text  t  =   {   offtextref  := t;   needs_redraw_gadget_request (); };

                fun set_button_image     i  =   {   imageref    := i;   needs_redraw_gadget_request (); };
                fun set_button_on_image  i  =   {   onimageref  := i;   needs_redraw_gadget_request (); };
                fun set_button_off_image i  =   {   offimageref := i;   needs_redraw_gadget_request (); };


                #
                # End of port section
                #####################


                ###############################
                # Top of widget hook fn section
                #
                # These fns get called by widget_imp logic, ultimately                                          # widget_imp            is from   src/lib/x-kit/widget/xkit/theme/widget/default/look/widget-imp.pkg
                # in response to user mouseclicks and keypresses etc:

                fun startup_fn
                    { 
                      id:                               Id,                                                     # Unique Id for widget.
                      doc:                              String,                                                 # Human-readable description of this widget, for debug and inspection.
                      widget_to_guiboss:                gt::Widget_To_Guiboss,
                      do:                               (Void -> Void) -> Void,                                 # Used by widget subthreads to execute code in main widget microthread.
                      to:                               Replyqueue
                    }
                    =
                    {   widget_to_guiboss__global
                            :=  
                            THE (widget_to_guiboss, id);

                        app_to_button
                          =
                          { id,
                            #
                            get_active,
                            get_state,
                            get_button_relief,
                            get_button_type,
                            #
                            get_button_text,
                            get_button_on_text,
                            get_button_off_text,

                            get_button_image,
                            get_button_on_image,
                            get_button_off_image,

                            set_button_text,
                            set_button_on_text,
                            set_button_off_text,

                            set_button_image,
                            set_button_on_image,
                            set_button_off_image,

                            set_active_to,
                            set_state_to,
                            set_button_relief_to
                          }
                          : App_To_Button
                          ;

                        apply   tell_watcher  portwatchers                                                      # We do this here rather than (say) above this fn because we don't want the port in circulation until we're running.
                                where
                                    fun tell_watcher  portwatcher
                                        =
                                        portwatcher  (THE app_to_button);
                                end;
                        ();
                    };

                fun shutdown_fn ()                                                                              # Return to widget_imp an exception packaging up our state; this will be returned to guiboss_imp, saved in the
                    =                                                                                           # Paused_Gui tree, and passed to our startup_fn when/if gui is restarted. This exception will never be raised;
                    {   apply   tell_watcher  portwatchers                                                      # 
                                where
                                    fun tell_watcher  portwatcher
                                        =
                                        portwatcher  NULL;
                                end;

                        apply tell_watcher sitewatchers
                            where
                                fun tell_watcher sitewatcher
                                    =
                                    sitewatcher NULL;
                            end;
                    };
        
                fun initialize_gadget_fn
                    {
                      id:                               Id,                                                     # Unique Id for widget.
                      doc:                              String,                                                 # Human-readable description of this widget, for debug and inspection.
                      site:                             g2d::Box,                                               # Window rectangle in which to draw.
                      widget_to_guiboss:                gt::Widget_To_Guiboss,
                      theme:                            wt::Widget_Theme,
                      pass_font:                        List(String) -> Replyqueue
                                                                     -> (evt::Font -> Void) -> Void,            # Nonblocking version of next, for use in imps.
                       get_font:                        List(String) ->  evt::Font,                             # Accepts a list of font names which are tried in order.
                      make_rw_pixmap:                   g2d::Size -> g2p::Gadget_To_Rw_Pixmap,
                      #
                      do:                               (Void -> Void) -> Void,                                 # Used by widget subthreads to execute code in main widget microthread.
                      to:                               Replyqueue                                              # Used to call 'pass_*' methods in other imps.
                    }
                    =
                    {   note_site (id,site);
                        #
                        ();
                    };

                fun redraw_request_fn_wrapper
                    {
                      id:                               Id,                             # Unique Id for widget.
                      doc:                              String,                         # Human-readable description of this widget, for debug and inspection.
                      frame_number:                     Int,                            # 1,2,3,... Purely for convenience of widget-imp, guiboss-imp makes no use of this.
                      frame_indent_hint:                gt::Frame_Indent_Hint,
                      site:                             g2d::Box,                       # Window rectangle in which to draw.
                      popup_nesting_depth:              Int,                            # 0 for gadgets on basewindow, 1 for gadgets on popup on basewindow, 2 for gadgets on popup on popup, etc.
                      duration_in_seconds:              Float,                          # If state has changed widget-imp should call redraw_gadget() before this time is up. Also useful for motionblur.
                      widget_to_guiboss:                gt::Widget_To_Guiboss,
                      gadget_mode:                      gt::Gadget_Mode,
                      theme:                            wt::Widget_Theme,
                      do:                               (Void -> Void) -> Void,
                      to:                               Replyqueue                      # Used to call 'pass_*' methods in other imps.
                    }
                    =
                    {   note_site (id,site);
                        #
                        palette =   *theme.current_gadget_colors  { gadget_is_on => *button_state,
                                                                    gadget_mode,
                                                                    popup_nesting_depth,
                                                                    #
                                                                    body_color,
                                                                    body_color_when_on,
                                                                    body_color_with_mousefocus,
                                                                    body_color_when_on_with_mousefocus
                                                                  };

                        text    =   if *button_state
                                        #
                                        case *ontextref
                                            #
                                            THE t =>  THE t;                                                    # Button is ON so use "ON" text.
                                            NULL  =>  *textref;                                                 # Button is ON but no "ON" text so use plain text (or none).
                                        esac;
                                    else
                                        case *offtextref
                                            #
                                            THE t =>  THE t;                                                    # Button is OFF so use "OFF" text.
                                            NULL  =>  *textref;                                                 # Button is OFF but no "OFF" text so use plain text (or none).
                                        esac;
                                    fi;



                        redraw_fn_arg
                            =
                            REDRAW_FN_ARG
                              { id,
                                doc,
                                frame_number,
                                frame_indent_hint,
                                site,
                                popup_nesting_depth,
                                duration_in_seconds,
                                widget_to_guiboss,
                                gadget_mode,
                                theme,
                                do,
                                to,
                                palette,
                                #
                                default_redraw_fn,      
                                #
                                button_state    => *button_state,
                                button_type,
                                button_relief   => *reliefref,

                                image           => *imageref,
                                on_image        => *onimageref,
                                off_image       => *offimageref,

                                text_position,
                                text,
                                #
                                fonts,
                                font_weight,
                                font_size,

                                no_box,
                                margin,
                                thick
                              };

                        (redraw_fn  redraw_fn_arg)
                            ->
                            { displaylist,
                              point_in_gadget,
                              pixels_high_min,
                              pixels_wide_min
                            };

                        widget_to_guiboss.g.redraw_gadget { id, site, displaylist, point_in_gadget };
                    };


                fun mouse_click_fn_wrapper                                                                      # This a callback we hand to   src/lib/x-kit/widget/xkit/theme/widget/default/look/widget-imp.pkg
                      {
                        id:                             Id,                                                     # Unique Id for widget.
                        doc:                            String,                                                 # Human-readable description of this widget, for debug and inspection.
                        event:                          gt::Mousebutton_Event,                                  # MOUSEBUTTON_PRESS or MOUSEBUTTON_RELEASE.
                        button:                         evt::Mousebutton,
                        point:                          g2d::Point,
                        widget_layout_hint:             gt::Widget_Layout_Hint,
                        frame_indent_hint:              gt::Frame_Indent_Hint,
                        site:                           g2d::Box,                                               # Widget's assigned area in window coordinates.
                        modifier_keys_state:            evt::Modifier_Keys_State,                               # State of the modifier keys (shift, ctrl...).
                        mousebuttons_state:             evt::Mousebuttons_State,                                # State of mouse buttons as a bool record.
                        widget_to_guiboss:              gt::Widget_To_Guiboss,
                        theme:                          wt::Widget_Theme,
                        do:                             (Void -> Void) -> Void,                                 # Used by widget subthreads to execute code in main widget microthread.
                        to:                             Replyqueue                                              # Used to call 'pass_*' methods in other imps.
                      }
                    = 
                    {   note_site  (id,site);
                        #
                        mouse_click_fn_arg
                            =
                            MOUSE_CLICK_FN_ARG
                              {
                                id,
                                doc,
                                event,
                                button,
                                point,
                                widget_layout_hint,
                                frame_indent_hint,
                                site,
                                modifier_keys_state,
                                mousebuttons_state,
                                widget_to_guiboss,
                                theme,
                                do,
                                to,
                                #
                                default_mouse_click_fn,
                                #
                                button_state    => *button_state,                                               # We don't pass the refcell here because we want client code to make state changes via note_state(), which will properly notify all state-watchers.
                                button_type,
                                button_relief   =>  reliefref,
                                #
                                initial_state,
                                note_state,
                                needs_redraw_gadget_request
                              };

                        mouse_click_fn  mouse_click_fn_arg;
                    };

                fun mouse_drag_fn_wrapper                                                                       # This a callback we hand to   src/lib/x-kit/widget/xkit/theme/widget/default/look/widget-imp.pkg
                    (
                      { id:                             Id,                                                     # Unique Id for widget.
                        doc:                            String,                                                 # Human-readable description of this widget, for debug and inspection.
                        event_point:                    g2d::Point,
                        start_point:                    g2d::Point,
                        last_point:                     g2d::Point,
                        widget_layout_hint:             gt::Widget_Layout_Hint,
                        frame_indent_hint:              gt::Frame_Indent_Hint,
                        site:                           g2d::Box,                                               # Widget's assigned area in window coordinates.
                        phase:                          gt::Drag_Phase, 
                        button:                         evt::Mousebutton,
                        modifier_keys_state:            evt::Modifier_Keys_State,                               # State of the modifier keys (shift, ctrl...).
                        mousebuttons_state:             evt::Mousebuttons_State,                                # State of mouse buttons as a bool record.
                        widget_to_guiboss:              gt::Widget_To_Guiboss,
                        theme:                          wt::Widget_Theme,
                        do:                             (Void -> Void) -> Void,                                 # Used by widget subthreads to execute code in main widget microthread.
                        to:                             Replyqueue                                              # Used to call 'pass_*' methods in other imps.
                      }
                    )
                    = 
                    {   note_site  (id,site);
                        #
                        mouse_drag_fn_arg
                            =
                            MOUSE_DRAG_FN_ARG
                              {
                                id,
                                doc,
                                event_point,
                                start_point,
                                last_point,
                                widget_layout_hint,
                                frame_indent_hint,
                                site,
                                phase,
                                button,
                                modifier_keys_state,
                                mousebuttons_state,
                                widget_to_guiboss,
                                theme,
                                do,
                                to,
                                #
                                default_mouse_drag_fn =>  \\ _ = (),                                            # Default drag behavior for buttons is to do absolutely nothing.
                                #
                                button_state    => *button_state,                                               # We don't pass the refcell here because we want client code to make state changes via note_state(), which will properly notify all state-watchers.
                                button_type,
                                button_relief   =>  reliefref,
                                #
                                initial_state,
                                note_state,
                                needs_redraw_gadget_request
                              };

                        case mouse_drag_fn
                            #
                            THE mouse_drag_fn =>   mouse_drag_fn  mouse_drag_fn_arg;
                            NULL              =>   ();                                                          # We do not expect this case to happen: If mouse_drag_fn is NULL mouse_drag_fn_wrapper should not have been registered with widget-imp so we should never get called.
                        esac;
                    };

                fun mouse_transit_fn_wrapper
                      #
                      ( arg as
                        {
                          id:                           Id,                                                     # Unique Id for widget.
                          doc:                          String,                                                 # Human-readable description of this widget, for debug and inspection.
                          event_point:                  g2d::Point,
                          widget_layout_hint:           gt::Widget_Layout_Hint,
                          frame_indent_hint:            gt::Frame_Indent_Hint,
                          site:                         g2d::Box,                                               # Widget's assigned area in window coordinates.
                          transit:                      gt::Gadget_Transit,                                     # Mouse is entering (CAME) or leaving (LEFT) widget, or moving (MOVE) across it.
                          modifier_keys_state:          evt::Modifier_Keys_State,                               # State of the modifier keys (shift, ctrl...).
                          widget_to_guiboss:            gt::Widget_To_Guiboss,
                          theme:                        wt::Widget_Theme,
                          do:                           (Void -> Void) -> Void,                                 # Used by widget subthreads to execute code in main widget microthread.
                          to:                           Replyqueue                                              # Used to call 'pass_*' methods in other imps.
                        }
                      ) 
                    = 
                    {   note_site (id,site);
                        #
                        mouse_transit_fn_arg
                            =
                            MOUSE_TRANSIT_FN_ARG
                              {
                                id,
                                doc,
                                event_point,
                                widget_layout_hint,
                                frame_indent_hint,
                                site,
                                transit,
                                modifier_keys_state,
                                widget_to_guiboss,
                                theme,
                                do,
                                to,
                                #
                                default_mouse_transit_fn,
                                #
                                button_state    => *button_state,                                               # We don't pass the refcell here because we want client code to make state changes via note_state(), which will properly notify all state-watchers.
                                button_type,
                                button_relief   =>  reliefref,
                                #
                                initial_state,
                                note_state,
                                needs_redraw_gadget_request
                              };

                        mouse_transit_fn  mouse_transit_fn_arg;

                        ();
                    };

                fun key_event_fn_wrapper
                      {
                        id:                             Id,                                                     # Unique Id for widget.
                        doc:                            String,                                                 # Human-readable description of this widget, for debug and inspection.
                        keystroke:                      gt::Keystroke_Info,                                     # Keystring etc for event.
                        widget_layout_hint:             gt::Widget_Layout_Hint,
                        frame_indent_hint:              gt::Frame_Indent_Hint,
                        site:                           g2d::Box,                                               # Widget's assigned area in window coordinates.
                        widget_to_guiboss:              gt::Widget_To_Guiboss,
                        guiboss_to_widget:              gt::Guiboss_To_Widget,                                  # Used by textpane.pkg keystroke-macro stuff to synthesize fake keystroke events to widget.
                        theme:                          wt::Widget_Theme,
                        do:                             (Void -> Void) -> Void,                                 # Used by widget subthreads to execute code in main widget microthread.
                        to:                             Replyqueue                                              # Used to call 'pass_*' methods in other imps.
                      }
                    = 
                    {   note_site (id,site);
                        #
                        key_event_fn_arg
                            =
                            KEY_EVENT_FN_ARG
                              {
                                id,
                                doc,
                                keystroke,
                                widget_layout_hint,
                                frame_indent_hint,
                                site,
                                widget_to_guiboss,
                                guiboss_to_widget,
                                theme,
                                do,
                                to,
                                #
                                default_key_event_fn =>  \\ _ = (),                                             # Default key event behavior for buttons is to do absolutely nothing.
                                #
                                button_state    => *button_state,                                               # We don't pass the refcell here because we want client code to make state changes via note_state(), which will properly notify all state-watchers.
                                button_type,
                                button_relief   =>  reliefref,
                                #
                                initial_state,
                                note_state,
                                needs_redraw_gadget_request
                              };

                        case key_event_fn
                            #
                            THE key_event_fn =>   key_event_fn  key_event_fn_arg;
                            NULL             =>   ();                                                           # We do not expect this case to happen: If key_event_fn is NULL key_event_fn_wrapper should not have been registered with widget-imp so we should never get called.
                        esac;

                       ();
                    };


                #
                # End of widget hook fn section
                ###############################

                fun widget_callback (widget:  Null_Or( wi::Widget ))
                    =
                    case widget
                        #
                        THE w => put_in_oneshot (widget_oneshot, w);
                        NULL  => ();
                    esac;

                widget_options
                    =
                    case mouse_drag_fn
                        #
                        THE _ =>  (wi::MOUSE_DRAG_FN mouse_drag_fn_wrapper)       ! widget_options;             # Register for drag events only if we are going to use them.
                        NULL  =>                                                    widget_options;
                    esac;

                widget_options
                    =
                    case key_event_fn
                        #
                        THE _ =>  (wi::KEY_EVENT_FN key_event_fn_wrapper)         ! widget_options;             # Register for key events only if we are going to use them.
                        NULL  =>                                                    widget_options;
                    esac;

                widget_options
                    =
                    case widget_id
                        #
                        THE id =>  (wi::ID id)                                    ! widget_options;             # 
                        NULL   =>                                                   widget_options;
                    esac;

                widget_options
                  =
                  [ wi::STARTUP_FN                      startup_fn,                                             # We always register for these five because our base behavior depends on them.
                    wi::SHUTDOWN_FN                     shutdown_fn,
                    wi::INITIALIZE_GADGET_FN            initialize_gadget_fn,
                    wi::REDRAW_REQUEST_FN               redraw_request_fn_wrapper,
                    wi::MOUSE_CLICK_FN                  mouse_click_fn_wrapper,
                    wi::MOUSE_TRANSIT_FN                mouse_transit_fn_wrapper,
                    wi::WIDGET_CALLBACK                 widget_callback,
                    wi::DOC                             widget_doc
                  ]
                  @
                  widget_options
                  ;

                make_widget_fn =  wi::make_widget_start_fn  widget_options;

                gt::WIDGET  make_widget_fn;                                                                     # So caller can write   guiplan = gt::ROW [ frame::with [...], frame::with [...], ... ];
            };                                                                                                  # PUBLIC
    };
end;




Comments and suggestions to: bugs@mythryl.org

PreviousUpNext