## roundbutton.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 problem is to compress a room full
### of digital computation equipment into
### the size of a suitcase, then a shoe box,
### and finally small enough to hold in the
### palm of the hand."
### -- Jack Staller, 1959
# 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 roundbutton
: Roundbutton # Roundbutton is from
src/lib/x-kit/widget/leaf/roundbutton.api {
package t { # "t" for "type".
#
Button_Type = MOMENTARY_CONTACT
| PUSH_ON_PUSH_OFF
| IGNORE_MOUSECLICKS
;
};
App_To_Roundbutton
=
{ id: Id,
#
get_active: Void -> Bool,
get_state: Void -> Bool,
#
get_button_relief: Void -> wt::Relief, #
get_button_type: Void -> t::Button_Type, #
#
get_button_text: Void -> Null_Or(String),
get_button_on_text: Void -> Null_Or(String),
get_button_off_text: Void -> Null_Or(String),
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);
set_button_relief_to: wt::Relief -> Void # Also calls gadget_to_guiboss.needs_redraw_gadget_request(id);
};
Redraw_Fn_Arg
=
REDRAW_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
frame_number: Int, # 1,2,3,... Purely for convenience of widget, guiboss-imp makes no use of this.
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Window rectangle in which to draw.
popup_nesting_depth: Int, # 0 for gadgets on basewindow, 1 for gadgets on popup on basewindow, 2 for gadgets on popup on popup, etc.
#
duration_in_seconds: Float, # If state has changed look-imp should call note_changed_gadget_foreground() before this time is up. Also useful for motionblur.
widget_to_guiboss: gt::Widget_To_Guiboss,
gadget_mode: gt::Gadget_Mode,
#
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
palette: wt::Gadget_Palette,
#
default_redraw_fn: Redraw_Fn,
#
button_state: Bool, # Is the button ON or OFF?
button_type: t::Button_Type, # Is the button push-on-push-off or momentary-contact?
button_relief: wt::Relief, # Is the button outline a slope, a ridge, or a flat band?
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?
button_relief: Ref(wt::Relief), # Is the button outline a slope, a ridge, or a flat band?
#
initial_state: Bool, # Original state of button.
note_state: Bool -> Void, # Change state of button. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this button needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
withtype
Mouse_Click_Fn = Mouse_Click_Fn_Arg -> Void;
Mouse_Drag_Fn_Arg
=
MOUSE_DRAG_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
start_point: g2d::Point,
last_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
phase: gt::Drag_Phase,
button: evt::Mousebutton,
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_mouse_drag_fn: Mouse_Drag_Fn,
#
button_state: Bool, # Is the button ON or OFF?
button_type: t::Button_Type, # Is the button push-on-push-off or momentary-contact?
button_relief: Ref(wt::Relief), # Is the button outline a slope, a ridge, or a flat band?
#
initial_state: Bool, # Original state of button.
note_state: Bool -> Void, # Change state of button. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this button needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
withtype
Mouse_Drag_Fn = Mouse_Drag_Fn_Arg -> Void;
Mouse_Transit_Fn_Arg # Note that buttons are always all up in a mouse-transit event -- otherwise it is a mouse-drag event.
=
MOUSE_TRANSIT_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
transit: gt::Gadget_Transit, # Mouse is entering (CAME) or leaving (LEFT) widget, or moving (MOVE) across it.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_mouse_transit_fn: Mouse_Transit_Fn,
#
button_state: Bool, # Is the button ON or OFF?
button_type: t::Button_Type, # Is the button push-on-push-off or momentary-contact?
button_relief: Ref(wt::Relief), # Is the button outline a slope, a ridge, or a flat band?
#
initial_state: Bool, # Original state of button.
note_state: Bool -> Void, # Change state of button. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this button needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
withtype
Mouse_Transit_Fn = Mouse_Transit_Fn_Arg -> Void;
Key_Event_Fn_Arg
=
KEY_EVENT_FN_ARG
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
keystroke: gt::Keystroke_Info, # Keystring etc for event.
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
widget_to_guiboss: gt::Widget_To_Guiboss,
guiboss_to_widget: gt::Guiboss_To_Widget, # Used by textpane.pkg keystroke-macro stuff to synthesize fake keystroke events to widget.
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue, # Used to call 'pass_*' methods in other imps.
#
default_key_event_fn: Key_Event_Fn,
#
button_state: Bool, # Is the button ON or OFF?
button_type: t::Button_Type, # Is the button push-on-push-off or momentary-contact?
button_relief: Ref(wt::Relief), # Is the button outline a slope, a ridge, or a flat band?
#
initial_state: Bool, # Original state of button.
note_state: Bool -> Void, # Change state of button. This takes care of notifying our state-watchers. (Does NOT call needs_redraw_gadget_request.)
needs_redraw_gadget_request: Void -> Void # Notify guiboss-imp that this button needs to be redrawn (i.e., sent a redraw_gadget_request()).
}
withtype
Key_Event_Fn = Key_Event_Fn_Arg -> Void;
Option = PIXELS_SQUARE Int
#
| PIXELS_HIGH_MIN Int
| PIXELS_WIDE_MIN Int
#
| PIXELS_HIGH_CUT Float
| PIXELS_WIDE_CUT Float
#
| INITIAL_STATE Bool
| INITIALLY_ACTIVE Bool
#
| MOMENTARY_CONTACT
# State is non-default (opposite of INITIAL_STATE) only between mouse downclick and upclick.
| PUSH_ON_PUSH_OFF
# Mouse downclicks toggle state between TRUE and FALSE.
| IGNORE_MOUSECLICKS
# Mouseclicks to not affect state.
#
| BODY_COLOR rgb::Rgb
| BODY_COLOR_WITH_MOUSEFOCUS rgb::Rgb
| BODY_COLOR_WHEN_ON rgb::Rgb
| BODY_COLOR_WHEN_ON_WITH_MOUSEFOCUS rgb::Rgb
#
| ID Id
| DOC String
#
| RELIEF wt::Relief
# Should button boundary be drawn flat, raised, sunken, ridged or grooved?
| MARGIN Int
# How many pixels to inset button relative to its assigned window site. Default is 4.
| THICK Int
# Thickness of lines (well, polygons) forming button. Default is 5.
#
| 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_Roundbutton) -> Void)
# Widget's app port will be sent to these fns at widget startup.
| SITEWATCHER (Null_Or((Id,g2d::Box)) -> Void)
# Widget's site in window coordinates will be sent to these fns each time it changes.
; # To help prevent deadlock, watcher fns should be fast and nonblocking, typically just setting a var or entering something into a mailqueue.
fun process_options
( options: List(Option),
#
{ button_type,
#
body_color,
body_color_with_mousefocus,
body_color_when_on,
body_color_when_on_with_mousefocus,
#
widget_id,
widget_doc,
#
relief,
margin,
thick,
#
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_body_color = REF body_color;
my_body_color_with_mousefocus = REF body_color_with_mousefocus;
my_body_color_when_on = REF body_color_when_on;
my_body_color_when_on_with_mousefocus = REF body_color_when_on_with_mousefocus;
#
my_widget_id = REF widget_id;
my_widget_doc = REF widget_doc;
#
my_relief = REF relief;
my_margin = REF margin;
my_thick = REF thick;
#
my_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 (BODY_COLOR c) => my_body_color := THE c;
do_option (BODY_COLOR_WITH_MOUSEFOCUS c) => my_body_color_with_mousefocus := THE c;
do_option (BODY_COLOR_WHEN_ON c) => my_body_color_when_on := THE c;
do_option (BODY_COLOR_WHEN_ON_WITH_MOUSEFOCUS c) => my_body_color_when_on_with_mousefocus := THE c;
#
do_option (ID i) => my_widget_id := THE i;
do_option (DOC d) => my_widget_doc := d;
#
do_option (RELIEF r) => my_relief := r;
do_option (MARGIN i) => my_margin := i;
do_option (THICK i) => my_thick := i;
#
do_option (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,
#
body_color => *my_body_color,
body_color_with_mousefocus => *my_body_color_with_mousefocus,
body_color_when_on => *my_body_color_when_on,
body_color_when_on_with_mousefocus => *my_body_color_when_on_with_mousefocus,
#
widget_id => *my_widget_id,
widget_doc => *my_widget_doc,
#
relief => *my_relief,
margin => *my_margin,
thick => *my_thick,
#
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.
=
{ 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;
theme = a.theme;
d = a.popup_nesting_depth;
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 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 ();
(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) ]
)
]
)
]
)
];
};
stipulate
inner_box -> { row, col, high, wide };
wide2 = wide / 2;
high2 = high / 2;
radius = int::min (wide2, high2) - 1;
outer_diameter = 2*radius;
inner_diameter = (outer_diameter * 9) / 10;
bwid = (outer_diameter - inner_diameter) / 2;
herein
outer_disk = { col=> col, row=> row, wide=>outer_diameter, high=>outer_diameter, start_angle=> 0.0, fill_angle=> 360.0 };
inner_disk = { col=> col + bwid, row=> row + bwid, wide=>inner_diameter, high=>inner_diameter, start_angle=> 0.0, fill_angle=> 360.0 };
outline = [ gd::COLOR
(
(*theme.shady_bevel_color)(d),
[ gd::FILLED_ARCS [ outer_disk ]]
),
gd::COLOR
(
# button_state ?? (*theme.sunny_bevel_color)(d) :: palette.body_color,
palette.body_color,
[ gd::FILLED_ARCS [ inner_disk ]]
)
];
foreground = outline;
midpoint = g2d::box::midpoint inner_box;
fun point_in_gadget (point: g2d::Point) # Mouseclick has hit gadget if it is within 'radius' of button center.
=
{ d = g2d::point::subtract (point, midpoint);
#
(d.row * d.row + d.col * d.col)
<
radius * radius
;
};
point_in_gadget = THE point_in_gadget;
end;
text_box = inner_box;
# Maybe incorporate text into button foreground:
#
foreground
=
case text
#
NULL => foreground;
#
THE t => {
foreground @ (text_displaylist (t, text_box));
};
esac;
{ 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 'roundbutton::with { this => that, foo => bar, ... }.'
=
{
reliefref = REF wt::RAISED; #
#
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,
#
body_color => NULL,
body_color_with_mousefocus => NULL,
body_color_when_on => NULL,
body_color_when_on_with_mousefocus => NULL,
#
widget_id => NULL,
widget_doc => "<roundbutton>",
#
relief => *reliefref,
margin => 4,
thick => 5,
#
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,
#
body_color,
body_color_with_mousefocus,
body_color_when_on,
body_color_when_on_with_mousefocus,
#
widget_id,
widget_doc,
#
relief,
margin,
thick,
#
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
};
reliefref := relief;
#
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 set_button_relief_to (relief: wt::Relief)
=
{
reliefref := relief;
#
needs_redraw_gadget_request ();
};
fun get_active ()
=
*button_active;
fun get_state ()
=
*button_state;
fun get_button_relief ()
=
*reliefref;
fun get_button_type ()
=
button_type;
fun get_button_text () = *textref;
fun get_button_on_text () = *ontextref;
fun get_button_off_text () = *offtextref;
fun 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_roundbutton
=
{ id,
#
get_active,
get_state,
get_button_relief,
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,
set_button_relief_to
}
: App_To_Roundbutton
;
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_roundbutton);
end;
();
};
fun shutdown_fn () # Return to widget_imp an exception packaging up our state; this will be returned to guiboss_imp, saved in the
= # Paused_Gui tree, and passed to our startup_fn when/if gui is restarted. This exception will never be raised;
{ apply tell_watcher portwatchers #
where
fun tell_watcher portwatcher
=
portwatcher NULL;
end;
apply tell_watcher sitewatchers
where
fun tell_watcher sitewatcher
=
sitewatcher NULL;
end;
};
fun initialize_gadget_fn
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
site: g2d::Box, # Window rectangle in which to draw.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
pass_font: List(String) -> Replyqueue
-> (evt::Font -> Void) -> Void, # Nonblocking version of next, for use in imps.
get_font: List(String) -> evt::Font, # Accepts a list of font names which are tried in order.
make_rw_pixmap: g2d::Size -> g2p::Gadget_To_Rw_Pixmap,
#
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
=
{ note_site (id,site);
#
();
};
fun redraw_request_fn_wrapper
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
frame_number: Int, # 1,2,3,... Purely for convenience of widget-imp, guiboss-imp makes no use of this.
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Window rectangle in which to draw.
popup_nesting_depth: Int, # 0 for gadgets on basewindow, 1 for gadgets on popup on basewindow, 2 for gadgets on popup on popup, etc.
#
duration_in_seconds: Float, # If state has changed widget-imp should call redraw_gadget() before this time is up. Also useful for motionblur.
widget_to_guiboss: gt::Widget_To_Guiboss,
gadget_mode: gt::Gadget_Mode,
#
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void,
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
=
{ note_site (id,site);
#
palette = *theme.current_gadget_colors { gadget_is_on => *button_state,
gadget_mode,
popup_nesting_depth,
#
body_color,
body_color_when_on,
body_color_with_mousefocus,
body_color_when_on_with_mousefocus
};
text = if *button_state
#
case *ontextref
#
THE t => THE t; # Button is ON so use "ON" text.
NULL => *textref; # Button is ON but no "ON" text so use plain text (or none).
esac;
else
case *offtextref
#
THE t => THE t; # Button is OFF so use "OFF" text.
NULL => *textref; # Button is OFF but no "OFF" text so use plain text (or none).
esac;
fi;
redraw_fn_arg
=
REDRAW_FN_ARG
{ id,
doc,
frame_number,
frame_indent_hint,
site,
popup_nesting_depth,
duration_in_seconds,
widget_to_guiboss,
gadget_mode,
theme,
do,
to,
palette,
#
default_redraw_fn,
#
button_state => *button_state,
button_type,
button_relief => *reliefref,
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,
button_relief => reliefref,
#
initial_state,
note_state,
needs_redraw_gadget_request
};
mouse_click_fn mouse_click_fn_arg;
};
fun mouse_drag_fn_wrapper # This a callback we hand to
src/lib/x-kit/widget/xkit/theme/widget/default/look/widget-imp.pkg (
{ id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
start_point: g2d::Point,
last_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
phase: gt::Drag_Phase,
button: evt::Mousebutton,
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
mousebuttons_state: evt::Mousebuttons_State, # State of mouse buttons as a bool record.
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
)
=
{ note_site (id,site);
#
mouse_drag_fn_arg
=
MOUSE_DRAG_FN_ARG
{
id,
doc,
event_point,
start_point,
last_point,
widget_layout_hint,
frame_indent_hint,
site,
phase,
button,
modifier_keys_state,
mousebuttons_state,
widget_to_guiboss,
theme,
do,
to,
#
default_mouse_drag_fn => \\ _ = (), # Default drag behavior for buttons is to do absolutely nothing.
#
button_state => *button_state, # We don't pass the refcell here because we want client code to make state changes via note_state(), which will properly notify all state-watchers.
button_type,
button_relief => reliefref,
#
initial_state,
note_state,
needs_redraw_gadget_request
};
case mouse_drag_fn
#
THE mouse_drag_fn => mouse_drag_fn mouse_drag_fn_arg;
NULL => (); # We do not expect this case to happen: If mouse_drag_fn is NULL mouse_drag_fn_wrapper should not have been registered with widget-imp so we should never get called.
esac;
};
fun mouse_transit_fn_wrapper
#
( arg as
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
event_point: g2d::Point,
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
transit: gt::Gadget_Transit, # Mouse is entering (CAME) or leaving (LEFT) widget, or moving (MOVE) across it.
modifier_keys_state: evt::Modifier_Keys_State, # State of the modifier keys (shift, ctrl...).
widget_to_guiboss: gt::Widget_To_Guiboss,
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
)
=
{ note_site (id,site);
#
mouse_transit_fn_arg
=
MOUSE_TRANSIT_FN_ARG
{
id,
doc,
event_point,
widget_layout_hint,
frame_indent_hint,
site,
transit,
modifier_keys_state,
widget_to_guiboss,
theme,
do,
to,
#
default_mouse_transit_fn,
#
button_state => *button_state, # We don't pass the refcell here because we want client code to make state changes via note_state(), which will properly notify all state-watchers.
button_type,
button_relief => reliefref,
#
initial_state,
note_state,
needs_redraw_gadget_request
};
mouse_transit_fn mouse_transit_fn_arg;
();
};
fun key_event_fn_wrapper
{
id: Id, # Unique Id for widget.
doc: String, # Human-readable description of this widget, for debug and inspection.
keystroke: gt::Keystroke_Info, # Keystring etc for event.
widget_layout_hint: gt::Widget_Layout_Hint,
frame_indent_hint: gt::Frame_Indent_Hint,
site: g2d::Box, # Widget's assigned area in window coordinates.
widget_to_guiboss: gt::Widget_To_Guiboss,
guiboss_to_widget: gt::Guiboss_To_Widget, # Used by textpane.pkg keystroke-macro stuff to synthesize fake keystroke events to widget.
theme: wt::Widget_Theme,
do: (Void -> Void) -> Void, # Used by widget subthreads to execute code in main widget microthread.
to: Replyqueue # Used to call 'pass_*' methods in other imps.
}
=
{ note_site (id,site);
#
key_event_fn_arg
=
KEY_EVENT_FN_ARG
{
id,
doc,
keystroke,
widget_layout_hint,
frame_indent_hint,
site,
widget_to_guiboss,
guiboss_to_widget,
theme,
do,
to,
#
default_key_event_fn => \\ _ = (), # Default key event behavior for buttons is to do absolutely nothing.
#
button_state => *button_state, # We don't pass the refcell here because we want client code to make state changes via note_state(), which will properly notify all state-watchers.
button_type,
button_relief => reliefref,
#
initial_state,
note_state,
needs_redraw_gadget_request
};
case key_event_fn
#
THE key_event_fn => key_event_fn key_event_fn_arg;
NULL => (); # We do not expect this case to happen: If key_event_fn is NULL key_event_fn_wrapper should not have been registered with widget-imp so we should never get called.
esac;
();
};
#
# End of widget hook fn section
###############################
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;