PreviousUpNext

15.4.1423  src/lib/x-kit/widget/edit/screenline.pkg

## screenline.pkg
#
# Display one line of textfile contents in a textpane display.
#
# Here we just handle re/display of one line.
# The editing process proper happens mainly in

#     src/lib/x-kit/widget/edit/fundamental-mode.pkg

# with toplevel dispatch done from

#     src/lib/x-kit/widget/edit/textpane.pkg

# and major support from

#     src/lib/x-kit/widget/edit/textmill.pkg

# See also:
#     src/lib/x-kit/widget/edit/textpane.pkg

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





# 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 chr =  char;                                                        # char                          is from   src/lib/std/char.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

    package p2l =  textpane_to_screenline;                                      # textpane_to_screenline        is from   src/lib/x-kit/widget/edit/textpane-to-screenline.pkg
    package l2p =  screenline_to_textpane;                                      # screenline_to_textpane        is from   src/lib/x-kit/widget/edit/screenline-to-textpane.pkg
    package tpt =  textpane_types;                                              # textpane_types                is from   src/lib/x-kit/widget/edit/textpane-types.pkg
    package mt  =  millboss_types;                                              # millboss_types                is from   src/lib/x-kit/widget/edit/millboss-types.pkg
    package g2d =  geometry2d;                                                  # geometry2d                    is from   src/lib/std/2d/geometry2d.pkg
    package slt =  screenline_types;                                            # screenline_types              is from   src/lib/x-kit/widget/edit/screenline-types.pkg

Dummy1 = slt::Redraw_Fn_Arg;            # XXX SUCKO DELETEME. This is a quick hack to make sure the package compiles during early development of it.

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

    package screenline
    :       Screenline                                                          # Screenline                    is from   src/lib/x-kit/widget/edit/screenline.api
    {
        include package screenline_types;
        #
        Option  = PIXELS_SQUARE         Int
                #
                | PIXELS_HIGH_MIN       Int
                | PIXELS_WIDE_MIN       Int
                #
                | PIXELS_HIGH_CUT       Float
                | PIXELS_WIDE_CUT       Float
                #
                | INITIALLY_ACTIVE      Bool
                #
                | 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
                #
                | STATE                 p2l::Linestate                          # What to display in screenline.
                #
                | 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.
                #
                | 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.
                #
                | STATEWATCHER          (p2l::Linestate -> Void)                        # Widget's current state              will be sent to these fns each time state changes.
#               | PORTWATCHER           (Null_Or(Textpane_To_Lineditor) -> 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),
              #
              { body_color,
                body_color_with_mousefocus,
                body_color_when_on,
                body_color_when_on_with_mousefocus,
                #
                screenline_id,
                widget_doc,
                #
                state,
                #
                fonts,
                font_weight,
                font_size,
                #
                redraw_fn,
                mouse_click_fn,
                mouse_drag_fn,
                mouse_transit_fn,
                #
                initially_active,
                #
                pixels_high_min,
                pixels_high_cut,
                widget_options,
                #
#               portwatchers,
                statewatchers,
                sitewatchers
              }
            )
            =
            {   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_screenline_id                        =  REF  screenline_id;
                my_widget_doc                           =  REF  widget_doc;
                #
                my_state                                =  REF  state;
                #
                my_fonts                                =  REF  fonts;
                my_font_weight                          =  REF  font_weight;
                my_font_size                            =  REF  font_size;
                #
                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_initially_active                     =  REF  initially_active;
                #
                my_pixels_high_min                      =  REF  pixels_high_min;
                my_pixels_high_cut                      =  REF  pixels_high_cut;
                my_widget_options                       =  REF  widget_options;
                #
#               my_portwatchers                         =  REF  portwatchers;
                my_statewatchers                        =  REF  statewatchers;
                my_sitewatchers                         =  REF  sitewatchers;
                #

                apply  do_option  options
                where
                    fun do_option (INITIALLY_ACTIVE                     b) =>   my_initially_active     :=  b;
                        #
                        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_screenline_id        :=  THE i;
                        do_option (DOC                                  d) =>   my_widget_doc           :=      d;
                        #
                        do_option (STATE                                t) =>   my_state                :=  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 (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 (PORTWATCHER                          c) =>   my_portwatchers         :=  c ! *my_portwatchers;
                        do_option (STATEWATCHER                         c) =>   my_statewatchers        :=  c ! *my_statewatchers;
                        do_option (SITEWATCHER                          c) =>   my_sitewatchers         :=  c ! *my_sitewatchers;
                        #
                        #
                        do_option (PIXELS_HIGH_MIN                      i) =>   my_pixels_high_min      :=  i;
                        do_option (PIXELS_WIDE_MIN                      i) =>   my_widget_options       :=  (wi::PIXELS_WIDE_MIN i) ! *my_widget_options;
                        #
                        do_option (PIXELS_HIGH_CUT                      f) =>   my_pixels_high_cut      :=  f;
                        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;

                { 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,
                  #
                  screenline_id                         =>  *my_screenline_id,
                  widget_doc                            =>  *my_widget_doc,
                  #
                  state                                 =>  *my_state,
                  #
                  fonts                                 =>  *my_fonts,
                  font_weight                           =>  *my_font_weight,
                  font_size                             =>  *my_font_size,
                  #
                  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,
                  #
                  initially_active                      =>  *my_initially_active,
                  #
                  pixels_high_min                       =>  *my_pixels_high_min,
                  pixels_high_cut                       =>  *my_pixels_high_cut,
                  widget_options                        =>  *my_widget_options,
                  #
#                 portwatchers                          =>  *my_portwatchers,
                  statewatchers                         =>  *my_statewatchers,
                  #     
                  sitewatchers                          =>  *my_sitewatchers
                };
            };


        fun default_redraw_fn (REDRAW_FN_ARG a)                                                                                         # Handle a guiboss request to redraw ourself.
            =
            {
                font_size       =  a.font_size;
                font_weight     =  a.font_weight;
                fonts           =  a.fonts;
                gadget_mode     =  a.gadget_mode;
                palette         =  a.palette;
                site            =  a.site;
                state           =  a.state;
                theme           =  a.theme;

                screencol0      =  state.screencol0;                                                                                    # Do not display text to left of this screen column.  Used to scroll display horizontally.  Must be nonnegative.

                background_box  =  site;

                text_color =    palette.text_color;
#               body_color =    palette.body_color;                                                                                     # Commented out in favor of whiter and more varied background colors under control of textpane.pkg.
                body_color =    case gadget_mode.has_mouse_focus                                                                        # Make background color just a little bit brighter on mouseover.
                                    #
                                    TRUE  =>                                   state.background ;
                                    FALSE => rgb::rgb_mix01 (0.98, rgb::black, state.background);
                                esac;

                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;
                    };


                stipulate
                    g =  wti::get__guiboss_to_hostwindow  theme;
                    #
                    font = g.get_font (get_fontnames ());
                herein
                    fun get_text_dimensions (text: String)
                        =
                        {
                            { font_ascent      =>  font.font_height.ascent,
                              font_descent     =>  font.font_height.descent,
                              length_in_pixels =>  font.string_length_in_pixels text
                            };
                        };
                end;

                (get_text_dimensions "m")
                    ->
                    { length_in_pixels => m_width_in_pixels, ... };

                m_width_in_pixels = max (3, m_width_in_pixels);                                                                         # Daft check to avoid possible divide by zero if text dimensions are crazy.

                Color_As = NORMAL_TEXT                                                                                                  # Text is not part of the active region nor is the cursor on it, so color text normally.
                         | REGION_TEXT                                                                                                  # Text is part of the active region delimited by 'mark' and 'point' -- color it region (green background).
                         | CURSOR_TEXT                                                                                                  # Text is under the cursor -- color it accordingly (reverse video).
                         | CURION_TEXT                                                                                                  # Text is part of the active region with the cursor at the start of the region -- color it region and draw cursor as an unfilled black box.
                         ;

                fun region_color (body_color: rgb::Rgb,  text_color: rgb::Rgb)                                                          # Construct background color for selected region (delimited by 'mark' and 'point').  Currently region_color is same as body_color except less red, leaving a cyan.
                    =
                    { red   => (body_color.red + text_color.red) * 0.5,                                                                 # Typically text color is black and body color is white(ish) -- in this case the region background here will wind up cyan-ish.  (Emacs uses yellow here.)
                      green =>  body_color.green,                                                                                       # (This hack should probably be a fn in the widget-theme rather than hardwired here.)
                      blue  =>  body_color.blue
                    };

                fun append_text_to_displaylist
                      (
                        displaylist_so_far:     gd::Gui_Displaylist,
                        chars_to_skip:          Int,                                                                                    # Support for scrolling textpane horizontally.
                        text_indent:            Int,                                                                                    # Draw our text starting at this horizontal pixel coordinate.  We use this to concatenate multiple strings sanely on a line.
                        text:                   String,                                                                                 # Text to draw.  Tabs and control chars have been expanded out.
                        text_box:               g2d::Box,
                        color_as:               Color_As
                      )
                    =
                    if (text == "")
                        (displaylist_so_far, chars_to_skip, text_indent);                                                               # ... nothing to do.
                    else
                        charlen =  string::length_in_chars text;
                        #                                                                                                               # If start of cursor is out of sight off left or right side of display, let textpane know that it needs to scroll horizontally to bring it into view.
                        case color_as
                            #
                            (CURSOR_TEXT | CURION_TEXT)                                                                                 # We DO have start-of-cursor at start of 'text'.
                                =>                                                                                                      # 
                                if (chars_to_skip > 0)                                                                                  # Start of text is out of view to left of textpane.
                                    #                                                                                                   # Hence, cursor is out of sight off left side of display.
                                    panewidth_in_cols = text_box.wide / m_width_in_pixels;
                                    out_by_in_cols    = -chars_to_skip;

                                    a.screenline_to_textpane.cursor_offscreen                                                           # Notify textpane.pkg.
                                      {
                                        out_by_in_cols,
                                        panewidth_in_cols,
                                        screencol0
                                      };

                                elif (text_indent > text_box.wide)                                                                      # 
                                    #                                                                                                   # Start of text is out of view to right of textpane.
                                    panewidth_in_cols = text_box.wide / m_width_in_pixels;

                                    out_by_in_cols    = (text_indent - text_box.wide)  /  m_width_in_pixels;

                                    a.screenline_to_textpane.cursor_offscreen                                                           # Notify textpane.pkg.
                                      {
                                        out_by_in_cols,
                                        panewidth_in_cols,
                                        screencol0
                                      };
                                fi;

                            _   => ();
                        esac;


                        if (charlen <= chars_to_skip)                                                                                   # The string 'text' is entirely out of view to left of visible textpane, so ...
                            #
                            (displaylist_so_far, chars_to_skip - charlen, text_indent);                                                 # ... nothing to do.
                        else
                            my (text, charlen)
                                =
                                if (chars_to_skip == 0)
                                    #
                                    (text, charlen);
                                else                                                                                                    # Drop 'chars_to_skip' chars from start of 'text'.
                                    bytes_to_skip = string::prefix_length_in_bytes (text, chars_to_skip);                               #
                                    #                                                                                                   # NB: Tabs and control chars are not an issue here because they've already been expanded into regular ascii (tabs into blank sequences, control chars into "^A" style sequences.
                                    text'    = string::extract (text, bytes_to_skip, NULL);                                             #
                                    charlen' =  string::length_in_chars text';
                                                                                                                                        #
                                    (text', charlen');                                                                                  # NB: 'text' is guaranteed to be nonempty because we know chars_to_skip < charlen.
                                fi;


                            my (boxcursor, text_color, body_color)
                                =
                                case color_as
                                    #
                                    CURSOR_TEXT =>      (FALSE, body_color, text_color);
                                    NORMAL_TEXT =>      (FALSE, text_color, body_color);
                                    REGION_TEXT =>      (FALSE, text_color, region_color (body_color, text_color));
                                    CURION_TEXT =>      (TRUE,  text_color, region_color (body_color, text_color));
                                esac;

                            text_dimensions =  get_text_dimensions  text;

                            fontnames =  get_fontnames ();

                            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 and also also center
                            # it properly vertically -- most fonts
                            # have ascent > descent:
                            #
                            row =  row - text_dimensions.font_descent + ((text_dimensions.font_ascent + text_dimensions.font_descent) / 2); 
                            col =  col + text_indent;                                                                                   # In general we're starting somewhere within the line, after other stuff has been rendered.
                            draw_point = { row, col };
                            #
                            textbox     = { row  =>  box_corners.upper_left.row,                                                        # Area behind text.
                                            col  =>  col,
                                            high =>  box_corners.lower_left.row - box_corners.upper_left.row,
#                                           wide =>  text_dimensions.length_in_pixels                                                   # Currently length_in_pixels seems broken for multibyte utf8 chars, so ... 
                                            wide =>  charlen * m_width_in_pixels                                                        # ... I'm doing this instead, which works fine for the fixed-width fonts we (currently) use for programming.
                                          };

                            displaylist = [ gd::COLOR
                                              ( body_color,
                                                [ gd::FILLED_BOXES [ textbox ] ]
                                              ),                                                                                        # Clear area behind text to correct color (which varies depending on color_as).
                                            #
                                            gd::COLOR
                                              ( text_color,
                                                [ gd::FONT ( fontnames,                                                                 # Draw relevant text atop it.
                                                             [ gd::PUT_TEXT   ( gd::TO_RIGHT_OF_POINT,
                                                                                [ gd::TEXT (draw_point, text) ]
                                                                              )
                                                             ]
                                                           )
                                                ]
                                              )
                                          ];

                            displaylist =   if (not boxcursor)
                                                #
                                                displaylist;
                                            else
                                                cursorbox = { row  => textbox.row,
                                                              col  => textbox.col,
                                                              high => textbox.high - 1,                                                 # Need these '-1's because otherwise right and bottom of cursorbox get overwritten by subsequent stuff.  This must represent an off-by-one error in the way we compute textboxes. XXX SUCKO FIXME.
                                                              wide => textbox.wide - 1
                                                            };

                                                displaylist
                                                @
                                                [ gd::COLOR                                                                             # Draw hollow box representing cursor.
                                                  ( text_color,
                                                    [ gd::BOXES [ cursorbox ] ]
                                                  )                                                                                     # 
                                                ];
                                            fi;

                            displaylist_so_far
                                =
                                displaylist_so_far  @  displaylist;

                            ( displaylist_so_far,
                              0,                                                                                                        # chars_to_skip
                              text_indent + (charlen * m_width_in_pixels)                                                               # As above, avoiding  text_dimensions.length_in_pixels  which seems broken for multibyte utf8 chars.
                            );
                        fi;
                    fi;

                displaylist =  [ gd::COLOR (body_color, [ gd::FILLED_BOXES [ background_box ] ] ) ];                                    # Interior of widget.

                text_box =    background_box;

                text_indent  = 3;                                                                                                       # For readability, insert some space between frame and start of text.  (In pixels.)

                fun expand_tabs_and_control_chars { utf8text: String, col: Int }
                    =
                    {   (string::expand_tabs_and_control_chars
                          {
                            utf8text,
                            startcol   =>  col,
                            screencol1 => -1,                                                                                           # Don't-care.
                            screencol2 => -1,                                                                                           # Don't-care.
                            utf8byte   => -1                                                                                            # Don't-care.
                          })
                          ->
                          { screentext => text,
                            startcol   => col,
                            ...
                          };

                        { text, col };
                    };


                displaylist
                    =                                                                           # XXX SUCKO FIXME It would be nice to find a better factorization of the below code so that we don't have such a maze of nested cases.  (Preferably without changing performance from O(N) -> O(N**2)!)
                    case state.selected                                                         # state.selected tells us the screen columns occupied on this line by the selected 'region' is (which includes the cursor if it is on current line), which should be shown in reverse video.
                        #
                        THE (col1, NULL)                                                        # We're given start screen column of region, which runs to end of line.
                            =>
                            {
                                (string::expand_tabs_and_control_chars
                                  {
                                    utf8text   =>  state.text,
                                    startcol   =>  0,
                                    screencol1 =>  col1,
                                    screencol2 => -1,                                                                                   # Don't-care.
                                    utf8byte   => -1                                                                                    # Don't-care.
                                  })
                                  ->
                                  { screentext,
                                    startcol   => col,
                                    #
                                    screencol1_byteoffset_in_screentext,
                                    screencol1_bytescount_in_screentext,
                                    ...
                                  };

                                screencols = string::length_in_chars  screentext;
                                                                                                                                        # NB: 'region' is just the cursor, if mark isn't set.
                                (string::expand_tabs_and_control_chars
                                  {
                                    utf8text   =>  state.text,
                                    startcol   =>  0,
                                    screencol1 =>  col1,
                                    screencol2 =>  screencols - 1,
                                    utf8byte   => -1                                                                                    # Don't-care.
                                  })
                                  ->
                                  { screentext,
                                    startcol   => col,
                                    #
                                    screencol1_byteoffset_in_screentext,
                                    screencol1_bytescount_in_screentext,
                                    #
                                    screencol2_byteoffset_in_screentext,
                                    screencol2_bytescount_in_screentext,
                                    ...
                                  };

                                my  { text_before_region,                                                                                       # Our game here is to show the char that the cursor is on in reverse video.
                                      text_within_cursr1,       
                                      text_within_region,                                                                                       # This is complicated by three details:
                                      text_within_cursr2,       
                                      text_beyond_region                                                                                        #  1. UTF-8 chars occupy one screen column but 1-6 bytes in string.
                                    }                                                                                                           #  2. Control chars occupy one byte in input string but two screen columns ("^A" etc)  and two bytes in output string.
                                    =                                                                                                           #  3. Tabs          occupy one byte in input string but 1-8 screen columns (as blanks) and 1-8 bytes in output string.
                                    {           
                                        if (col1 >= screencols)                                                                                 # In the latter two cases this means we are showing more than one visible char in reverse video, even though it represents a single byte in input string.
                                            #   
                                            msg = sprintf "default_redraw_fn/CCC0: col1(%d) >= screencols(%d)!!" col1 screencols;
                                            log::fatal msg;
                                            raise exception DIE msg;
                                        fi;

                                        case state.cursor_at
                                            #
                                            p2l::NO_CURSOR
                                                =>
                                                { text_before_region =>  string::substring (screentext, 0,  screencol1_byteoffset_in_screentext),
                                                  text_within_cursr1 =>  "",
                                                  text_within_region =>  string::extract   (screentext, screencol1_byteoffset_in_screentext,  NULL)     except INDEX_OUT_OF_BOUNDS = "",
                                                  text_within_cursr2 =>  "",
                                                  text_beyond_region =>  ""                                                                     #
                                                };

                                            p2l::CURSOR_AT_START
                                                =>
                                                { text_before_region =>  string::substring (screentext, 0,  screencol1_byteoffset_in_screentext),
                                                  text_within_cursr1 =>  string::substring (screentext, screencol1_byteoffset_in_screentext,  screencol1_bytescount_in_screentext)      except INDEX_OUT_OF_BOUNDS = "",
                                                  text_within_region =>  string::extract   (screentext, screencol1_byteoffset_in_screentext+screencol1_bytescount_in_screentext, NULL)  except INDEX_OUT_OF_BOUNDS = "",
                                                  text_within_cursr2 =>  "",
                                                  text_beyond_region =>  ""                                                                     #
                                                };

                                            p2l::CURSOR_AT_END                                                                                  # We treat this identically to above.
                                                =>
                                                { text_before_region =>  string::substring (screentext, 0,  screencol1_byteoffset_in_screentext),
                                                  text_within_cursr1 =>  "",
                                                  text_within_region =>  string::substring (screentext, screencol1_byteoffset_in_screentext,  screencol2_byteoffset_in_screentext-screencol1_byteoffset_in_screentext)  except INDEX_OUT_OF_BOUNDS = "",
                                                  text_within_cursr2 =>  string::substring (screentext, screencol2_byteoffset_in_screentext,  screencol2_bytescount_in_screentext)      except INDEX_OUT_OF_BOUNDS = "",
                                                  text_beyond_region =>  ""                                                                     #
                                                };
                                        esac;
                                    };

                                my (displaylist, chars_to_skip, text_indent) =   append_text_to_displaylist (displaylist, screencol0,    text_indent, state.prompt,       text_box, NORMAL_TEXT);       # Eventually we'll probably want to run state.prompt through string::expand_tabs_and_control_chars...  XXX SUCKO FIXME
                                my (displaylist, chars_to_skip, text_indent) =   append_text_to_displaylist (displaylist, chars_to_skip, text_indent, text_before_region, text_box, NORMAL_TEXT);
                                my (displaylist, chars_to_skip, text_indent) =   append_text_to_displaylist (displaylist, chars_to_skip, text_indent, text_within_cursr1, text_box, CURION_TEXT);
                                my (displaylist, chars_to_skip, text_indent) =   append_text_to_displaylist (displaylist, chars_to_skip, text_indent, text_within_region, text_box, REGION_TEXT);
                                my (displaylist, chars_to_skip, text_indent) =   append_text_to_displaylist (displaylist, chars_to_skip, text_indent, text_within_cursr2, text_box, CURSOR_TEXT);
                                my (displaylist, chars_to_skip, text_indent) =   append_text_to_displaylist (displaylist, chars_to_skip, text_indent, text_beyond_region, text_box, NORMAL_TEXT);

                                displaylist;
                            };

                        THE (col1, THE col2)                                                                                                    # We are given both start and end screeen columns for the region.
                            =>
                            {
                                (string::expand_tabs_and_control_chars
                                  {
                                    utf8text   =>  state.text,
                                    startcol   =>  0,
                                    screencol1 =>  col1,
                                    screencol2 =>  col2,
                                    utf8byte   => -1                                                                                    # Don't-care.
                                  })
                                  ->
                                  { screentext,
                                    startcol   => col,
                                    #
                                    screencol1_byteoffset_in_screentext,
                                    screencol1_bytescount_in_screentext,
                                    #
                                    screencol2_byteoffset_in_screentext,
                                    screencol2_bytescount_in_screentext,
                                    ...
                                  };

                                screencols = string::length_in_chars  screentext;
                                                                                                                                        # NB: 'region' is just the cursor, if mark isn't set.
                                my  { text_before_region,                                                                               # Our game here is to show the region in reverse video.
                                      text_within_cursr1,       
                                      text_within_region,                                                                               # This is complicated by three details:
                                      text_within_cursr2,       
                                      text_beyond_region                                                                                #  1. UTF-8 chars occupy one screen column but 1-6 bytes in string.
                                    }                                                                                                   #  2. Control chars occupy one byte in input string but two screen columns ("^A" etc)  and two bytes in output string.
                                    =                                                                                                   #  3. Tabs          occupy one byte in input string but 1-8 screen columns (as blanks) and 1-8 bytes in output string.
                                    {
                                        if (col1 >= screencols)                                                                         # In the latter two cases this means we are showing more than one visible char in reverse video, even though it represents a single byte in input string.
                                            #
                                            msg = sprintf "default_redraw_fn/ccc: col1(%d) >= screencols(%d)!!" col1 screencols;
                                            log::fatal msg;
                                            raise exception DIE msg;
                                        fi;
                                        if (col2 >= screencols)                                                                         # Region starts within input string but extends beyond actual end of line in input string.
                                            #
                                            msg = sprintf "default_redraw_fn/ddd: col2(%d) >= screencols(%d)!!" col1 screencols;
                                            log::fatal msg;
                                            raise exception DIE msg;
                                        fi;

                                        case state.cursor_at
                                            #
                                            p2l::NO_CURSOR
                                                =>
                                                { text_before_region =>  string::substring (screentext, 0,  screencol1_byteoffset_in_screentext),
                                                  text_within_cursr1 =>  "",
                                                  text_within_region =>  string::substring (screentext, screencol1_byteoffset_in_screentext,  (screencol2_byteoffset_in_screentext + screencol2_bytescount_in_screentext) - screencol1_byteoffset_in_screentext),
                                                  text_within_cursr2 =>  "",
                                                  text_beyond_region =>  string::extract   (screentext, screencol2_byteoffset_in_screentext + screencol2_bytescount_in_screentext,  NULL                        )       except INDEX_OUT_OF_BOUNDS = ""
                                                };

                                            p2l::CURSOR_AT_START
                                                =>
                                                { text_before_region =>  string::substring (screentext, 0,  screencol1_byteoffset_in_screentext),
                                                  text_within_cursr1 =>  string::substring (screentext, screencol1_byteoffset_in_screentext,  screencol1_bytescount_in_screentext),
                                                  text_within_region =>  string::substring (screentext, screencol1_byteoffset_in_screentext + screencol1_bytescount_in_screentext,  (screencol2_byteoffset_in_screentext + screencol2_bytescount_in_screentext) - (screencol1_byteoffset_in_screentext + screencol1_bytescount_in_screentext)),
                                                  text_within_cursr2 =>  "",
                                                  text_beyond_region =>  string::extract   (screentext, screencol2_byteoffset_in_screentext + screencol2_bytescount_in_screentext,  NULL                        )       except INDEX_OUT_OF_BOUNDS = ""
                                                };

                                            p2l::CURSOR_AT_END                                                                          # 
                                                =>
                                                { text_before_region =>  string::substring (screentext, 0,  screencol1_byteoffset_in_screentext),
                                                  text_within_cursr1 =>  "",
                                                  text_within_region =>  string::substring (screentext, screencol1_byteoffset_in_screentext,  screencol2_byteoffset_in_screentext - screencol1_byteoffset_in_screentext),
                                                  text_within_cursr2 =>  string::substring (screentext, screencol2_byteoffset_in_screentext,  screencol2_bytescount_in_screentext),
                                                  text_beyond_region =>  string::extract   (screentext, screencol2_byteoffset_in_screentext + screencol2_bytescount_in_screentext,  NULL                        )       except INDEX_OUT_OF_BOUNDS = ""
                                                };
                                        esac;
                                    };


                                my (displaylist, chars_to_skip, text_indent) =   append_text_to_displaylist (displaylist, screencol0,    text_indent, state.prompt,       text_box, NORMAL_TEXT);
                                my (displaylist, chars_to_skip, text_indent) =   append_text_to_displaylist (displaylist, chars_to_skip, text_indent, text_before_region, text_box, NORMAL_TEXT);
                                my (displaylist, chars_to_skip, text_indent) =   append_text_to_displaylist (displaylist, chars_to_skip, text_indent, text_within_cursr1, text_box, CURION_TEXT);
                                my (displaylist, chars_to_skip, text_indent) =   append_text_to_displaylist (displaylist, chars_to_skip, text_indent, text_within_region, text_box, REGION_TEXT);
                                my (displaylist, chars_to_skip, text_indent) =   append_text_to_displaylist (displaylist, chars_to_skip, text_indent, text_within_cursr2, text_box, CURSOR_TEXT);
                                my (displaylist, chars_to_skip, text_indent) =   append_text_to_displaylist (displaylist, chars_to_skip, text_indent, text_beyond_region, text_box, NORMAL_TEXT);

                                displaylist;
                            };

                        NULL =>                                                                                                         # Region does not show on line.
                            {
                                (string::expand_tabs_and_control_chars
                                  {
                                    utf8text   =>  state.text,
                                    startcol   =>  0,
                                    screencol1 => -1,                                                                                   # Don't-care.
                                    screencol2 => -1,                                                                                   # Don't-care.
                                    utf8byte   => -1                                                                                    # Don't-care.

                                  })
                                  ->
                                  { screentext,
                                    startcol   => col,
                                    ...
                                  };

                                screencols = string::length_in_chars  screentext;

                                my (displaylist, chars_to_skip, text_indent) =   append_text_to_displaylist (displaylist, screencol0,    text_indent, state.prompt, text_box, NORMAL_TEXT);
                                my (displaylist, chars_to_skip, text_indent) =   append_text_to_displaylist (displaylist, chars_to_skip, text_indent, screentext,   text_box, NORMAL_TEXT);
                                
                                displaylist;
                            };
                    esac;

                displaylist  = [ gd::CLIP_TO (text_box, displaylist) ];

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

                point_in_gadget = THE point_in_gadget;


                { displaylist,
                  point_in_gadget,
                  pixels_high_min => 0,
                  pixels_wide_min => 0
                };
            };

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


        fun default_mouse_click_fn (MOUSE_CLICK_FN_ARG a)
            =
            {   screenline_to_textpane          =  a.screenline_to_textpane;        
                #
                mouse_click_arg                                                                                 # Construct a generic tpt::Mouse_Click_Fn arg from our screenline-specialized one.
                  =
                  {
                    id                  =>  a.id,
                    doc                 =>  a.doc,
                    event               =>  a.event,
                    button              =>  a.button,
                    point               =>  a.point,
                    widget_layout_hint  =>  a.widget_layout_hint,
                    frame_indent_hint   =>  a.frame_indent_hint,
                    site                =>  a.site,
                    modifier_keys_state =>  a.modifier_keys_state,
                    mousebuttons_state  =>  a.mousebuttons_state,
                    widget_to_guiboss   =>  a.widget_to_guiboss,
                    theme               =>  a.theme
                  }:                        tpt::Mouse_Click_Fn_Arg;

                screenline_to_textpane.mouse_click_fn  mouse_click_arg;
            };



        fun with                                                                                                # PUBLIC.  The point of the 'with' name is that GUI coders can write 'screenline::with { this => that, foo => bar, ... }.'
              { paneline:       Int,
                textpane_id:    Id,                                                                             # The textpane to which we belong. Caller provides this so we can register outself with it via millboss_imp.
                options:        List(Option)
              }
            =
            {
                #######################################
                # Top of per-imp state variable section
                #

                screenline_to_textpane__global  =  REF (NULL:  Null_Or(l2p::Screenline_To_Textpane));
                widget_to_guiboss__global       =  REF (NULL:  Null_Or( { widget_to_guiboss: gt::Widget_To_Guiboss, screenline_id: Id }));

                stateref        = REF { selected    =>  NULL,                                                   # Part of line to show with (typically) greenish background -- selected region. (Part may be overwritten by the cursor.)
                                        cursor_at   =>  p2l::NO_CURSOR,                                         # Does cursor appear at start or end of 'selected' part -- or neither?
                                        text        =>  "",
                                        prompt      =>  "",
                                        screencol0  =>  0,
                                        background  =>  rgb::white      
                                      };
                
                bogus_site
                   =
                   { col => -1,  wide => -1,
                     row => -1,  high => -1
                   }:                                           g2d::Box;

                last_known_site
                    =
                    REF bogus_site;

                button_active
                    =
                    REF TRUE;

                #
                #######################################



                #
                (process_options
                  (
                    options,
                    #
                    { body_color                         =>  NULL,
                      body_color_with_mousefocus         =>  NULL,
                      body_color_when_on                 =>  NULL,
                      body_color_when_on_with_mousefocus =>  NULL,
                      #
                      screenline_id     =>  NULL,
                      widget_doc        =>  "<screenline>",
                      #
                      state             =>  *stateref,
                      #
                      fonts             =>  [],
                      font_weight       =>  (THE wt::BOLD_FONT: Null_Or(wt::Font_Weight)),
                      font_size         =>  (NULL: Null_Or(Int)),
                      #
                      redraw_fn         =>  default_redraw_fn,
                      mouse_click_fn    =>  default_mouse_click_fn,
                      mouse_drag_fn     =>  NULL,
                      mouse_transit_fn  =>  default_mouse_transit_fn,
                      #
                      initially_active  =>  *button_active,
                      #
                      pixels_high_min   =>  0,                                                                  # Setting this to 16 resulted in an infinite loop of vertical site expansion in textpane.pkg.  So currently we leave the driving to textpane.pkg.
                      pixels_high_cut   =>  1.0,                                                                # So main screenlines will evenly divide up all space left after modeline has taken its fixed allotment.
                      widget_options    =>  [],
                      #
#                     portwatchers      =>  [],
                      statewatchers     =>  [],
                      sitewatchers      =>  []
                    }
                ) )
                    ->
                    {                                                                                           # These values are globally visible to the subsequenc fns, which can lock them in as needed.
                      body_color,
                      body_color_with_mousefocus,
                      body_color_when_on,
                      body_color_when_on_with_mousefocus,
                      #
                      screenline_id,
                      widget_doc,
                      #
                      state,
                      #
                      fonts,
                      font_weight,
                      font_size,
                      #
                      redraw_fn,
                      mouse_click_fn,
                      mouse_drag_fn,
                      mouse_transit_fn,
                      #
                      initially_active,
                      #
                      pixels_high_min,  
                      pixels_high_cut,  
                      widget_options,
                      #
#                     portwatchers,
                      statewatchers,
                      sitewatchers
                    };

                stateref        := state;
                button_active   := initially_active;

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

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



                fun note_site
                      (arg as
                        { screenline_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 (screenline_id,site));
                            end;
                    fi;

                fun notify_statewatchers ()
                    =   
                    apply tell_watcher statewatchers
                        where
                            fun tell_watcher statewatcher
                                =
                                statewatcher *stateref;
                        end;


                fun note_state (state: p2l::Linestate)
                    =
                    if(*stateref != state)
                        #
                        # Blinking the cursor seemed like a great idea, but in practice
                        # it means no feedback on cursor position half the time, which
                        # slows down fast typing, so I've commented it out.  (Note that
                        # emacs doesn't blink its cursor either.)
                        #
#                       fun flip_blink (wa: gt::Wakeup_Arg)
#                           =
#                           {   cursoronref :=  not *cursoronref;
#                               #
#                               needs_redraw_gadget_request ();
#                           };

#                       case (*widget_to_guiboss__global)                                               # Turn cursorblink-driving wakeme call on or off as necessary.
#                           #
#                           (THE { widget_to_guiboss,  screenline_id })
#                               =>
#                               case ((*stateref).cursor, state.cursor)
#                                   #
#                                   (THE _, THE _) =>   ();                                             # Cursorblink was on,  still on,  nothing to do here.
#                                   (NULL , NULL ) =>   ();                                             # Cursorblink was off, still off, nothing to do here.

#                                   (THE _, NULL )                                                      # Cursorblink was on, need to turn it off.
#                                       =>
#                                       widget_to_guiboss.g.wake_me
#                                         {
#                                           id      => screenline_id,
#                                           options => [ gt::EVERY_N_FRAMES (NULL                ) ]
#                                         };

#                                   (NULL, THE _)                                                       # Cursorblink was off, need to turn it on.
#                                       =>
#                                       widget_to_guiboss.g.wake_me
#                                         {
#                                           id      => screenline_id,
#                                           options => [ gt::EVERY_N_FRAMES (THE (40, flip_blink)) ]
#                                         };
#                               esac;

#                           _ => ();                                                                    # We don't expect this to happen. Should probably log an error or warning if it does...
#                       esac;

                        stateref := state;

                        needs_redraw_gadget_request ();

                        notify_statewatchers ();
                    fi;

                #
                # End of state variable 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
                    }
                    =
                    {
                        #####################
                        # Top of port section
                        #
                        # Here we implement our Textpane_To_Lineditor port:

                        fun set_active_to (is_active: Bool)                                                     # PUBLIC.
                            =
                            do {.                                                                               # The 'do' switches us from executing in microthread of caller to our own microthread.
                                button_active :=  is_active;
                                #
                                note_changed_gadget_activity  is_active;
                            };

                        fun set_state_to (state: p2l::Linestate)                                                # PUBLIC.
                            =
                            do {.                                                                               # The 'do' switches us from executing in microthread of caller to our own microthread.
                                note_state state;
                            };

                        fun get_active ()                                                                       # PUBLIC.
                            =
                            *button_active;                                                                     # We don't really need the 'do' dance here, since this call is read-only functionality.  (Avoiding needless 'do's also reduces deadlock risks.)

                        fun get_state ()                                                                        # PUBLIC.
                            =
                            *stateref;                                                                          # We don't really need the 'do' dance here, since this call is read-only functionality.  (Avoiding needless 'do's also reduces deadlock risks.)


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


                        widget_to_guiboss__global
                            :=  
                            THE { widget_to_guiboss, screenline_id => id };

                        (mt::get__mill_to_millboss  "screenline::startup_fn")                                   # Find our port to src/lib/x-kit/widget/edit/millboss-imp.pkg
                            ->
                            mt::MILL_TO_MILLBOSS mb;

# nb {. sprintf "startup_fn: screenline id=%d (%s) registering with textpane id=%d  --screenline.pkg" (id_to_int id) doc (id_to_int textpane_id); };
                        mb.mail_pane (textpane_id, crypt)                                                       # Register ourself with our textpane.pkg instance.
                            where
                                fun note__screenline_to_textpane
                                      (
                                        screenline_to_textpane: l2p::Screenline_To_Textpane
                                      )
                                    =
                                    do {.                                                                       # The 'do' switches us from executing in microthread of caller to our own microthread.
                                        screenline_to_textpane__global := THE  screenline_to_textpane;          # Note port to our textpane.pkg instance.
                                    };

                                textpane_to_screenline
                                  =
                                  { screenline_id => id,
                                    paneline,
                                    textpane_id,
                                    #
                                    get_active,
                                    get_state,
                                    #
                                    set_active_to,
                                    set_state_to,
                                    #
                                    note__screenline_to_textpane
                                  }:                    p2l::Textpane_To_Screenline;

                                crypt = { id   =>  issue_unique_id (),
                                          type =>  "millboss_types::TEXTPANE_TO_SCREENLINE__CRYPT",
                                          info =>  "Initialization from screenline.pkg for textpane.pkg.",
                                          data =>  mt::TEXTPANE_TO_SCREENLINE__CRYPT textpane_to_screenline
                                        };
                            end;        

#                       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 sitewatchers
                            where
                                fun tell_watcher sitewatcher
                                    =
                                    sitewatcher NULL;
                            end;

#                       apply   tell_watcher  portwatchers                                                      # 
#                               where
#                                   fun tell_watcher  portwatcher
#                                       =
#                                       portwatcher  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  { screenline_id => 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 { screenline_id => id, site };
                        #
                        case (*screenline_to_textpane__global)
                            #
                            THE screenline_to_textpane
                                =>
                                {
                                    palette =   *theme.current_gadget_colors  { gadget_is_on => FALSE,                  # We're not a button, we don't have ON/OFF state. (But maybe click-to-focus should work like ON, if/when we implement it?)
                                                                                gadget_mode,
                                                                                popup_nesting_depth,
                                                                                #
                                                                                body_color,
                                                                                body_color_when_on,
                                                                                body_color_with_mousefocus,
                                                                                body_color_when_on_with_mousefocus
                                                                              };

                                    redraw_fn_arg
                                        =
                                        REDRAW_FN_ARG
                                          { id,
                                            doc,
                                            frame_number,
                                            frame_indent_hint,
                                            site,
                                            popup_nesting_depth,
                                            duration_in_seconds,
                                            widget_to_guiboss,
                                            screenline_to_textpane,
                                            gadget_mode,
                                            theme,
                                            do,
                                            to,
                                            palette,
                                            #
                                            default_redraw_fn,  
                                            #
                                            state         => *stateref,

                                            fonts,
                                            font_weight,
                                            font_size
                                          };

                                    (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 };
                                };

                            NULL => ();                                                                         # We don't expect this -- we should be fully wired well before any redraw requests have time to arrive. Possibly we should log a warning or even fatal error.
                        esac;
                    };


                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  { screenline_id => 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.
                                #
                                state     =>  stateref,                                                         # 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.
                                #
                                notify_statewatchers,
                                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 { screenline_id => 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,                                                       # 
                                #
                                state     =>  stateref,                                                         # 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.
                                #
                                notify_statewatchers,
                                needs_redraw_gadget_request
                              };

                        mouse_transit_fn  mouse_transit_fn_arg;

                        ();
                    };

                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  { screenline_id => id, site };
                        #
                        case (*screenline_to_textpane__global)
                            #
                            THE screenline_to_textpane
                                =>
                                {
                                    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,
                                            #
                                            state         =>  stateref,                                                         # 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.
                                            #
                                            notify_statewatchers,
                                            needs_redraw_gadget_request,
                                            screenline_to_textpane
                                          };

                                    mouse_click_fn  mouse_click_fn_arg;
                                };

                            NULL => ();                                                                         # We don't expect this -- we should be fully wired well before any keystrokes have time to arrive. Possibly we should log a warning or even fatal error.
                        esac;
                    };


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

                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 screenline_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::PIXELS_HIGH_MIN                 pixels_high_min,
                    wi::PIXELS_HIGH_CUT                 pixels_high_cut,
                    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 [ button::with [...], button::with [...], ... ];
            };                                                                                                  # PUBLIC
    };
end;




Comments and suggestions to: bugs@mythryl.org

PreviousUpNext