## horizontal-int-slider.pkg
#
# See also:
#
src/lib/x-kit/widget/leaf/button.pkg#
src/lib/x-kit/widget/leaf/diamondbutton.pkg#
src/lib/x-kit/widget/leaf/roundbutton.pkg# Compiled by:
#
src/lib/x-kit/widget/xkit-widget.sublib### "I am amazed, O Wall, that you have
### not collapsed and fallen, since you
### must bear the tedious stupidities
### of so many scrawlers."
###
### -- graffiti in Pompeii, 79AD
# This package gets used in:
#
#
stipulate
include package threadkit; # threadkit is from
src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg include package geometry2d; # geometry2d is from
src/lib/std/2d/geometry2d.pkg #
package evt = gui_event_types; # gui_event_types is from
src/lib/x-kit/widget/gui/gui-event-types.pkg package g2p = gadget_to_pixmap; # gadget_to_pixmap is from
src/lib/x-kit/widget/theme/gadget-to-pixmap.pkg package gd = gui_displaylist; # gui_displaylist is from
src/lib/x-kit/widget/theme/gui-displaylist.pkg package gt = guiboss_types; # guiboss_types is from
src/lib/x-kit/widget/gui/guiboss-types.pkg package wt = widget_theme; # widget_theme is from
src/lib/x-kit/widget/theme/widget/widget-theme.pkg package wti = widget_theme_imp; # widget_theme_imp is from
src/lib/x-kit/widget/xkit/theme/widget/default/widget-theme-imp.pkg package r8 = rgb8; # rgb8 is from
src/lib/x-kit/xclient/src/color/rgb8.pkg package r64 = rgb; # rgb is from
src/lib/x-kit/xclient/src/color/rgb.pkg package wi = widget_imp; # widget_imp is from
src/lib/x-kit/widget/xkit/theme/widget/default/look/widget-imp.pkg package g2d = geometry2d; # geometry2d is from
src/lib/std/2d/geometry2d.pkg package g2j = geometry2d_junk; # geometry2d_junk is from
src/lib/std/2d/geometry2d-junk.pkg package mtx = rw_matrix; # rw_matrix is from
src/lib/std/src/rw-matrix.pkg package pp = standard_prettyprinter; # standard_prettyprinter is from
src/lib/prettyprint/big/src/standard-prettyprinter.pkg package gtg = guiboss_to_guishim; # guiboss_to_guishim is from
src/lib/x-kit/widget/theme/guiboss-to-guishim.pkg nb = log::note_on_stderr; # log is from
src/lib/std/src/log.pkgherein
package horizontal_int_slider
: Horizontal_Int_Slider # Horizontal_Int_Slider is from
src/lib/x-kit/widget/leaf/horizontal-int-slider.api {
App_To_Horizontal_Int_Slider
=
{ id: Id,
#
get_active: Void -> Bool,
get_value: Void -> Int,
#
get_lower_limit: Void -> Int,
get_upper_limit: Void -> Int,
get_coverage: Void -> Float,
#
get_slider_text: Void -> Null_Or(String),
set_slider_text: Null_Or(String) -> Void,
#
set_active_to: Bool -> Void,
set_value_to: Int -> Void, # Also calls gadget_to_guiboss.needs_redraw_gadget_request(id);
#
set_lower_limit_to: Int -> Void,
set_upper_limit_to: Int -> Void,
set_coverage_to: Float -> Void
};
Redraw_Fn_Arg
=
REDRAW_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
frame_number: Int, # 1,2,3,... Purely for convenience of widget, guiboss-imp makes no use of this.
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Window rectangle in which to draw.
popup_nesting_depth: Int, # 0 for gadgets on basewindow, 1 for gadgets on popup on basewindow, 2 for gadgets on popup on popup, etc.
#
duration_in_seconds: Float, # If state has changed look-imp should call note_changed_gadget_foreground() before this time is up. Also useful for motionblur.
widget_to_guiboss: gt::Widget_To_Guiboss,
gadget_mode: gt::Gadget_Mode,
#
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
palette: wt::Gadget_Palette,
#
default_redraw_fn: Redraw_Fn,
lower_limit: Int,
upper_limit: Int,
coverage: Float,
#
show_limits: Bool,
show_value: Bool,
#
slider_value: Int, # A value between lower_limit and upper_limit.
slider_relief: wt::Relief, # Is the slider outline a slope, a ridge, or a flat band?
text: Null_Or(String),
fonts: List(String),
font_weight: Null_Or(wt::Font_Weight),
font_size: Null_Or(Int),
no_box: Bool,
margin: Int,
thick: Int
}
withtype
Redraw_Fn
=
Redraw_Fn_Arg
->
{ displaylist: gd::Gui_Displaylist,
point_in_gadget: Null_Or(g2d::Point -> Bool), #
point_to_value: g2d::Point -> Int, #
pixels_high_min: Int,
pixels_wide_min: Int
}
;
Mouse_Click_Fn_Arg
=
MOUSE_CLICK_FN_ARG # Needs to be a sumtype because of recursive reference in default_mouse_click_fn.
{ id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event: gt::Mousebutton_Event, # MOUSEBUTTON_PRESS or MOUSEBUTTON_RELEASE.
button: evt::Mousebutton, # Which mousebutton was pressed/released.
point: g2d::Point, # Where the mouse was.
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_mouse_click_fn: Mouse_Click_Fn,
#
lower_limit: Int,
upper_limit: Int,
coverage: Float,
#
show_limits: Bool,
show_value: Bool,
#
slider_value: Int, # A value between lower_limit and upper_limit.
slider_relief: wt::Relief, # Is the slider outline a slope, a ridge, or a flat band?
point_to_value: g2d::Point -> Int,
#
initial_value: Int, # Original state of slider.
note_value: Int -> Void, # Change state of slider. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this slider needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
withtype
Mouse_Click_Fn = Mouse_Click_Fn_Arg -> Void;
Mouse_Drag_Fn_Arg
=
MOUSE_DRAG_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
start_point: g2d::Point,
last_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
phase: gt::Drag_Phase,
button: evt::Mousebutton,
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_mouse_drag_fn: Mouse_Drag_Fn,
#
lower_limit: Int,
upper_limit: Int,
coverage: Float,
#
show_limits: Bool,
show_value: Bool,
#
slider_value: Int, # A value between lower_limit and upper_limit.
slider_relief: wt::Relief, # Is the slider outline a slope, a ridge, or a flat band?
point_to_value: g2d::Point -> Int,
#
initial_value: Int, # Original state of slider.
note_value: Int -> Void, # Change state of slider. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this slider needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
withtype
Mouse_Drag_Fn = Mouse_Drag_Fn_Arg -> Void;
Mouse_Transit_Fn_Arg # Note that buttons are always all up in a mouse-transit event -- otherwise it is a mouse-drag event.
=
MOUSE_TRANSIT_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
transit: gt::Gadget_Transit, # Mouse is entering (CAME) or leaving (LEFT) widget, or moving (MOVE) across it.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_mouse_transit_fn: Mouse_Transit_Fn,
#
lower_limit: Int,
upper_limit: Int,
coverage: Float,
#
show_limits: Bool,
show_value: Bool,
#
slider_value: Int, # A value between lower_limit and upper_limit.
slider_relief: wt::Relief, # Is the slider outline a slope, a ridge, or a flat band?
point_to_value: g2d::Point -> Int,
#
initial_value: Int, # Original state of slider.
note_value: Int -> Void, # Change state of slider. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this slider needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
withtype
Mouse_Transit_Fn = Mouse_Transit_Fn_Arg -> Void;
Key_Event_Fn_Arg
=
KEY_EVENT_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
keystroke: gt::Keystroke_Info, # Keystring etc for event.
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
widget_to_guiboss: gt::Widget_To_Guiboss,
guiboss_to_widget: gt::Guiboss_To_Widget, # Used by textpane.pkg keystroke-macro stuff to synthesize fake keystroke events to widget.
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_key_event_fn: Key_Event_Fn,
#
lower_limit: Int,
upper_limit: Int,
coverage: Float,
#
show_limits: Bool,
show_value: Bool,
#
slider_value: Int, # A value between lower_limit and upper_limit.
slider_relief: wt::Relief, # Is the slider outline a slope, a ridge, or a flat band?
point_to_value: g2d::Point -> Int,
#
initial_value: Int, # Original state of slider.
note_value: Int -> Void, # Change state of slider. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this slider needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
withtype
Key_Event_Fn = Key_Event_Fn_Arg -> Void;
Option = PIXELS_SQUARE Int
#
| PIXELS_HIGH_MIN Int
| PIXELS_WIDE_MIN Int
#
| PIXELS_HIGH_CUT Float
| PIXELS_WIDE_CUT Float
#
| LOWER_LIMIT Int
# Smallest value which slider value is allowed to assume. Defaults to 0.
| UPPER_LIMIT Int
# Largest value which slider value is allowed to assume. Defaults to 1000.
| COVERAGE Float
#
#
| SHOW_LIMITS Bool
# If TRUE, display limits in decimal on slider widget. Defaults to TRUE.
| SHOW_VALUE Bool
# If TRUE, display value in decimal on slider widget. Defaults to TRUE.
#
| INITIAL_VALUE Int
| INITIALLY_ACTIVE Bool
#
| BODY_COLOR rgb::Rgb
| BODY_COLOR_WITH_MOUSEFOCUS rgb::Rgb
#
| ID Id
| DOC String
#
| RELIEF wt::Relief
# Should slider boundary be drawn flat, raised, sunken, ridged or grooved?
| MARGIN Int
# How many pixels to inset slider relative to its assigned window site. Default is 4.
| THICK Int
# Thickness of lines (well, polygons) forming slider. Default is 5.
| NO_BOX
# Do not draw a box around slider gutter.
#
| TEXT String
# Text to draw inside slider. Default is "".
#
| 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.
| KEY_EVENT_FN Key_Event_Fn
# Application-specific handler for keyboard input.
#
| INT_OUT (Int -> Void)
# Widget's current state will be sent to these fns each time state changes.
| PORTWATCHER (Null_Or(App_To_Horizontal_Int_Slider) -> 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,
#
widget_id,
widget_doc,
#
relief,
margin,
thick,
no_box,
#
text,
#
fonts,
font_weight,
font_size,
#
redraw_fn,
mouse_click_fn,
mouse_drag_fn,
mouse_transit_fn,
key_event_fn,
#
lower_limit,
upper_limit,
coverage,
#
show_limits,
show_value,
#
initial_value,
initially_active,
#
widget_options,
#
portwatchers,
int_outs,
sitewatchers
}
)
=
{ my_body_color = REF body_color;
my_body_color_with_mousefocus = REF body_color_with_mousefocus;
#
my_widget_id = REF widget_id;
my_widget_doc = REF widget_doc;
#
my_relief = REF relief;
my_margin = REF margin;
my_thick = REF thick;
my_no_box = REF no_box;
#
my_text = REF text;
#
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_key_event_fn = REF key_event_fn;
#
my_lower_limit = lower_limit;
my_upper_limit = upper_limit;
my_coverage = coverage;
#
my_show_limits = REF show_limits;
my_show_value = REF show_value;
#
my_initial_value = REF initial_value;
my_initially_active = REF initially_active;
#
my_widget_options = REF widget_options;
#
my_portwatchers = REF portwatchers;
my_int_outs = REF int_outs;
my_sitewatchers = REF sitewatchers;
#
apply do_option options
where
fun do_option (LOWER_LIMIT b) => my_lower_limit := b;
do_option (UPPER_LIMIT b) => my_upper_limit := b;
do_option (COVERAGE f) => my_coverage := f;
#
do_option (SHOW_LIMITS b) => my_show_limits := b;
do_option (SHOW_VALUE b) => my_show_value := b;
#
do_option (INITIAL_VALUE b) => my_initial_value := b;
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 (ID i) => my_widget_id := THE i;
do_option (DOC d) => my_widget_doc := d;
#
do_option (RELIEF r) => my_relief := r;
do_option (MARGIN i) => my_margin := i;
do_option (THICK i) => my_thick := i;
do_option (NO_BOX ) => my_no_box := TRUE;
#
do_option (TEXT t) => my_text := THE t;
#
do_option (FONT_SIZE i) => my_font_size := THE i;
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 (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 := f;
do_option (MOUSE_TRANSIT_FN f) => my_mouse_transit_fn := f;
do_option (KEY_EVENT_FN f) => my_key_event_fn := THE f;
#
do_option (PORTWATCHER c) => my_portwatchers := c ! *my_portwatchers;
do_option (INT_OUT c) => my_int_outs := c ! *my_int_outs;
do_option (SITEWATCHER c) => my_sitewatchers := c ! *my_sitewatchers;
#
do_option (PIXELS_HIGH_MIN i) => my_widget_options := (wi::PIXELS_HIGH_MIN i) ! *my_widget_options;
do_option (PIXELS_WIDE_MIN i) => my_widget_options := (wi::PIXELS_WIDE_MIN i) ! *my_widget_options;
#
do_option (PIXELS_HIGH_CUT f) => my_widget_options := (wi::PIXELS_HIGH_CUT f) ! *my_widget_options;
do_option (PIXELS_WIDE_CUT f) => my_widget_options := (wi::PIXELS_WIDE_CUT f) ! *my_widget_options;
#
do_option (PIXELS_SQUARE i) => my_widget_options := (wi::PIXELS_HIGH_MIN i)
! (wi::PIXELS_WIDE_MIN i)
! (wi::PIXELS_HIGH_CUT 0.0)
! (wi::PIXELS_WIDE_CUT 0.0)
! *my_widget_options;
end;
end;
{ body_color => *my_body_color,
body_color_with_mousefocus => *my_body_color_with_mousefocus,
#
widget_id => *my_widget_id,
widget_doc => *my_widget_doc,
#
relief => *my_relief,
margin => *my_margin,
thick => *my_thick,
no_box => *my_no_box,
#
text => *my_text,
#
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,
key_event_fn => *my_key_event_fn,
#
# lower_limit => my_lower_limit,
# upper_limit => my_upper_limit,
# coverage => my_coverage,
#
show_limits => *my_show_limits,
show_value => *my_show_value,
#
initial_value => *my_initial_value,
initially_active => *my_initially_active,
#
widget_options => *my_widget_options,
#
portwatchers => *my_portwatchers,
int_outs => *my_int_outs,
#
sitewatchers => *my_sitewatchers
};
};
fun default_redraw_fn (REDRAW_FN_ARG a) # Handle a guiboss request to redraw ourself.
=
{ background_box = a.site;
coverage = a.coverage;
lower_limit = a.lower_limit;
margin = a.margin;
relief = a.slider_relief;
site = a.site;
slider_value = a.slider_value;
thick = a.thick;
upper_limit = a.upper_limit;
background = [ gd::COLOR (a.palette.surround_color, [ gd::FILLED_BOXES [ background_box ]]) ];
inner_box = g2d::box::make_nested_box (background_box, margin); #
gutter_box = g2d::box::make_nested_box ( inner_box, thick ); #
fun get_fontnames ()
=
{ font_size_to_use
=
case a.font_size THE i => i;
NULL => *a.theme.default_font_size;
esac;
fontname_to_use
=
case a.font_weight THE wt::ROMAN_FONT => *a.theme.get_roman_fontname font_size_to_use;
THE wt::ITALIC_FONT => *a.theme.get_italic_fontname font_size_to_use;
THE wt::BOLD_FONT => *a.theme.get_bold_fontname font_size_to_use;
NULL => *a.theme.get_roman_fontname font_size_to_use;
esac;
fontnames = a.fonts @ [ fontname_to_use, "9x15" ];
fontnames;
};
fun get_text_dimensions (text: String)
=
{ g = wti::get__guiboss_to_hostwindow a.theme;
#
font = g.get_font (get_fontnames ());
{ font_ascent => font.font_height.ascent,
font_descent => font.font_height.descent,
length_in_pixels => font.string_length_in_pixels text
};
};
fun point_to_value (point: g2d::Point)
=
{ gutter_box -> { row, col, high, wide };
#
wide = int::max (wide, 1); # Prevent divide-by-zero;
fpixels = float::from_int wide;
fvalues = float::from_int ((upper_limit - lower_limit) + 1);
p_to_v = fvalues / fpixels;
fvalue = float::from_int (point.col - col) * p_to_v;
value = float::round fvalue;
value = int::min (value, upper_limit);
value = int::max (value, lower_limit);
value;
};
fun thumb_displaylist { lower_limit, slider_value, upper_limit, gutter_box, coverage } # Thumb shows portion of file currently visible in window. If coverage==1.0, all the file is visible and thumb fills gutter. If coverage==0.5, half the file is visible, and thumb fills half of gutter.
= # Position of thumb shows which part of file is visible: Top, middle, bottom, whatever.
{ gutter_box -> { row, col, high, wide };
#
thumb_width = float::round ((float::from_int wide) * coverage); # Pixel height of thumb.
thumb_range = (float::from_int wide) * (1.0 - coverage); # Number of pixels which thumb is free to move.
value_range = float::from_int ((upper_limit - lower_limit) + 1); # Number of values which slider_value is free to range over.
fvalue = float::from_int (upper_limit - slider_value); # Zero-based value of slider_value.
v_to_p = thumb_range / value_range; # Conversion factor from slider_value range to thumb range.
thumb_lo = col + wide - (float::round (fvalue * v_to_p));
thumb_hi = thumb_lo - thumb_width;
thumb_box = { row => row + 2, col => thumb_hi, high => high - 4, wide => thumb_width }; #
thumb_body = [ gd::COLOR ( rgb::black, [ gd::FILLED_BOXES [ thumb_box ]]) ];
thumb_body;
};
fun cursor_displaylist { lower_limit, slider_value, upper_limit, gutter_box }
=
{ gutter_box -> { row, col, high, wide };
#
fpixels = float::from_int wide;
fvalues = float::from_int ((upper_limit - lower_limit) + 1);
fvalue = float::from_int slider_value;
v_to_p = fpixels / fvalues;
cursor_mid = col + (float::round (fvalue * v_to_p));
cursor_wide2 = 10; # Half-width of cursor.
cursor_width = 2*cursor_wide2 + 1;
cursor_col = cursor_mid - cursor_wide2;
cursor_box = { row => row + 4, col => cursor_col, high => high - 8, wide => cursor_width }; # "+ 4" and "- 8" so the cursor outline is cleanly separated from the gutter frame.
(g2d::box::box_corners cursor_box)
->
{ upper_left, lower_left, lower_right, upper_right };
top_mid = g2d::point::mean [ upper_left, upper_right ];
bot_mid = g2d::point::mean [ lower_left, lower_right ];
cursor_outline = [ bot_mid, top_mid, upper_left, lower_left, lower_right, upper_right, top_mid ];
[ gd::COLOR ( rgb::white, [ gd::FILLED_BOXES [ cursor_box ]]) ]
@
[ gd::COLOR ( rgb::rgb_mix01(0.9,rgb::black,rgb::white), [ gd::LINE_THICKNESS (0, [ gd::PATH cursor_outline ]) ]) ];
};
foreground = [ gd::COLOR (a.palette.body_color, [ gd::FILLED_POLYGON (g2d::box::to_points inner_box) ]) ]; # Interior of gutter. We draw this first because 3D outline occupies same bounding box:
foreground = if (coverage == 0.0) foreground;
else foreground @ thumb_displaylist { lower_limit, slider_value, upper_limit, gutter_box, coverage };
fi;
foreground = foreground @ cursor_displaylist { lower_limit, slider_value, upper_limit, gutter_box }; # Draw cursor next because we want it to overwrite gutter interior but be overwritten by gutter frame.
foreground = case a.no_box FALSE => foreground @ *a.theme.pictureframe a.palette { box => inner_box, thick, relief }; # 3-D outline for gutter.
TRUE => foreground;
esac;
foreground = { fontnames = get_fontnames ();
#
lotext = sprintf "%d" lower_limit;
mitext = sprintf "%d" slider_value;
hitext = sprintf "%d" upper_limit;
lodims = get_text_dimensions lotext;
hidims = get_text_dimensions hitext;
mipoint = g2d::box::midpoint inner_box;
textrow = mipoint.row - lodims.font_descent + ((lodims.font_ascent + lodims.font_descent) / 2);
lopoint = { row => textrow, col => site.col + 10 };
mipoint = { row => textrow, col => mipoint.col };
hipoint = { row => textrow, col => site.col + site.wide - 10 };
lodraw = [ gd::PUT_TEXT ( gd::TO_RIGHT_OF_POINT,
[ gd::TEXT (lopoint, lotext) ]
)
];
midraw = case (a.text, a.show_value)
#
(NULL, FALSE ) => [ ];
(NULL, TRUE ) => [ gd::PUT_TEXT ( gd::CENTERED_ON_POINT,
[ gd::TEXT (mipoint, mitext) ]
)
];
(THE t, FALSE) => [ gd::PUT_TEXT ( gd::CENTERED_ON_POINT,
[ gd::TEXT (mipoint, t ) ]
)
];
(THE t, TRUE ) => [ gd::PUT_TEXT ( gd::TO_LEFT_OF_POINT,
[ gd::TEXT (mipoint, t + ": ") ]
),
gd::PUT_TEXT ( gd::TO_RIGHT_OF_POINT,
[ gd::TEXT (mipoint, mitext) ]
)
];
esac;
hidraw = [ gd::PUT_TEXT ( gd::TO_LEFT_OF_POINT,
[ gd::TEXT (hipoint, hitext) ]
)
];
display_list = if a.show_limits lodraw @ midraw @ hidraw;
else midraw;
fi;
display_list = case display_list [] => [];
_ => [ gd::COLOR ( a.palette.text_color, [ gd::FONT (fontnames, display_list) ] ) ];
esac;
foreground @ display_list;
};
fun point_in_gadget (point: g2d::Point)
=
g2d::point::in_box (point, inner_box);
point_in_gadget = THE point_in_gadget;
{ displaylist => background @ foreground,
point_in_gadget,
point_to_value,
pixels_high_min => 0,
pixels_wide_min => 0
};
};
fun default_mouse_click_fn (MOUSE_CLICK_FN_ARG a)
=
if (a.modifier_keys_state == evt::no_modifier_keys_were_down)
#
button = a.button;
lower_limit = a.lower_limit;
needs_redraw_gadget_request = a.needs_redraw_gadget_request;
note_value = a.note_value;
slider_value = a.slider_value;
upper_limit = a.upper_limit;
if (button == evt::button4 # Mousewheel forward.
and slider_value < upper_limit)
#
note_value (slider_value + 1);
needs_redraw_gadget_request ();
fi;
if (button == evt::button5 # Mousewheel backward.
and slider_value > lower_limit)
#
note_value (slider_value - 1);
needs_redraw_gadget_request ();
fi;
();
fi;
fun default_mouse_drag_fn
(
MOUSE_DRAG_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
start_point: g2d::Point,
last_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
phase: gt::Drag_Phase,
button: evt::Mousebutton,
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_mouse_drag_fn: Mouse_Drag_Fn,
#
lower_limit: Int,
upper_limit: Int,
coverage: Float,
#
show_limits: Bool,
show_value: Bool,
#
slider_value: Int, # A value between lower_limit and upper_limit.
slider_relief: wt::Relief, # Is the slider outline a slope, a ridge, or a flat band?
point_to_value: g2d::Point -> Int,
#
initial_value: Int, # Original state of slider.
note_value: Int -> Void, # Change state of slider. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this slider needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
)
=
{
if ( modifier_keys_state == evt::no_modifier_keys_were_down
and
mousebuttons_state
==
{ mousebutton_1_was_down => TRUE,
mousebutton_2_was_down => FALSE,
mousebutton_3_was_down => FALSE,
mousebutton_4_was_down => FALSE,
mousebutton_5_was_down => FALSE
}
)
# At the moment we don't care which phase we're in, so we ignore it.
# The following at least documents how to key on phase if desired:
#
case phase
#
gt::DONE => (); #
gt::OPEN => (); #
gt::DRAG => (); #
esac;
value = point_to_value event_point;
note_value value;
needs_redraw_gadget_request ();
fi;
();
};
fun default_mouse_transit_fn (MOUSE_TRANSIT_FN_ARG a)
=
case a.transit
#
gt::CAME => a.needs_redraw_gadget_request (); # So slider will lighten when mouse enters it.
gt::LEFT => a.needs_redraw_gadget_request (); # So slider will revert when mosue leaves it.
_ => ();
esac;
fun with (options: List(Option)) # PUBLIC. The point of the 'with' name is that GUI coders can write 'horizontal_int_slider::with { this => that, foo => bar, ... }.'
=
{
textref = REF (NULL: Null_Or(String));
lower_limit = REF 0;
upper_limit = REF 1000;
coverage = REF 0.0;
point_to_value = REF (\\ _ = *lower_limit);
(process_options
(
options,
#
{ body_color => NULL,
body_color_with_mousefocus => NULL,
#
widget_id => NULL,
widget_doc => "<horizontal_int_slider>",
#
relief => wt::SUNKEN,
margin => 0,
thick => 5,
no_box => FALSE,
#
text => *textref,
#
fonts => [],
font_weight => THE wt::BOLD_FONT, # Bold seems to work much better than roman for buttons and sliders.
font_size => (NULL: Null_Or(Int)),
#
redraw_fn => default_redraw_fn,
mouse_click_fn => default_mouse_click_fn,
mouse_drag_fn => default_mouse_drag_fn,
mouse_transit_fn => default_mouse_transit_fn,
key_event_fn => NULL,
#
lower_limit,
upper_limit,
coverage,
#
show_limits => TRUE,
show_value => TRUE,
#
initial_value => 0,
initially_active => TRUE,
#
widget_options => [],
#
portwatchers => [],
int_outs => [],
sitewatchers => []
}
) )
->
{ # These values are globally visible to the subsequenc fns, which can lock them in as needed.
body_color,
body_color_with_mousefocus,
#
widget_id,
widget_doc,
#
relief,
margin,
thick,
no_box,
#
text,
#
fonts,
font_weight,
font_size,
#
redraw_fn,
mouse_click_fn,
mouse_drag_fn,
mouse_transit_fn,
key_event_fn,
#
# lower_limit,
# upper_limit,
# coverage,
#
show_limits,
show_value,
#
initial_value,
initially_active,
#
widget_options,
#
portwatchers,
int_outs,
sitewatchers
};
textref := text;
#######################################
# Top of per-imp state variable section
#
widget_to_guiboss__global
=
REF (NULL: Null_Or((gt::Widget_To_Guiboss, Id)));
fun note_changed_gadget_activity (is_active: Bool)
=
case (*widget_to_guiboss__global)
#
THE (widget_to_guiboss, id) => widget_to_guiboss.g.note_changed_gadget_activity { id, is_active };
NULL => ();
esac;
fun needs_redraw_gadget_request ()
=
case (*widget_to_guiboss__global)
#
THE (widget_to_guiboss, id) => widget_to_guiboss.g.needs_redraw_gadget_request(id);
NULL => ();
esac;
last_known_site
=
REF ( { col => -1, wide => -1,
row => -1, high => -1
}: g2d::Box
);
slider_value = REF initial_value;
slider_active
=
REF initially_active;
exception SAVED_STATE { last_known_site: g2d::Box, # Here we're doing the usual hack of using Exception as an extensible datatype -- nothing to do with actually raising or trapping exceptions.
slider_value: Int,
slider_active: Bool
};
fun note_site (id: Id, site: g2d::Box)
=
if(*last_known_site != site)
last_known_site := site;
#
apply tell_watcher sitewatchers
where
fun tell_watcher sitewatcher
=
sitewatcher (THE (id,site));
end;
fi;
fun note_value (state: Int)
=
if(*slider_value != state)
slider_value := state;
#
apply tell_watcher int_outs
where
fun tell_watcher int_out
=
int_out state;
end;
fi;
#
# End of state variable section
###############################
#####################
# Top of port section
#
# Here we implement our App_To_Slider port:
fun set_active_to (is_active: Bool)
=
{ slider_active := is_active;
#
note_changed_gadget_activity is_active;
};
fun set_value_to (state: Int)
=
{ note_value state;
#
needs_redraw_gadget_request ();
};
fun get_active ()
=
*slider_active;
fun get_value ()
=
*slider_value;
fun get_slider_text () = *textref;
fun set_slider_text t = { textref := t;
needs_redraw_gadget_request ();
};
fun get_lower_limit () = *lower_limit;
fun set_lower_limit_to i = { lower_limit := i;
if (*slider_value < *lower_limit)
slider_value := *lower_limit;
fi;
if (*upper_limit < *lower_limit)
upper_limit := *lower_limit;
fi;
needs_redraw_gadget_request ();
};
fun get_upper_limit () = *upper_limit;
fun set_upper_limit_to i = { upper_limit := i;
if (*slider_value > *upper_limit)
slider_value := *upper_limit;
fi;
if (*lower_limit > *upper_limit)
lower_limit := *upper_limit;
fi;
needs_redraw_gadget_request ();
};
fun get_coverage () = *coverage;
fun set_coverage_to f = { f = float::max (0.0, f);
f = float::min (1.0, f);
coverage := f;
needs_redraw_gadget_request ();
};
#
# End of port section
#####################
###############################
# Top of widget hook fn section
#
# These fns get called by widget_imp logic, ultimately # widget_imp is from
src/lib/x-kit/widget/xkit/theme/widget/default/look/widget-imp.pkg # in response to user mouseclicks and keypresses etc:
fun startup_fn
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
widget_to_guiboss: gt::Widget_To_Guiboss,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue
}
=
{ widget_to_guiboss__global
:=
THE (widget_to_guiboss, id);
app_to_horizontal_int_slider
=
{ id,
#
get_active,
get_value,
#
get_lower_limit,
get_upper_limit,
get_coverage,
#
get_slider_text,
set_slider_text,
#
set_active_to,
set_value_to,
#
set_lower_limit_to,
set_upper_limit_to,
set_coverage_to
}
: App_To_Horizontal_Int_Slider
;
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_horizontal_int_slider);
end;
();
};
fun shutdown_fn () # Return to widget_imp an exception packaging up our state; this will be returned to guiboss_imp, saved in the
= # Paused_Gui tree, and passed to our startup_fn when/if gui is restarted. This exception will never be raised;
{ apply tell_watcher portwatchers #
where
fun tell_watcher portwatcher
=
portwatcher NULL;
end;
apply tell_watcher sitewatchers
where
fun tell_watcher sitewatcher
=
sitewatcher NULL;
end;
};
fun initialize_gadget_fn
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
site: g2d::Box, # Window rectangle in which to draw.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
pass_font: List(String) -> Replyqueue
-> (evt::Font -> Void) -> Void, # Nonblocking version of next, for use in imps.
get_font: List(String) -> evt::Font, # Accepts a list of font names which are tried in order.
make_rw_pixmap: g2d::Size -> g2p::Gadget_To_Rw_Pixmap,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
=
{ note_site (id,site);
();
};
fun redraw_request_fn_wrapper
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
frame_number: Int, # 1,2,3,... Purely for convenience of widget-imp, guiboss-imp makes no use of this.
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Window rectangle in which to draw.
popup_nesting_depth: Int, # 0 for gadgets on basewindow, 1 for gadgets on popup on basewindow, 2 for gadgets on popup on popup, etc.
#
duration_in_seconds: Float, # If state has changed widget-imp should call redraw_gadget() before this time is up. Also useful for motionblur.
widget_to_guiboss: gt::Widget_To_Guiboss,
gadget_mode: gt::Gadget_Mode,
#
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void,
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
=
{ note_site (id,site);
#
palette = *theme.current_gadget_colors { gadget_is_on => FALSE,
gadget_mode,
popup_nesting_depth,
#
body_color,
body_color_with_mousefocus,
body_color_when_on => NULL,
body_color_when_on_with_mousefocus => NULL
};
text = *textref;
redraw_fn_arg
=
REDRAW_FN_ARG
{ id,
doc,
frame_number,
frame_indent_hint,
site,
popup_nesting_depth,
duration_in_seconds,
widget_to_guiboss,
gadget_mode,
theme,
do,
to,
palette,
#
default_redraw_fn,
#
lower_limit => *lower_limit,
upper_limit => *upper_limit,
coverage => *coverage,
#
show_limits,
show_value,
#
slider_value => *slider_value,
slider_relief => relief,
text,
fonts,
font_weight,
font_size,
no_box,
margin,
thick
};
(redraw_fn redraw_fn_arg)
->
{ displaylist,
point_in_gadget,
point_to_value => p2v,
pixels_high_min,
pixels_wide_min
};
point_to_value := p2v;
widget_to_guiboss.g.redraw_gadget { id, site, displaylist, point_in_gadget };
};
fun mouse_click_fn_wrapper # This a callback we hand to
src/lib/x-kit/widget/xkit/theme/widget/default/look/widget-imp.pkg {
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event: gt::Mousebutton_Event, # MOUSEBUTTON_PRESS or MOUSEBUTTON_RELEASE.
button: evt::Mousebutton,
point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
=
{ note_site (id,site);
#
mouse_click_fn_arg
=
MOUSE_CLICK_FN_ARG
{
id,
doc,
event,
button,
point,
widget_layout_hint,
frame_indent_hint,
site,
modifier_keys_state,
mousebuttons_state,
widget_to_guiboss,
theme,
do,
to,
#
default_mouse_click_fn,
#
lower_limit => *lower_limit,
upper_limit => *upper_limit,
coverage => *coverage,
#
show_limits,
show_value,
#
slider_value => *slider_value, # We don't pass the refcell here because we want client code to make state changes via note_value(), which will properly notify all state-watchers.
slider_relief => relief,
point_to_value => *point_to_value,
#
initial_value,
note_value,
needs_redraw_gadget_request
};
mouse_click_fn mouse_click_fn_arg;
};
fun mouse_drag_fn_wrapper # This a callback we hand to
src/lib/x-kit/widget/xkit/theme/widget/default/look/widget-imp.pkg (
{ id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
start_point: g2d::Point,
last_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
phase: gt::Drag_Phase,
button: evt::Mousebutton,
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
)
=
{ note_site (id,site);
#
mouse_drag_fn_arg
=
MOUSE_DRAG_FN_ARG
{
id,
doc,
event_point,
start_point,
last_point,
widget_layout_hint,
frame_indent_hint,
site,
phase,
button,
modifier_keys_state,
mousebuttons_state,
widget_to_guiboss,
theme,
do,
to,
#
default_mouse_drag_fn,
#
lower_limit => *lower_limit,
upper_limit => *upper_limit,
coverage => *coverage,
#
show_limits,
show_value,
#
slider_value => *slider_value, # We don't pass the refcell here because we want client code to make state changes via note_value(), which will properly notify all state-watchers.
slider_relief => relief,
point_to_value => *point_to_value,
#
initial_value,
note_value,
needs_redraw_gadget_request
};
mouse_drag_fn mouse_drag_fn_arg;
};
fun mouse_transit_fn_wrapper
#
( arg as
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
transit: gt::Gadget_Transit, # Mouse is entering (CAME) or leaving (LEFT) widget, or moving (MOVE) across it.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
)
=
{ note_site (id,site);
#
mouse_transit_fn_arg
=
MOUSE_TRANSIT_FN_ARG
{
id,
doc,
event_point,
widget_layout_hint,
frame_indent_hint,
site,
transit,
modifier_keys_state,
widget_to_guiboss,
theme,
do,
to,
#
default_mouse_transit_fn,
#
lower_limit => *lower_limit,
upper_limit => *upper_limit,
coverage => *coverage,
#
show_limits,
show_value,
#
slider_value => *slider_value, # We don't pass the refcell here because we want client code to make state changes via note_value(), which will properly notify all state-watchers.
slider_relief => relief,
point_to_value => *point_to_value,
#
initial_value,
note_value,
needs_redraw_gadget_request
};
mouse_transit_fn mouse_transit_fn_arg;
();
};
fun key_event_fn_wrapper
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
keystroke: gt::Keystroke_Info, # Keystring etc for event.
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
widget_to_guiboss: gt::Widget_To_Guiboss,
guiboss_to_widget: gt::Guiboss_To_Widget, # Used by textpane.pkg keystroke-macro stuff to synthesize fake keystroke events to widget.
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
=
{ note_site (id,site);
#
key_event_fn_arg
=
KEY_EVENT_FN_ARG
{
id,
doc,
keystroke,
widget_layout_hint,
frame_indent_hint,
site,
widget_to_guiboss,
guiboss_to_widget,
theme,
do,
to,
#
default_key_event_fn => \\ _ = (), # Default key event behavior for sliders is to do absolutely nothing.
#
lower_limit => *lower_limit,
upper_limit => *upper_limit,
coverage => *coverage,
#
show_limits,
show_value,
#
slider_value => *slider_value, # We don't pass the refcell here because we want client code to make state changes via note_value(), which will properly notify all state-watchers.
slider_relief => relief,
point_to_value => *point_to_value,
#
initial_value,
note_value,
needs_redraw_gadget_request
};
case key_event_fn
#
THE key_event_fn => key_event_fn key_event_fn_arg;
NULL => (); # We do not expect this case to happen: If key_event_fn is NULL key_event_fn_wrapper should not have been registered with widget-imp so we should never get called.
esac;
();
};
#
# End of widget hook fn section
###############################
widget_options
=
case key_event_fn
#
THE _ => (wi::KEY_EVENT_FN key_event_fn_wrapper) ! widget_options; # Register for key events only if we are going to use them.
NULL => widget_options;
esac;
widget_options
=
case widget_id
#
THE id => (wi::ID id) ! widget_options; #
NULL => widget_options;
esac;
widget_options
=
[ wi::STARTUP_FN startup_fn, # We always register for these five because our base behavior depends on them.
wi::SHUTDOWN_FN shutdown_fn,
wi::INITIALIZE_GADGET_FN initialize_gadget_fn,
wi::REDRAW_REQUEST_FN redraw_request_fn_wrapper,
wi::MOUSE_CLICK_FN mouse_click_fn_wrapper,
wi::MOUSE_DRAG_FN mouse_drag_fn_wrapper,
wi::MOUSE_TRANSIT_FN mouse_transit_fn_wrapper,
wi::DOC widget_doc
]
@
widget_options
;
make_widget_fn = wi::make_widget_start_fn widget_options;
gt::WIDGET make_widget_fn; # So caller can write guiplan = gt::ROW [ frame::with [...], frame::with [...], ... ];
}; # PUBLIC
};
end;