## checkbox.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### "The general application of the transistor in
### radio and television is far in the future."
### -- Lee deForest, 1952
# 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 checkbox
: Checkbox # Checkbox is from
src/lib/x-kit/widget/leaf/checkbox.api {
package p { # "p" for "position".
#
Text_Position = TEXT_AT_LEFT
| TEXT_AT_RIGHT
| TEXT_IN_CENTER
;
};
package t { # "t" for "type".
#
Button_Type = MOMENTARY_CONTACT
| PUSH_ON_PUSH_OFF
| IGNORE_MOUSECLICKS
;
};
App_To_Checkbox
=
{ id: Id,
#
get_active: Void -> Bool,
get_state: Void -> Bool,
#
get_button_type: Void -> t::Button_Type, #
#
get_button_text: Void -> Null_Or(String),
get_button_on_text: Void -> Null_Or(String),
get_button_off_text: Void -> Null_Or(String),
set_button_text: Null_Or(String) -> Void,
set_button_on_text: Null_Or(String) -> Void,
set_button_off_text: Null_Or(String) -> Void,
#
set_active_to: Bool -> Void,
set_state_to: Bool -> Void # Also calls gadget_to_guiboss.needs_redraw_gadget_request(id);
};
Redraw_Fn_Arg
=
REDRAW_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
frame_number: Int, # 1,2,3,... Purely for convenience of widget, guiboss-imp makes no use of this.
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Window rectangle in which to draw.
popup_nesting_depth: Int, # 0 for gadgets on basewindow, 1 for gadgets on popup on basewindow, 2 for gadgets on popup on popup, etc.
duration_in_seconds: Float, # If state has changed look-imp should call note_changed_gadget_foreground() before this time is up. Also useful for motionblur.
widget_to_guiboss: gt::Widget_To_Guiboss,
gadget_mode: gt::Gadget_Mode,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
palette: wt::Gadget_Palette,
#
default_redraw_fn: Redraw_Fn,
#
button_state: Bool, # Is the button ON or OFF?
button_type: t::Button_Type, # Is the button push-on-push-off or momentary-contact?
text_position: Null_Or(p::Text_Position),
text: Null_Or(String),
#
fonts: List(String),
font_weight: Null_Or(wt::Font_Weight),
font_size: Null_Or(Int),
margin: Int,
thick: Int
}
withtype
Redraw_Fn
=
Redraw_Fn_Arg
->
{ displaylist: gd::Gui_Displaylist,
point_in_gadget: Null_Or(g2d::Point -> Bool), #
pixels_high_min: Int,
pixels_wide_min: Int
}
;
Mouse_Click_Fn_Arg
=
MOUSE_CLICK_FN_ARG # Needs to be a sumtype because of recursive reference in default_mouse_click_fn.
{ id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event: gt::Mousebutton_Event, # MOUSEBUTTON_PRESS or MOUSEBUTTON_RELEASE.
button: evt::Mousebutton, # Which mousebutton was pressed/released.
point: g2d::Point, # Where the mouse was.
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_mouse_click_fn: Mouse_Click_Fn,
#
button_state: Bool, # Is the button ON or OFF?
button_type: t::Button_Type, # Is the button push-on-push-off or momentary-contact?
#
initial_state: Bool, # Original state of button.
note_state: Bool -> Void, # Change state of button. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this button needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
withtype
Mouse_Click_Fn = Mouse_Click_Fn_Arg -> Void;
Mouse_Drag_Fn_Arg
=
MOUSE_DRAG_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
start_point: g2d::Point,
last_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
phase: gt::Drag_Phase,
button: evt::Mousebutton,
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_mouse_drag_fn: Mouse_Drag_Fn,
#
button_state: Bool, # Is the button ON or OFF?
button_type: t::Button_Type, # Is the button push-on-push-off or momentary-contact?
#
initial_state: Bool, # Original state of button.
note_state: Bool -> Void, # Change state of button. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this button needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
withtype
Mouse_Drag_Fn = Mouse_Drag_Fn_Arg -> Void;
Mouse_Transit_Fn_Arg # Note that buttons are always all up in a mouse-transit event -- otherwise it is a mouse-drag event.
=
MOUSE_TRANSIT_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
transit: gt::Gadget_Transit, # Mouse is entering (CAME) or leaving (LEFT) widget, or moving (MOVE) across it.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_mouse_transit_fn: Mouse_Transit_Fn,
#
button_state: Bool, # Is the button ON or OFF?
button_type: t::Button_Type, # Is the button push-on-push-off or momentary-contact?
#
initial_state: Bool, # Original state of button.
note_state: Bool -> Void, # Change state of button. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this button needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
withtype
Mouse_Transit_Fn = Mouse_Transit_Fn_Arg -> Void;
Key_Event_Fn_Arg
=
KEY_EVENT_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
keystroke: gt::Keystroke_Info, # Keystring etc for event.
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
widget_to_guiboss: gt::Widget_To_Guiboss,
guiboss_to_widget: gt::Guiboss_To_Widget, # Used by textpane.pkg keystroke-macro stuff to synthesize fake keystroke events to widget.
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_key_event_fn: Key_Event_Fn,
#
button_state: Bool, # Is the button ON or OFF?
button_type: t::Button_Type, # Is the button push-on-push-off or momentary-contact?
#
initial_state: Bool, # Original state of button.
note_state: Bool -> Void, # Change state of button. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this button needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
withtype
Key_Event_Fn = Key_Event_Fn_Arg -> Void;
Option = PIXELS_SQUARE Int
#
| PIXELS_HIGH_MIN Int
| PIXELS_WIDE_MIN Int
#
| PIXELS_HIGH_CUT Float
| PIXELS_WIDE_CUT Float
#
| INITIAL_STATE Bool
| INITIALLY_ACTIVE Bool
#
| MOMENTARY_CONTACT
# State is non-default (opposite of INITIAL_STATE) only between mouse downclick and upclick.
| PUSH_ON_PUSH_OFF
# Mouse downclicks toggle state between TRUE and FALSE.
| IGNORE_MOUSECLICKS
# Mouseclicks to not affect state.
#
| ID Id
| DOC String
#
| MARGIN Int
# How many pixels to inset button relative to its assigned window site. Default is 4.
| THICK Int
# Thickness of lines (well, polygons) forming button. Default is 5.
#
| TEXT_AT_LEFT
| TEXT_AT_RIGHT
| TEXT_IN_CENTER
#
| TEXT String
# Text to draw inside button. Default is "".
| ON_TEXT String
# Text to draw inside button when switch is ON. Default is TEXT else "".
| OFF_TEXT String
# Text to draw inside button when switch is OFF. Default is TEXT else "".
#
| FONT_SIZE Int
# Show any text in this pointsize. Default is 12.
| FONTS List(String)
# Override theme font: Font to use for text label, e.g. "-*-courier-bold-r-*-*-20-*-*-*-*-*-*-*". We'll use the first font in list which is found on X server, else "9x15" (which X guarantees to have).
#
| ROMAN
# Show any text in plain font from widget-theme. This is the default.
| ITALIC
# Show any text in italic font from widget-theme.
| BOLD
# Show any text in bold font from widget-theme. NB: Text is either bold or italic, not both.
#
| REDRAW_FN Redraw_Fn
# Application-specific handler for widget redraw.
| MOUSE_CLICK_FN Mouse_Click_Fn
# Application-specific handler for mousebutton clicks.
| MOUSE_DRAG_FN Mouse_Drag_Fn
# Application-specific handler for mouse drags.
| MOUSE_TRANSIT_FN Mouse_Transit_Fn
# Application-specific handler for mouse crossings.
| KEY_EVENT_FN Key_Event_Fn
# Application-specific handler for keyboard input.
#
| BOOL_OUT (Bool -> Void)
# Widget's current state will be sent to these fns each time state changes.
| PORTWATCHER (Null_Or(App_To_Checkbox) -> Void)
# Widget's app port will be sent to these fns at widget startup.
| SITEWATCHER (Null_Or((Id,g2d::Box)) -> Void)
# Widget's site in window coordinates will be sent to these fns each time it changes.
; # To help prevent deadlock, watcher fns should be fast and nonblocking, typically just setting a var or entering something into a mailqueue.
fun process_options
( options: List(Option),
#
{ button_type,
#
widget_id,
widget_doc,
#
margin,
thick,
#
text_position,
text,
on_text,
off_text,
#
fonts,
font_weight,
font_size,
#
redraw_fn,
mouse_click_fn,
mouse_drag_fn,
mouse_transit_fn,
key_event_fn,
#
initial_state,
initially_active,
#
widget_options,
#
portwatchers,
bool_outs,
sitewatchers
}
)
=
{ my_button_type = REF button_type;
#
my_widget_id = REF widget_id;
my_widget_doc = REF widget_doc;
#
my_margin = REF margin;
my_thick = REF thick;
#
my_text_position = REF text_position;
my_text = REF text;
my_on_text = REF on_text;
my_off_text = REF off_text;
#
my_fonts = REF fonts;
my_font_weight = REF font_weight;
my_font_size = REF font_size;
#
my_redraw_fn = REF redraw_fn;
my_mouse_click_fn = REF mouse_click_fn;
my_mouse_drag_fn = REF mouse_drag_fn;
my_mouse_transit_fn = REF mouse_transit_fn;
my_key_event_fn = REF key_event_fn;
#
my_initial_state = REF initial_state;
my_initially_active = REF initially_active;
#
my_widget_options = REF widget_options;
#
my_portwatchers = REF portwatchers;
my_bool_outs = REF bool_outs;
my_sitewatchers = REF sitewatchers;
#
apply do_option options
where
fun do_option (INITIAL_STATE b) => my_initial_state := b;
do_option (INITIALLY_ACTIVE b) => my_initially_active := b;
#
do_option (MOMENTARY_CONTACT ) => my_button_type := t::MOMENTARY_CONTACT;
do_option (PUSH_ON_PUSH_OFF ) => my_button_type := t::PUSH_ON_PUSH_OFF;
do_option (IGNORE_MOUSECLICKS ) => my_button_type := t::IGNORE_MOUSECLICKS;
#
do_option (ID i) => my_widget_id := THE i;
do_option (DOC d) => my_widget_doc := d;
#
do_option (MARGIN i) => my_margin := i;
do_option (THICK i) => my_thick := i;
#
do_option (TEXT_AT_LEFT ) => my_text_position := THE p::TEXT_AT_LEFT;
do_option (TEXT_AT_RIGHT ) => my_text_position := THE p::TEXT_AT_RIGHT;
do_option (TEXT_IN_CENTER ) => my_text_position := THE p::TEXT_IN_CENTER;
#
do_option (TEXT t) => my_text := THE t;
do_option (ON_TEXT t) => my_on_text := THE t;
do_option (OFF_TEXT t) => my_off_text := THE t;
#
do_option (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 := THE f;
do_option (MOUSE_TRANSIT_FN f) => my_mouse_transit_fn := f;
do_option (KEY_EVENT_FN f) => my_key_event_fn := THE f;
#
do_option (PORTWATCHER c) => my_portwatchers := c ! *my_portwatchers;
do_option (BOOL_OUT c) => my_bool_outs := c ! *my_bool_outs;
do_option (SITEWATCHER c) => my_sitewatchers := c ! *my_sitewatchers;
#
#
do_option (PIXELS_HIGH_MIN i) => my_widget_options := (wi::PIXELS_HIGH_MIN i) ! *my_widget_options;
do_option (PIXELS_WIDE_MIN i) => my_widget_options := (wi::PIXELS_WIDE_MIN i) ! *my_widget_options;
#
do_option (PIXELS_HIGH_CUT f) => my_widget_options := (wi::PIXELS_HIGH_CUT f) ! *my_widget_options;
do_option (PIXELS_WIDE_CUT f) => my_widget_options := (wi::PIXELS_WIDE_CUT f) ! *my_widget_options;
#
do_option (PIXELS_SQUARE i) => my_widget_options := (wi::PIXELS_HIGH_MIN i)
! (wi::PIXELS_WIDE_MIN i)
! (wi::PIXELS_HIGH_CUT 0.0)
! (wi::PIXELS_WIDE_CUT 0.0)
! *my_widget_options;
end;
end;
{ button_type => *my_button_type,
#
widget_id => *my_widget_id,
widget_doc => *my_widget_doc,
#
margin => *my_margin,
thick => *my_thick,
#
text_position => *my_text_position,
text => *my_text,
on_text => *my_on_text,
off_text => *my_off_text,
#
fonts => *my_fonts,
font_weight => *my_font_weight,
font_size => *my_font_size,
#
redraw_fn => *my_redraw_fn,
mouse_click_fn => *my_mouse_click_fn,
mouse_drag_fn => *my_mouse_drag_fn,
mouse_transit_fn => *my_mouse_transit_fn,
key_event_fn => *my_key_event_fn,
#
initial_state => *my_initial_state,
initially_active => *my_initially_active,
#
widget_options => *my_widget_options,
#
portwatchers => *my_portwatchers,
bool_outs => *my_bool_outs,
sitewatchers => *my_sitewatchers
};
};
fun default_redraw_fn (REDRAW_FN_ARG a) # Handle a guiboss request to redraw ourself.
=
{ button_state = a.button_state;
font_size = a.font_size;
font_weight = a.font_weight;
fonts = a.fonts;
margin = a.margin;
palette = a.palette;
site = a.site;
text = a.text;
text_position = a.text_position;
theme = a.theme;
box_line_width = 2; # XXX SUCKO FIXME these should be supplied like 'margin' and 'thick', which should be eliminated if we're not going to use them.
check_line_width = 3;
background_box = site;
background = [ gd::COLOR (palette.surround_color, [ gd::FILLED_BOXES [ background_box ]]) ];
inner_box = g2d::box::make_nested_box (background_box, margin); #
fun pick_text_position_to_use ()
=
case text_position
#
THE p => p; # If programmer explicitly specified a position, use that.
#
NULL => p::TEXT_AT_LEFT; # By default, position the text next to the checkbox.
esac;
text_position_to_use = pick_text_position_to_use ();
fun get_fontnames ()
=
{ font_size_to_use
=
case font_size THE i => i;
NULL => *theme.default_font_size;
esac;
fontname_to_use
=
case font_weight THE wt::ROMAN_FONT => *theme.get_roman_fontname font_size_to_use;
THE wt::ITALIC_FONT => *theme.get_italic_fontname font_size_to_use;
THE wt::BOLD_FONT => *theme.get_bold_fontname font_size_to_use;
NULL => *theme.get_roman_fontname font_size_to_use;
esac;
fontnames = fonts @ [ fontname_to_use, "9x15" ];
fontnames;
};
fun get_text_dimensions (text: String)
=
{ g = wti::get__guiboss_to_hostwindow theme;
#
font = g.get_font (get_fontnames ());
{ font_ascent => font.font_height.ascent,
font_descent => font.font_height.descent,
length_in_pixels => font.string_length_in_pixels text
};
};
fun text_displaylist
(
text: String,
text_box: g2d::Box
)
=
{ text_dimensions = get_text_dimensions text;
#
fontnames = get_fontnames ();
case text_position_to_use
#
p::TEXT_IN_CENTER
=>
{ (g2d::box::midpoint text_box)
->
{ row, col };
row = row - text_dimensions.font_descent + ((text_dimensions.font_ascent + text_dimensions.font_descent) / 2);
draw_point = { row, col };
[ gd::COLOR ( palette.text_color,
[ gd::FONT ( fontnames,
[ gd::PUT_TEXT ( gd::CENTERED_ON_POINT,
[ gd::TEXT (draw_point, text) ]
)
]
)
]
)
];
};
p::TEXT_AT_LEFT
=>
{ box_corners = g2d::box::box_corners text_box;
#
(g2d::point::mean [ box_corners.upper_left, box_corners.lower_left ])
->
{ row, col };
# Indent text a bit from image and also
# center it properly vertically -- most
# fonts have ascent > descent:
#
col = col + 10;
row = row - text_dimensions.font_descent + ((text_dimensions.font_ascent + text_dimensions.font_descent) / 2);
#
draw_point = { row, col };
[ gd::COLOR ( palette.text_color,
[ gd::FONT ( fontnames,
[ gd::PUT_TEXT ( gd::TO_RIGHT_OF_POINT,
[ gd::TEXT (draw_point, text) ]
)
]
)
]
)
];
};
p::TEXT_AT_RIGHT #
=>
{ box_corners = g2d::box::box_corners text_box;
#
(g2d::point::mean [ box_corners.upper_right, box_corners.lower_right ])
->
{ row, col };
# Indent text a bit from image and also
# center it properly vertically -- most
# fonts have ascent > descent:
#
col = col - 10 - text_dimensions.length_in_pixels;
row = row - text_dimensions.font_descent + ((text_dimensions.font_ascent + text_dimensions.font_descent) / 2);
#
draw_point = { row, col };
[ gd::COLOR ( palette.text_color,
[ gd::FONT ( fontnames,
[ gd::PUT_TEXT ( gd::TO_RIGHT_OF_POINT,
[ gd::TEXT (draw_point, text) ]
)
]
)
]
)
];
};
esac;
};
fun make_check_points (checkbox: g2d::Box)
=
[ { col=> checkbox.col + 4, row=> checkbox.row + (checkbox.high / 2) }, # XXX SUCKO FIXME the additive constants here suck because they don't scale properly. They should be replaced with some suitable fraction of box size.
{ col=> checkbox.col + (checkbox.wide / 2), row=> checkbox.row + checkbox.high - 4 },
{ col=> checkbox.col + checkbox.wide + 4, row=> checkbox.row - (checkbox.high / 6) }
];
# Construct displaylist for checkbox proper:
#
my (foreground, text_indent, checkbox)
=
case text
#
THE txt => { inner_box -> { row, col, wide, high }; # We have a text label for the checkbox, so draw checkbox at left sized to match text.
#
(get_text_dimensions txt)
->
{ font_ascent,
font_descent,
length_in_pixels
};
box_size = font_ascent + font_descent;
row' = (high - box_size) / 2;
col' = 10;
checkbox = { col => col + col', high => box_size,
row => row + row', wide => box_size
};
box_points = g2d::box::to_points checkbox;
check_points = make_check_points checkbox;
box_displaylist = [ gd::COLOR ( palette.body_color, [ gd::FILLED_POLYGON box_points ]), # Interior of button. We draw this first because 3D outline occupies same bounding box.
#
gd::COLOR ( palette.text_color,
[ gd::LINE_THICKNESS ( box_line_width, [ gd::POLYGON box_points ]) ]
)
];
check_displaylist = [ gd::COLOR ( palette.text_color,
[ gd::LINE_THICKNESS (check_line_width, [ gd::PATH check_points ]) ]
)
];
foreground = button_state ?? box_displaylist @ check_displaylist
:: box_displaylist;
text_indent = box_size + 10;
(foreground, text_indent, checkbox);
};
NULL => { inner_box -> { row, col, wide, high }; # No text, so just center checkbox in available site.
#
box_size = (int::min (wide, high)) / 2;
row' = (high - box_size) / 2;
col' = (wide - box_size) / 2;
checkbox = { col => col + col', high => box_size,
row => row + row', wide => box_size
};
box_points = g2d::box::to_points checkbox;
check_points = make_check_points checkbox;
box_displaylist = [ gd::COLOR ( palette.body_color, [ gd::FILLED_POLYGON box_points ]), # Interior of button. We draw this first because 3D outline occupies same bounding box.
#
gd::COLOR ( palette.text_color,
[ gd::LINE_THICKNESS ( box_line_width, [ gd::POLYGON box_points ]) ]
)
];
check_displaylist = [ gd::COLOR ( palette.text_color,
[ gd::LINE_THICKNESS (check_line_width, [ gd::PATH check_points ]) ]
)
];
foreground = button_state ?? box_displaylist @ check_displaylist
:: box_displaylist;
text_indent = 0;
(foreground, text_indent, checkbox);
};
esac;
text_box = { row => inner_box.row,
col => inner_box.col + text_indent,
high => inner_box.high,
wide => inner_box.wide - text_indent
};
# Maybe incorporate text into button foreground:
#
foreground
=
case text
#
NULL => foreground;
#
THE t => {
foreground @ (text_displaylist (t, text_box));
};
esac;
fun point_in_gadget (point: g2d::Point)
=
g2d::point::in_box (point, checkbox);
point_in_gadget = THE point_in_gadget;
{ displaylist => background @ foreground,
point_in_gadget,
pixels_high_min => 0,
pixels_wide_min => 0
};
};
fun default_mouse_click_fn (MOUSE_CLICK_FN_ARG a)
=
if (a.button == evt::button1
and a.modifier_keys_state == evt::no_modifier_keys_were_down)
#
button_state = a.button_state;
button_type = a.button_type;
event = a.event;
initial_state = a.initial_state;
needs_redraw_gadget_request = a.needs_redraw_gadget_request;
note_state = a.note_state;
#
case event
#
gt::MOUSEBUTTON_PRESS
=>
if (button_type != t::IGNORE_MOUSECLICKS)
#
note_state (not button_state);
needs_redraw_gadget_request ();
fi;
gt::MOUSEBUTTON_RELEASE
=>
if (button_type == t::MOMENTARY_CONTACT)
#
note_state initial_state;
needs_redraw_gadget_request ();
fi;
esac;
();
fi;
fun default_mouse_transit_fn (MOUSE_TRANSIT_FN_ARG a)
=
case a.transit
#
gt::CAME => a.needs_redraw_gadget_request (); # So button will lighten when mouse enters it.
gt::LEFT => a.needs_redraw_gadget_request (); # So button will revert when mosue leaves it.
_ => ();
esac;
fun with (options: List(Option)) # PUBLIC. The point of the 'with' name is that GUI coders can write 'checkbox::with { this => that, foo => bar, ... }.'
=
{
textref = REF (NULL: Null_Or(String));
ontextref = REF (NULL: Null_Or(String));
offtextref = REF (NULL: Null_Or(String));
(process_options
(
options,
#
{ button_type => t::PUSH_ON_PUSH_OFF,
#
widget_id => NULL,
widget_doc => "<checkbox>",
#
margin => 4,
thick => 5,
#
text_position => (NULL: Null_Or(p::Text_Position)),
text => *textref,
on_text => *ontextref,
off_text => *offtextref,
#
fonts => [],
font_weight => (NULL: Null_Or(wt::Font_Weight)),
font_size => (NULL: Null_Or(Int)),
#
redraw_fn => default_redraw_fn,
mouse_click_fn => default_mouse_click_fn,
mouse_drag_fn => NULL,
mouse_transit_fn => default_mouse_transit_fn,
key_event_fn => NULL,
#
initial_state => FALSE,
initially_active => TRUE,
#
widget_options => [],
#
portwatchers => [],
bool_outs => [],
sitewatchers => []
}
) )
->
{ # These values are globally visible to the subsequenc fns, which can lock them in as needed.
button_type,
#
widget_id,
widget_doc,
#
margin,
thick,
#
text_position,
text,
on_text,
off_text,
#
fonts,
font_weight,
font_size,
#
redraw_fn,
mouse_click_fn,
mouse_drag_fn,
mouse_transit_fn,
key_event_fn,
#
initial_state,
initially_active,
#
widget_options,
#
portwatchers,
bool_outs,
sitewatchers
};
textref := text;
ontextref := on_text;
offtextref := off_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
);
button_state = REF initial_state;
button_active
=
REF initially_active;
exception SAVED_STATE { last_known_site: g2d::Box, # Here we're doing the usual hack of using Exception as an extensible datatype -- nothing to do with actually raising or trapping exceptions.
button_state: Bool,
button_active: Bool
};
fun note_site (id: Id, site: g2d::Box)
=
if(*last_known_site != site)
last_known_site := site;
#
apply tell_watcher sitewatchers
where
fun tell_watcher sitewatcher
=
sitewatcher (THE (id,site));
end;
fi;
fun note_state (state: Bool)
=
if(*button_state != state)
button_state := state;
#
apply tell_watcher bool_outs
where
fun tell_watcher bool_out
=
bool_out state;
end;
fi;
#
# End of state variable section
###############################
#####################
# Top of port section
#
# Here we implement our App_To_Button port:
fun set_active_to (is_active: Bool)
=
{ button_active := is_active;
#
note_changed_gadget_activity is_active;
};
fun set_state_to (state: Bool)
=
{ note_state state;
#
needs_redraw_gadget_request ();
};
fun get_active ()
=
*button_active;
fun get_state ()
=
*button_state;
fun get_button_type ()
=
button_type;
fun get_button_text () = *textref;
fun get_button_on_text () = *ontextref;
fun get_button_off_text () = *offtextref;
fun set_button_text t = { textref := t; needs_redraw_gadget_request (); };
fun set_button_on_text t = { ontextref := t; needs_redraw_gadget_request (); };
fun set_button_off_text t = { offtextref := t; needs_redraw_gadget_request (); };
#
# 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_checkbox
=
{ id,
#
get_active,
get_state,
get_button_type,
#
get_button_text,
get_button_on_text,
get_button_off_text,
set_button_text,
set_button_on_text,
set_button_off_text,
set_active_to,
set_state_to
}
: App_To_Checkbox
;
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_checkbox);
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, # FALSE instead of button_state because I think it looks silly to set interior of checkbox to white when it is set.
gadget_mode,
popup_nesting_depth,
#
body_color => NULL,
body_color_when_on => NULL,
body_color_with_mousefocus => NULL,
body_color_when_on_with_mousefocus => NULL
};
text = if *button_state
#
case *ontextref
#
THE t => THE t; # Button is ON so use "ON" text.
NULL => *textref; # Button is ON but no "ON" text so use plain text (or none).
esac;
else
case *offtextref
#
THE t => THE t; # Button is OFF so use "OFF" text.
NULL => *textref; # Button is OFF but no "OFF" text so use plain text (or none).
esac;
fi;
redraw_fn_arg
=
REDRAW_FN_ARG
{ id,
doc,
frame_number,
frame_indent_hint,
site,
popup_nesting_depth,
duration_in_seconds,
widget_to_guiboss,
gadget_mode,
theme,
do,
to,
palette,
#
default_redraw_fn,
#
button_state => *button_state,
button_type,
text_position,
text,
#
fonts,
font_weight,
font_size,
margin,
thick
};
(redraw_fn redraw_fn_arg)
->
{ displaylist,
point_in_gadget,
pixels_high_min,
pixels_wide_min
};
widget_to_guiboss.g.redraw_gadget { id, site, displaylist, point_in_gadget };
};
fun mouse_click_fn_wrapper # This a callback we hand to
src/lib/x-kit/widget/xkit/theme/widget/default/look/widget-imp.pkg {
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event: gt::Mousebutton_Event, # MOUSEBUTTON_PRESS or MOUSEBUTTON_RELEASE.
button: evt::Mousebutton,
point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
=
{ note_site (id,site);
#
mouse_click_fn_arg
=
MOUSE_CLICK_FN_ARG
{
id,
doc,
event,
button,
point,
widget_layout_hint,
frame_indent_hint,
site,
modifier_keys_state,
mousebuttons_state,
widget_to_guiboss,
theme,
do,
to,
#
default_mouse_click_fn,
#
button_state => *button_state, # We don't pass the refcell here because we want client code to make state changes via note_state(), which will properly notify all state-watchers.
button_type,
#
initial_state,
note_state,
needs_redraw_gadget_request
};
mouse_click_fn mouse_click_fn_arg;
};
fun mouse_drag_fn_wrapper # This a callback we hand to
src/lib/x-kit/widget/xkit/theme/widget/default/look/widget-imp.pkg (
{ id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
start_point: g2d::Point,
last_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
phase: gt::Drag_Phase,
button: evt::Mousebutton,
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
)
=
{ note_site (id,site);
#
mouse_drag_fn_arg
=
MOUSE_DRAG_FN_ARG
{
id,
doc,
event_point,
start_point,
last_point,
widget_layout_hint,
frame_indent_hint,
site,
phase,
button,
modifier_keys_state,
mousebuttons_state,
widget_to_guiboss,
theme,
do,
to,
#
default_mouse_drag_fn => \\ _ = (), # Default drag behavior for buttons is to do absolutely nothing.
#
button_state => *button_state, # We don't pass the refcell here because we want client code to make state changes via note_state(), which will properly notify all state-watchers.
button_type,
#
initial_state,
note_state,
needs_redraw_gadget_request
};
case mouse_drag_fn
#
THE mouse_drag_fn => mouse_drag_fn mouse_drag_fn_arg;
NULL => (); # We do not expect this case to happen: If mouse_drag_fn is NULL mouse_drag_fn_wrapper should not have been registered with widget-imp so we should never get called.
esac;
};
fun mouse_transit_fn_wrapper
#
( arg as
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
transit: gt::Gadget_Transit, # Mouse is entering (CAME) or leaving (LEFT) widget, or moving (MOVE) across it.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
)
=
{ note_site (id,site);
#
mouse_transit_fn_arg
=
MOUSE_TRANSIT_FN_ARG
{
id,
doc,
event_point,
widget_layout_hint,
frame_indent_hint,
site,
transit,
modifier_keys_state,
widget_to_guiboss,
theme,
do,
to,
#
default_mouse_transit_fn,
#
button_state => *button_state, # We don't pass the refcell here because we want client code to make state changes via note_state(), which will properly notify all state-watchers.
button_type,
#
initial_state,
note_state,
needs_redraw_gadget_request
};
mouse_transit_fn mouse_transit_fn_arg;
();
};
fun key_event_fn_wrapper
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
keystroke: gt::Keystroke_Info, # Keystring etc for event.
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
widget_to_guiboss: gt::Widget_To_Guiboss,
guiboss_to_widget: gt::Guiboss_To_Widget, # Used by textpane.pkg keystroke-macro stuff to synthesize fake keystroke events to widget.
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
=
{ note_site (id,site);
#
key_event_fn_arg
=
KEY_EVENT_FN_ARG
{
id,
doc,
keystroke,
widget_layout_hint,
frame_indent_hint,
site,
widget_to_guiboss,
guiboss_to_widget,
theme,
do,
to,
#
default_key_event_fn => \\ _ = (), # Default key event behavior for buttons is to do absolutely nothing.
#
button_state => *button_state, # We don't pass the refcell here because we want client code to make state changes via note_state(), which will properly notify all state-watchers.
button_type,
#
initial_state,
note_state,
needs_redraw_gadget_request
};
case key_event_fn
#
THE key_event_fn => key_event_fn key_event_fn_arg;
NULL => (); # We do not expect this case to happen: If key_event_fn is NULL key_event_fn_wrapper should not have been registered with widget-imp so we should never get called.
esac;
();
};
#
# End of widget hook fn section
###############################
widget_options
=
case mouse_drag_fn
#
THE _ => (wi::MOUSE_DRAG_FN mouse_drag_fn_wrapper) ! widget_options; # Register for drag events only if we are going to use them.
NULL => widget_options;
esac;
widget_options
=
case key_event_fn
#
THE _ => (wi::KEY_EVENT_FN key_event_fn_wrapper) ! widget_options; # Register for key events only if we are going to use them.
NULL => widget_options;
esac;
widget_options
=
case widget_id
#
THE id => (wi::ID id) ! widget_options; #
NULL => widget_options;
esac;
widget_options
=
[ wi::STARTUP_FN startup_fn, # We always register for these five because our base behavior depends on them.
wi::SHUTDOWN_FN shutdown_fn,
wi::INITIALIZE_GADGET_FN initialize_gadget_fn,
wi::REDRAW_REQUEST_FN redraw_request_fn_wrapper,
wi::MOUSE_CLICK_FN mouse_click_fn_wrapper,
wi::MOUSE_TRANSIT_FN mouse_transit_fn_wrapper,
wi::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;