## widget-theme-imp.pkg
#
# For the big picture see the imp dataflow diagrams in
#
#
src/lib/x-kit/xclient/src/window/xclient-ximps.pkg#
# Compiled by:
#
src/lib/x-kit/widget/xkit-widget.sublibstipulate
include package threadkit; # threadkit is from
src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg #
# package ap = client_to_atom; # client_to_atom is from
src/lib/x-kit/xclient/src/iccc/client-to-atom.pkg# package au = authentication; # authentication is from
src/lib/x-kit/xclient/src/stuff/authentication.pkg# package cpm = cs_pixmap; # cs_pixmap is from
src/lib/x-kit/xclient/src/window/cs-pixmap.pkg# package cpt = cs_pixmat; # cs_pixmat is from
src/lib/x-kit/xclient/src/window/cs-pixmat.pkg# package dy = display; # display is from
src/lib/x-kit/xclient/src/wire/display.pkg# package xet = xevent_types; # xevent_types is from
src/lib/x-kit/xclient/src/wire/xevent-types.pkg# package w2x = windowsystem_to_xserver; # windowsystem_to_xserver is from
src/lib/x-kit/xclient/src/window/windowsystem-to-xserver.pkg# package fil = file__premicrothread; # file__premicrothread is from
src/lib/std/src/posix/file--premicrothread.pkg# package fti = font_index; # font_index is from
src/lib/x-kit/xclient/src/window/font-index.pkg# package r2k = xevent_router_to_keymap; # xevent_router_to_keymap is from
src/lib/x-kit/xclient/src/window/xevent-router-to-keymap.pkg# package mtx = rw_matrix; # rw_matrix is from
src/lib/std/src/rw-matrix.pkg# package rop = ro_pixmap; # ro_pixmap is from
src/lib/x-kit/xclient/src/window/ro-pixmap.pkg# package rw = root_window; # root_window is from
src/lib/x-kit/widget/lib/root-window.pkg# package rwv = rw_vector; # rw_vector is from
src/lib/std/src/rw-vector.pkg# package sep = client_to_selection; # client_to_selection is from
src/lib/x-kit/xclient/src/window/client-to-selection.pkg# package shp = shade; # shade is from
src/lib/x-kit/widget/lib/shade.pkg# package sj = socket_junk; # socket_junk is from
src/lib/internet/socket-junk.pkg# package x2s = xclient_to_sequencer; # xclient_to_sequencer is from
src/lib/x-kit/xclient/src/wire/xclient-to-sequencer.pkg# package tr = logger; # logger is from
src/lib/src/lib/thread-kit/src/lib/logger.pkg# package tsr = thread_scheduler_is_running; # thread_scheduler_is_running is from
src/lib/src/lib/thread-kit/src/core-thread-kit/thread-scheduler-is-running.pkg# package u1 = one_byte_unt; # one_byte_unt is from
src/lib/std/one-byte-unt.pkg# package v1u = vector_of_one_byte_unts; # vector_of_one_byte_unts is from
src/lib/std/src/vector-of-one-byte-unts.pkg# package v2w = value_to_wire; # value_to_wire is from
src/lib/x-kit/xclient/src/wire/value-to-wire.pkg# package wg = widget; # widget is from
src/lib/x-kit/widget/old/basic/widget.pkg# package wi = window; # window is from
src/lib/x-kit/xclient/src/window/window.pkg# package wme = window_map_event_sink; # window_map_event_sink is from
src/lib/x-kit/xclient/src/window/window-map-event-sink.pkg# package wpp = client_to_window_watcher; # client_to_window_watcher is from
src/lib/x-kit/xclient/src/window/client-to-window-watcher.pkg# package wy = widget_style; # widget_style is from
src/lib/x-kit/widget/lib/widget-style.pkg# package e2s = xevent_to_string; # xevent_to_string is from
src/lib/x-kit/xclient/src/to-string/xevent-to-string.pkg# package xc = xclient; # xclient is from
src/lib/x-kit/xclient/xclient.pkg# package g2d = geometry2d; # geometry2d is from
src/lib/std/2d/geometry2d.pkg# package xj = xsession_junk; # xsession_junk is from
src/lib/x-kit/xclient/src/window/xsession-junk.pkg# package xt = xtypes; # xtypes is from
src/lib/x-kit/xclient/src/wire/xtypes.pkg# package xtr = xlogger; # xlogger is from
src/lib/x-kit/xclient/src/stuff/xlogger.pkg #
package gtg = guiboss_to_guishim; # guiboss_to_guishim is from
src/lib/x-kit/widget/theme/guiboss-to-guishim.pkg #
package psi = widgetspace_imp; # widgetspace_imp is from
src/lib/x-kit/widget/space/widget/widgetspace-imp.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 c64 = rgb; # Colors with Float64 red-green-blue values. # rgb is from
src/lib/x-kit/xclient/src/color/rgb.pkg package c8 = rgb8; # Colors with Unt8 red-green-blue values. # rgb8 is from
src/lib/x-kit/xclient/src/color/rgb8.pkg #
package g2d = geometry2d; # geometry2d is from
src/lib/std/2d/geometry2d.pkg package f8b = eight_byte_float; # eight_byte_float is from
src/lib/std/eight-byte-float.pkg package sfp = sfprintf; # sfprintf is from
src/lib/src/sfprintf.pkg package evt = gui_event_types; # gui_event_types is from
src/lib/x-kit/widget/gui/gui-event-types.pkg package gtg = guiboss_to_guishim; # guiboss_to_guishim is from
src/lib/x-kit/widget/theme/guiboss-to-guishim.pkg nb = log::note_on_stderr; # log is from
src/lib/std/src/log.pkg tracefile = "widget-unit-test.trace.log";
herein
package widget_theme_imp
: Widget_Theme_Imp # Widget_Theme_Imp is from
src/lib/x-kit/widget/theme/widget/widget-theme-imp.api {
#
include package widget_theme; # widget_theme is from
src/lib/x-kit/widget/theme/widget/widget-theme.pkg #
Theme_State = Ref( Void ); # Holds all nonephemeral mutable state maintained by skin.
Imports = { # Ports we use, provided by other imps.
int_sink: Int -> Void,
guiboss_to_guishim: gtg::Guiboss_To_Guishim
};
Me_Slot = Mailslot( { imports: Imports,
me: Theme_State,
run_gun': Run_Gun,
end_gun': End_Gun
}
);
Exports = { # Ports we provide for use by other imps.
theme: Widget_Theme
};
Option = MICROTHREAD_NAME String; #
Widget_Theme_Egg = Void -> (Exports, (Imports, Run_Gun, End_Gun) -> Void);
Runstate = { # These values will be statically globally visible throughout the code body for the imp.
me: Theme_State, #
imports: Imports, # Imps to which we send requests.
to: Replyqueue, # The name makes foo::pass_something(imp) to {. ... } syntax read well.
end_gun': End_Gun # We shut down the microthread when this fires.
};
Theme_Q = Mailqueue( Runstate -> Void );
fun get__guiboss_to_hostwindow (theme: Widget_Theme)
=
case *theme.guiboss_to_hostwindow
#
THE g => g;
#
NULL => { msg = "font functions called before guiboss_to_hostwindow available!";
log::fatal msg;
raise exception DIE msg;
};
esac;
fun run ( theme_q: Theme_Q, #
#
runstate as
{ # These values will be statically globally visible throughout the code body for the imp.
me: Theme_State, #
imports: Imports, # Imps to which we send requests.
to: Replyqueue, # The name makes foo::pass_something(imp) to {. ... } syntax read well.
end_gun': End_Gun # We shut down the microthread when this fires.
}
)
=
loop ()
where
fun loop () # Outer loop for the imp.
=
{ do_one_mailop' to [
#
(end_gun' ==> shut_down_theme_imp'),
(take_from_mailqueue' theme_q ==> do_theme_plea)
];
loop ();
}
where
fun do_theme_plea thunk
=
thunk runstate;
fun shut_down_theme_imp' ()
=
{
thread_exit { success => TRUE }; # Will not return.
};
end;
end;
fun startup (reply_oneshot: Oneshot_Maildrop( (Me_Slot, Exports) )) () # Root fn of imp microthread. Note currying.
=
{ me_slot = make_mailslot () : Me_Slot;
#
# Functions like text_color refer to widget_theme
# but widget_theme also refers to them. We break
# the cycle via a four-step dance:
#
# 1) Define dummy fns.
# 2) Define widget_theme in terms of them.
# 3) Define real fns in terms of widget_theme.
# 4) Plug the real fns into widget_theme, replacing the dummy fns.
#
# Here are the dummies:
#
text_color = REF (\\ _ = c64::white);
textfield_color = REF (\\ _ = c64::white);
#
surround_color = REF (\\ _ = c64::white);
#
body_color = REF (\\ _ = c64::white);
body_color_with_mousefocus = REF (\\ _ = c64::white);
body_color_when_on = REF (\\ _ = c64::white);
body_color_when_on_with_mousefocus = REF (\\ _ = c64::white);
#
sunny_bevel_color = REF (\\ _ = c64::white);
shady_bevel_color = REF (\\ _ = c64::white);
#
current_gadget_colors = REF (\\ _ = { text_color => c64::white,
surround_color => c64::white,
body_color => c64::white,
#
upperleft_bevel_color => c64::white,
lowerright_bevel_color => c64::white
}
);
pictureframe = REF (\\ _ = \\ _ = []: gd::Gui_Displaylist);
filled_pictureframe = REF (\\ _ = \\ _ = []: gd::Gui_Displaylist);
rounded_pictureframe = REF (\\ _ = \\ _ = []: gd::Gui_Displaylist);
polygon3d = REF (\\ _ = \\ _ = []: gd::Gui_Displaylist);
guiboss_to_hostwindow = REF (NULL: Null_Or(gtg::Guiboss_To_Hostwindow));
#
stipulate
dummy_font = { id => id_zero,
font_height => { ascent => 0, descent => 0 },
string_length_in_pixels => (\\ (s: String) = 0)
};
herein
get_roman_fontname = REF (\\ (pointsize: Int) = ""); # Dummy fn, will be replaced momentarily below.
get_italic_fontname = REF (\\ (pointsize: Int) = ""); # Dummy fn, will be replaced momentarily below.
get_bold_fontname = REF (\\ (pointsize: Int) = ""); # Dummy fn, will be replaced momentarily below.
#
get_roman_font = REF (\\ (pointsize: Int) = dummy_font); # Dummy fn, will be replaced momentarily below.
get_italic_font = REF (\\ (pointsize: Int) = dummy_font); # Dummy fn, will be replaced momentarily below.
get_bold_font = REF (\\ (pointsize: Int) = dummy_font); # Dummy fn, will be replaced momentarily below.
end;
# A few widget_theme fns don't refer to widget_theme,
# so we can define the real versions of them before
# defining widget_theme:
fun slight_blackening (color: c64::Rgb) = c64::rgb_mix01 (0.9, c64::black, color); # PUBLIC.
fun medium_blackening (color: c64::Rgb) = c64::rgb_mix01 (0.5, c64::black, color); # PUBLIC.
fun lavish_blackening (color: c64::Rgb) = c64::rgb_mix01 (0.3, c64::black, color); # PUBLIC.
fun slight_graying (color: c64::Rgb) = c64::rgb_mix01 (0.9, c64::gray, color); # PUBLIC.
fun medium_graying (color: c64::Rgb) = c64::rgb_mix01 (0.5, c64::gray, color); # PUBLIC.
fun lavish_graying (color: c64::Rgb) = c64::rgb_mix01 (0.3, c64::gray, color); # PUBLIC.
fun slight_whitening (color: c64::Rgb) = c64::rgb_mix01 (0.9, c64::white, color); # PUBLIC.
fun medium_whitening (color: c64::Rgb) = c64::rgb_mix01 (0.5, c64::white, color); # PUBLIC.
fun lavish_whitening (color: c64::Rgb) = c64::rgb_mix01 (0.3, c64::white, color); # PUBLIC.
fun color_by_depth # This fn is an alternative to drop shadows, which are hard to do cleanly with just the original X drawops.
( # The idea is that the eye interprets dimmer and bluer as more distant, so by making each layer of popup
color: c64::Rgb, # successively brighter and warmer in color, we can make them look nearer than their parent guipane.
popup_nesting_depth: Int # The effect is deliberately toned down enough to be nearly subliminal.
)
=
warm_and_brighten_n_times (color, popup_nesting_depth)
where
fun warm_and_brighten ({ red, green, blue })
=
{ red = red * 0.9 + 0.1; # Move red value 10% of way to 100% red.
green = green * 0.95 + 0.05; # Move green value 5% of way to 100% green.
blue = blue * 1.0 + 0.0; # Move blue value 0% of way to 50% blue.
{ red, green, blue };
};
fun warm_and_brighten_n_times (color, 0)
=>
color;
warm_and_brighten_n_times (color, d)
=>
warm_and_brighten_n_times (warm_and_brighten(color), d - 1);
end;
end;
theme = { # We'll rename this to widget_theme later, for now a shorter name is nice.
do_something,
#
base_color => REF { red => 0.78, # Nice light gray background for GUI, initially a little bluish, getting warmer in color as we ascend the popup hierarchy.
green => 0.80,
blue => 0.82
},
text_color, # Dummy fn.
textfield_color, # Dummy fn.
surround_color, # Dummy fn.
#
body_color, # Dummy fn.
body_color_with_mousefocus, # Dummy fn.
body_color_when_on, # Dummy fn.
body_color_when_on_with_mousefocus, # Dummy fn.
#
sunny_bevel_color, # Dummy fn.
shady_bevel_color, # Dummy fn.
current_gadget_colors, # Dummy fn.
pictureframe, # Dummy fn.
filled_pictureframe, # Dummy fn.
rounded_pictureframe, # Dummy fn.
polygon3d, # Dummy fn.
#
slight_blackening => REF slight_blackening, # Real fn.
medium_blackening => REF medium_blackening, # Real fn.
lavish_blackening => REF lavish_blackening, # Real fn.
#
slight_graying => REF slight_graying, # Real fn.
medium_graying => REF medium_graying, # Real fn.
lavish_graying => REF lavish_graying, # Real fn.
#
slight_whitening => REF slight_whitening, # Real fn.
medium_whitening => REF medium_whitening, # Real fn.
lavish_whitening => REF lavish_whitening, # Real fn.
#
color_by_depth => REF color_by_depth, # Real fn.
default_font_size => REF 13,
# I found these font triples in use in Reppy+Gansner's 1990-era
# CML+eXene codebase. These days there may be better choices.
# Feel free to research and improve: -- 2014-12-29 CrT
# roman_font_spex => REF "-adobe-times-medium-r-normal--*-%d-*-*-p-*-iso8859-1",
# italic_font_spex => REF "-adobe-times-medium-i-normal--*-%d-*-*-p-*-iso8859-1",
# bold_font_spex => REF "-adobe-times-bold-r-normal--*-%d-*-*-p-*-iso8859-1",
# roman_font_spex => REF "-*-courier-medium-r-*-*-%d-*-*-*-*-*-*",
# italic_font_spex => REF "-*-courier-medium-o-*-*-%d-*-*-*-*-*-*",
# bold_font_spex => REF "-*-courier-bold-r-*-*-%d-*-*-*-*-*-*",
# roman_font_spex => REF "-adobe-helvetica-medium-r-normal--*-*-%d-*-*-*-*-*",
# italic_font_spex => REF "-adobe-helvetica-medium-o-normal--*-*-%d-*-*-*-*-*",
# bold_font_spex => REF "-adobe-helvetica-bold-r-normal--*-*-%d-*-*-*-*-*",
# To work with modern scalable fonts (recognisable by
# zeros in the seventh, eight and twelfth fields),
#
# http://menehune.opt.wfu.edu/Kokua/Irix_6.5.21_doc_cd/usr/share/Insight/library/SGI_bookshelves/SGI_Developer/books/XLib_PG/sgi_html/apa.html
#
# recommends using strings like
#
# -*-helvetica-medium-r-*-*-*-120-75-75-*-*-iso8859-1 # Normal font
# -*-helvetica-bold-r-*-*-*-120-75-75-*-*-iso8859-1 # Bold font
# -*-helvetica-medium-i-*-*-*-120-75-75-*-*-iso8859-1 # Italic font ("o" for "oblique" is also italic)
#
# where '120'== pointsize*10 and the two 75s are respectively
# horizontal and vertical screen resolution in dpi.
#
# Useful variants:
#
# -*-helvetica-medium-r-*-*-*-120-75-75-m-*-iso8859-1 # Monospace fonts only.
# -*-helvetica-medium-r-*-*-*-120-75-75-p-*-iso8859-1 # Proportional fonts only.
# -*-helvetica-medium-r-*-*-*-120-75-75-c-*-iso8859-1 # Constant-width "cell" fonts only -- "typewriter spacing".
#
# -*-helvetica-medium-r-*-*-*-120-75-75-c-*-iso10646-1 # Unicode font.
#
# Currently focus is on scalable typewriter utf-8 fonts:
#
# -misc-fixed-medium-r-normal--0-0-75-75-c-0-iso10646-1
# -misc-fixed-medium-o-normal--0-0-75-75-c-0-iso10646-1
# -misc-fixed-bold-r-normal--0-0-75-75-c-0-iso10646-1
#
# -misc-fixed-medium-r-normal-ja-0-0-75-75-c-0-iso10646-1
#
# -misc-fixed-bold-r-semicondensed--0-0-75-75-c-0-iso10646-1
# -misc-fixed-medium-o-semicondensed--0-0-75-75-c-0-iso10646-1
#
# -misc-fixed-medium-r-normal--0-0-100-100-c-0-iso10646-1
# -misc-fixed-bold-r-normal--0-0-100-100-c-0-iso10646-1
#
# -misc-fixed-medium-r-normal-ja-0-0-100-100-c-0-iso10646-1
# -misc-fixed-medium-r-normal-ko-0-0-100-100-c-0-iso10646-1
#
roman_font_spex => REF "-misc-fixed-medium-r-normal-*-*-%d-75-75-c-*-iso10646-1",
italic_font_spex => REF "-misc-fixed-medium-o-normal-*-*-%d-75-75-c-*-iso10646-1",
bold_font_spex => REF "-misc-fixed-bold-r-normal-*-*-%d-75-75-c-*-iso10646-1",
# I've also experimented with these triples:
# roman_font_spex => REF "lucidasans-%d",
# italic_font_spex => REF "lucidasans-italic-%d",
# bold_font_spex => REF "lucidasans-bold-%d",
# roman_font_spex => REF "lucidasanstypewriter-%d",
# italic_font_spex => REF "lucidasanstypewriter-italic-%d",
# bold_font_spex => REF "lucidasanstypewriter-bold-%d",
#
get_roman_fontname,
get_italic_fontname,
get_bold_fontname,
#
get_roman_font,
get_italic_font,
get_bold_font,
guiboss_to_hostwindow
# dummy_make_button_displaylist
};
#######################################################################
# Time to define the real versions of the above dummy fns:
#
fun text_color (d: Int) # My current thought here is that text usually
= # looks best in either black or white, so if
if (c64::rgb_is_light ((*theme.body_color)(d))) c64::black; # the widget body color is light use black
else c64::white; # otherwise use white.
fi; #
fun textfield_color (d: Int) # All through here 'd'=='popup_nesting_depth'.
=
if (c64::rgb_is_light (*theme.text_color d)) { red => 0.1, green => 0.1, blue => 0.1 };
else { red => 0.9, green => 0.9, blue => 0.9 };
fi;
fun surround_color (d: Int) = *theme.color_by_depth (*theme.base_color, d); # NB: We make it so that app programmer can change just base_color
# (or just surround_color) and have the other colors change reasonably.
fun body_color (d: Int) = *theme.surround_color d; #
fun body_color_when_on (d: Int) = rgb::rgb_mix01 (0.7, *theme.surround_color d, rgb::white);
#
fun body_color_with_mousefocus (d: Int) = rgb::rgb_scale (1.05, *theme.body_color d); # 5% brighter than body_color.
fun body_color_when_on_with_mousefocus (d: Int) = rgb::rgb_scale (1.05, *theme.body_color_when_on d); # 5% brighter than body_color_when_on.
fun sunny_bevel_color (d: Int) = *theme.lavish_whitening (*theme.surround_color d);
fun shady_bevel_color (d: Int) = *theme.lavish_blackening (*theme.surround_color d);
fun current_gadget_colors # Compute appropriate gadget colors based on mode and on/off status.
{ # This avoids duplicating this logic in every button etc and provides
gadget_is_on: Bool, # a central place for customizing these decisions.
#
gadget_mode => gadget as
{
is_active: Bool, # An inactive gadget is passed no user input. Inactive widgets are typically drawn "grayed-out".
has_mouse_focus: Bool, # A widget which has the mouse cursor on it may want to draw itself brigher or such.
has_keyboard_focus: Bool # A widget which has the keyboard focus will often draw a black outline around its text-entry rectangle.
} : gt::Gadget_Mode,
popup_nesting_depth: Int, # 0 for gadgets on basewindow, 1 for gadgets on popup on basewindow, 2 for gadgets on popup on popup, etc.
#
body_color: Null_Or( c64::Rgb ), # These four values allow per-widget overrides of the theme colors via
body_color_when_on: Null_Or( c64::Rgb ), # widget::BODY_COLOR (etc) Option values. They will typically be NULL:
body_color_with_mousefocus: Null_Or( c64::Rgb ), # most widgets will just let body colors default to the theme settings.
body_color_when_on_with_mousefocus: Null_Or( c64::Rgb ) #
}
=
{
d = popup_nesting_depth;
# Get our base colors from the theme:
#
surround_color = *theme.surround_color d;
text_color = *theme.text_color d;
sunny_bevel_color = *theme.sunny_bevel_color d;
shady_bevel_color = *theme.shady_bevel_color d;
text_color = if gadget.is_active text_color; # NB: 'active' is designed in but not tested or even(?) fully implemented.
else *theme.lavish_graying text_color; # Gray out inactive widget.
fi;
# Typically the body of a (for example) button widget
# will have different colors when it is ON vs OFF,
# plus it will brighten 5% when the mouse is over it,
# just to let the end user know that it is live.
#
# Also, typically the body colors come from the theme,
# but we allow the app programmer to override them,
# so that (e.g.) a stop/go switch can be red vs green.
#
# If widget is not 'active' (mouse-responsive) we signal
# that by graying its text (above) and here by setting
# it not to light up on mouse-over.
#
# Here we implement the above by:
#
# o Making body_color key on 'gadget_is_on' + 'gadget.has_mouse_focus'.
#
# o Giving per-widget colors precedence over theme colors.
#
# o Making the "_with_mousefocus" colors default to 5% brighter
# than the corresponding non-"_with_mousefocus" colors
# if the app programmer specified ON and/or OFF colors
# but not _with_mousefocus variants of them.
#
body_color = if (gadget.is_active)
#
case (gadget_is_on, gadget.has_mouse_focus)
#
(FALSE, TRUE ) => case body_color_with_mousefocus THE c => c; NULL => case body_color THE c => rgb::rgb_scale (1.05, c); NULL => *theme.body_color_with_mousefocus d; esac; esac;
(TRUE , TRUE ) => case body_color_when_on_with_mousefocus THE c => c; NULL => case body_color_when_on THE c => rgb::rgb_scale (1.05, c); NULL => *theme.body_color_when_on_with_mousefocus d; esac; esac;
#
(FALSE, FALSE) => case body_color THE c => c; NULL => *theme.body_color d; esac;
(TRUE , FALSE) => case body_color_when_on THE c => c; NULL => *theme.body_color_when_on d; esac;
esac;
else
case gadget_is_on
#
FALSE => case body_color THE c => c; NULL => *theme.body_color d; esac;
TRUE => case body_color_when_on THE c => c; NULL => *theme.body_color_when_on d; esac;
esac;
fi;
my ( upperleft_bevel_color,
lowerright_bevel_color
)
=
if gadget_is_on (shady_bevel_color, sunny_bevel_color); # Make button look pressed.
else (sunny_bevel_color, shady_bevel_color); # Make button look popped.
fi;
{ surround_color,
body_color,
text_color,
#
upperleft_bevel_color,
lowerright_bevel_color
} : Gadget_Palette;
};
stipulate
fun make_pictureframe # Used by pictureframe for FLAT, RAISED and SUNKEN.
(
upperleft_bevel_color: c64::Rgb,
lowerright_bevel_color: c64::Rgb,
thick: Int,
box as { col, row, wide, high }: g2d::Box
)
=
{
thick2 = thick*2;
if (wide < thick2
or high < thick2)
#
[];
else
point_1 = { col, row => row+high }; #
point_2 = { col, row }; # 2 3
point_3 = { col => col+wide, row }; # 5 4
point_4 = { col => col+wide-thick, row => row+thick }; #
point_5 = { col => col+thick, row => row+thick }; #
point_6 = { col => col+thick, row => row+high-thick }; # 6 7
point_7 = { col => col+wide-thick, row => row+high-thick }; # 1 8
point_8 = { col => col+wide, row => row+high };
upper_left_points
=
[ point_1, # Clockwise order. (Is this the convention?)
point_2,
point_3,
point_4,
point_5,
point_6,
point_1
];
lower_right_points
=
[ point_1, # Clockwise order again.
point_6,
point_7,
point_4,
point_3,
point_8,
point_1
];
[ gd::COLOR ( upperleft_bevel_color, [ gd::FILLED_POLYGON upper_left_points ] ),
gd::COLOR ( lowerright_bevel_color, [ gd::FILLED_POLYGON lower_right_points ] )
];
fi;
};
fun make_pictureframe' # Used by pictureframe for GROOVE and RIDGE.
(
upperleft_bevel_color: c64::Rgb,
lowerright_bevel_color: c64::Rgb,
thick: Int,
outerbox as { col, row, wide, high }: g2d::Box
)
=
{ inner_thick = thick / 2;
outer_thick = thick - inner_thick; # NB: If thick is odd, we'll have outer_thick == inner_thick + 1.
innerbox
=
{ col => col + outer_thick,
row => row + outer_thick,
#
wide => wide - outer_thick * 2,
high => high - outer_thick * 2
};
outer = make_pictureframe ( upperleft_bevel_color, lowerright_bevel_color, outer_thick, outerbox );
inner = make_pictureframe (lowerright_bevel_color, upperleft_bevel_color, inner_thick, innerbox );
outer @ inner;
};
fun make_rounded_pictureframe # Used by rounded_pictureframe for FLAT, RAISED and SUNKEN.
#
( upperleft_bevel_color: c64::Rgb,
lowerright_bevel_color: c64::Rgb,
thick: Int,
corner_high: Int,
corner_wide: Int,
{ col, row, wide, high }: g2d::Box
)
=
{
halfthick = thick / 2;
col = col + halfthick;
row = row + halfthick;
wide' = wide - 2*halfthick;
high' = high - 2*halfthick;
stipulate
#
cw2 = corner_wide*2;
ch2 = corner_high*2;
herein
my (ew, ew2) = if (cw2 > wide') (0, 0); else (corner_wide, cw2); fi;
my (eh, eh2) = if (ch2 > high') (0, 0); else (corner_high, ch2); fi;
end;
[ gd::LINE_THICKNESS
(
thick,
[
# XXX BUGGO FIXME This stuff is all wrong at the moment:
gd::COLOR ( upperleft_bevel_color, # ARC # ORIGIN # SHAPE
[ gd::ARCS [ # =============== # ============== # =========
{ col=> col, row=> row, wide=> ew2, high=> eh2, start_angle=> 180.0, fill_angle=> -90.0 }, # Topleft # topleft # corner
{ col=> col+ew, row=> row, wide=> wide' - ew2, high=> 0, start_angle=> 180.0, fill_angle=> -180.0 }, # Tophalf # topleft + ew # horizontal
{ col=> col, row=> row+eh, wide=> 0, high=> high' - eh2, start_angle=> 270.0, fill_angle=> -180.0 }, # All but topleft # topleft + eh # vertical
{ col=> col+wide' - ew2, row=> row, wide=> ew2, high=> eh2, start_angle=> 45.0, fill_angle=> 45.0 }, # Full circle # topright - ew2 # corner
{ col=> col, row=> row+high' - eh2, wide=> ew2, high=> eh2, start_angle=> 225.0, fill_angle=> -45.0 } # Top quarter # botleft - eh2 # corner
] ]
),
gd::COLOR ( lowerright_bevel_color, # ARC # ORIGIN # SHAPE
[ gd::ARCS [ # =============== # ============== # =========
{ col=> col+wide' - ew2, row=> row, wide=> ew2, high=> eh2, start_angle=> 45.0, fill_angle=> -45.0 }, # Left 3/4 # topright - ew2 # corner
{ col=> col+wide', row=> row+eh, wide=> 0, high=> high' - eh2, start_angle=> 90.0, fill_angle=> -180.0 }, # Botleft 1/4 # topright + eh # vertical
{ col=> col+wide' - ew2, row=> row+high' - eh2, wide=> ew2, high=> eh2, start_angle=> 0.0, fill_angle=> -90.0 }, # All but topright # botright - 22 # corner
{ col=> col+ew, row=> row+high', wide=> wide' - ew2, high=> 0, start_angle=> 0.0, fill_angle=> -180.0 }, # Bothalf # botleft + ew # horizontal
{ col=> col, row=> row+high' - eh2, wide=> ew2, high=> eh2, start_angle=> 270.0, fill_angle=> -45.0 } # Noon->1:30 # botleft - eh2 # corner
] ]
)
]
)
];
};
fun make_rounded_pictureframe' # Used by rounded_pictureframe for GROOVE and RIDGE.
#
( upperleft_bevel_color: c64::Rgb,
lowerright_bevel_color: c64::Rgb,
thick: Int,
corner_high: Int,
corner_wide: Int,
outerbox as { col, row, wide, high }: g2d::Box
)
=
{ inner_thick = thick / 2;
outer_thick = thick - inner_thick;
innerbox
=
{ col => col + outer_thick,
row => row + outer_thick,
#
wide => wide - outer_thick * 2,
high => high - outer_thick * 2
};
outer = make_rounded_pictureframe ( upperleft_bevel_color, lowerright_bevel_color, outer_thick, corner_wide, corner_high, outerbox);
inner = make_rounded_pictureframe (lowerright_bevel_color, upperleft_bevel_color, inner_thick, corner_wide, corner_high, innerbox);
outer @ inner;
};
stipulate
# The table below is used for a quick approximation in
# computing a new point parallel to a given line
# An index into the table is 128 times the slope of the
# original line (the slope must always be between 0.0
# and 1.0). The value of the table entry is 128 times
# the amount to displace the new line in row for each unit
# of perpendicular distance. In other words, the table
# maps from the tangent of an angle to the inverse of
# its cosine. If the slope of the original line is greater
# than 1, then the displacement is done in col rather than in row.
#
shift_table
=
{ fun compute i
=
{ tangent = (float i) / 128.0;
#
f8b::truncate ((128.0 / math::cos (math::atan tangent)) + 0.5);
};
v = vector::from_fn (129, compute);
\\ i = vector::get (v, i);
};
herein
# Given two points on a line, compute a point on a
# new line that is parallel to the given line and
# a given distance away from it.
#
fun shift_line (p1 as { col, row }, p2, distance)
=
{ fun (<<) (w, i) = unt::to_int (unt::(<<) (unt::from_int w, i));
fun (>>) (w, i) = unt::to_int (unt::(>>) (unt::from_int w, i));
infix my << >>;
(g2d::point::subtract (p2, p1))
->
{ col=>dx, row=>dy };
my (dy, dy_neg) = if (dy < 0) (-dy, TRUE); else (dy, FALSE); fi;
my (dx, dx_neg) = if (dx < 0) (-dx, TRUE); else (dx, FALSE); fi;
fun adjust (dy, dx)
=
if (distance > 0) ((( distance * shift_table((dy << 0u7) / dx)) + 64) >> 0u7);
else -(((-distance * shift_table((dy << 0u7) / dx)) + 64) >> 0u7); # Our >> op won't work with negative numbers, hence the double-negation trick.
fi;
if (dy <= dx )
#
dy = adjust (dy, dx);
{ col, row=> row + (if dx_neg dy; else -dy;fi) };
else
dx = adjust (dx, dy);
{ col=> col + (if dy_neg -dx; else dx;fi), row };
fi;
};
end;
fun last2pts [] => raise exception lib_base::IMPOSSIBLE "three_d::last2Pts";
last2pts [v1, v2] => (v1, v2);
last2pts (v ! vs) => last2pts vs;
end;
#####################################################################
# draw3DPoly draws a polygon of given thickness. The widening occurs
# on the left of the polygon as it is traversed. If the thickness
# is negative, the widening occurs on the right. Duplicate points
# are ignored. If there are less than two distinct points, nothing
# is drawn.
#
# The main loop below (loop2) is executed once for each vertex in
# the polgon. At the beginning of each iteration things get like this:
#
# poly1 /
# * /
#
| /
# b1 * poly0
#
| |
#
| |
#
| |
#
| |
#
| |
#
| | p1 p2
# b2 *--------------------*
#
|
#
|
# *----*--------------------*
# poly2 newb1 newb2
#
# For each interation, we:
# (a) Compute poly2 (the border corner corresponding to p1)
# As part of this process, compute a new b1 and b2 value
# for the next side (p1-p2) of the polygon.
# (b) Draw the polygon (poly0, poly1, poly2, p1)
#
# The above situation doesn't exist until two points have
# been processed. We start with the last two points in the list
# (in loop0) to get an initial b1 and b2. Then, in loop1, we
# use the first point to get a new b1 and b2, with which we
# can calculate an initial poly1 (poly0 is the last point in
# the list). At this point, we can start the main loop.
#
# If two consecutive segments of the polygon are parallel,
# then things get more complex. (See findIntersect).
# Consider the following diagram:
#
# poly1
# *----b1-----------b2------a
# \
# \
# *---------*----------* b
# poly0 p2 p1 /
# /
# --*--------*----c
# newB1 newB2
#
# Instead of using the intersection and p1 as the last two points
# in the polygon and as poly1 and poly0 in the next iteration, we
# use a and b, and b and c, respectively.
#
# Do the computation in three stages:
# 1. Compute a point "perp" such that the line p1-perp
# is perpendicular to p1-p2.
# 2. Compute the points a and c by intersecting the lines
# b1-b2 and newb1-newb2 with p1-perp.
# 3. Compute b by shifting p1-perp to the right and
# intersecting it with p1-p2.
#####################################################################
fun make_polygon3d (_,_,_, [ ]) => []; # Used by polygon3d for FLAT, RAISED and SUNKEN.
make_polygon3d (_,_,_, [_]) => [];
make_polygon3d
( upperleft_bevel_color: c64::Rgb,
lowerright_bevel_color: c64::Rgb,
thick: Int,
points as (i_p ! _)
)
=>
loop0 (p1, p2 ! points)
where
(last2pts points) -> (p1, p2);
fun calc_off_points (v1, v2) # Given (v1,v2) return (b1,b2) parallel to (v1,v2) but offset perpendicularly by 'thick' pixels. Thus, (v1,v2) and (b1,b2) form a rectangle.
=
{
b1 = shift_line (v1, v2, thick);
#
(b1, g2d::point::add (b1, g2d::point::subtract (v2, v1)));
};
fun find_intersect (p1, p2, newb1, newb2, b1, b2)
=
case (g2d::line::intersection ((newb1, newb2), (b1, b2)))
#
THE col => (col, p1, col);
#
NULL =>
(poly2, poly3, c)
where
(g2d::line::rotate_90_degrees_counterclockwise (p1, p2))
->
(_, perp);
#
poly2 = the (g2d::line::intersection ((p1, perp), (b1, b2)));
c = the (g2d::line::intersection ((p1, perp), (newb1, newb2)));
shift1 = shift_line (p1, perp, thick);
shift2 = g2d::point::add (shift1, g2d::point::subtract (perp, p1));
poly3 = the (g2d::line::intersection ((p1, p2), (shift1, shift2)));
end;
esac;
fun draw (p0, p1, p2, p3)
=
{
(g2d::point::subtract (p3, p0))
->
{ col => dx, # We color lines (polygons) "bottom" if pointing into the lower-right halfplane from the origin, else "top".
row => dy #
}; # "top" /
# /
ul = upperleft_bevel_color; # O
lr = lowerright_bevel_color; # / "bottom"
# /
color = if (dx > 0) if (dy <= dx) lr; else ul; fi; #
elif (dy < dx) lr; else ul; fi; # NB: We might reasonably use more colors here. In the original code Reppy+Gansner only
# had 256 colors (colortable size), so they had to economize, but we have 24-bit
gd::COLOR (color, [ gd::FILLED_POLYGON [p0, p1, p2, p3] ]); # color and consequently can use as many as we want/need: we could take inner-product
}; # of polygon normal with lighting vector and interpolate between our two given colors.
fun loop2 (p1,[], b1, b2, poly0, poly1, result) # Main loop. This loop is executed once for each vertex in the input polygon.
=>
if (p1 != i_p)
#
(calc_off_points (p1, i_p)) -> (newb1, newb2);
(find_intersect (p1, i_p, newb1, newb2, b1, b2)) -> (poly2, poly3, _);
result = (draw (poly0, poly1, poly2, poly3)) ! result; #
result;
else
result;
fi;
loop2 (p1, p2 ! points, b1, b2, poly0, poly1, result)
=>
if (p1 == p2)
#
loop2 (p1, points, b1, b2, poly0, poly1, result);
else
(calc_off_points (p1, p2)) -> (newb1, newb2);
(find_intersect (p1, p2, newb1, newb2, b1, b2)) -> (poly2, poly3, c);
result = (draw (poly0, poly1, poly2, poly3)) ! result; #
loop2 (p2, points, newb1, newb2, poly3, c, result);
fi;
end;
fun loop1 (p1,[], _, _) => []; # More initialization.
loop1 (p1, p2 ! points, b1, b2)
=>
if (p1 == p2)
#
loop1 (p1, points, b1, b2);
else
(calc_off_points (p1, p2)) -> (newb1, newb2);
(find_intersect (p1, p2, newb1, newb2, b1, b2)) -> (poly2, poly3, c);
loop2 (p2, points, newb1, newb2, poly3, c, []);
fi;
end;
fun loop0 (_,[]) => []; # Initialization: Find two distinct points, then start up loop1.
loop0 (p1, p2 ! points)
=>
if (p1 == p2)
#
loop0 (p2, points);
else
(calc_off_points (p1, p2)) -> (b1, b2);
reverse (loop1 (p2, points, b1, b2));
fi;
end;
end;
end; # fun make_polygon3d
fun make_polygon3d'
( upperleft_bevel_color: c64::Rgb,
lowerright_bevel_color: c64::Rgb,
thick: Int,
points
)
=
{ halfthick = thick / 2;
#
outer = make_polygon3d ( upperleft_bevel_color, lowerright_bevel_color, halfthick, points);
inner = make_polygon3d (lowerright_bevel_color, upperleft_bevel_color, -halfthick, points);
outer @ inner;
};
herein
fun pictureframe
( p as { upperleft_bevel_color, lowerright_bevel_color, ... }: Gadget_Palette)
( { box, thick, relief }: Pictureframe)
=
case relief
#
FLAT => make_pictureframe ( upperleft_bevel_color, upperleft_bevel_color, thick, box);
RAISED => make_pictureframe ( upperleft_bevel_color, lowerright_bevel_color, thick, box);
SUNKEN => make_pictureframe (lowerright_bevel_color, upperleft_bevel_color, thick, box);
RIDGE => make_pictureframe' ( upperleft_bevel_color, lowerright_bevel_color, thick, box);
GROOVE => make_pictureframe' (lowerright_bevel_color, upperleft_bevel_color, thick, box);
esac;
fun filled_pictureframe
(palette as { body_color, ... }: Gadget_Palette)
(frame as { box, thick, relief }: Pictureframe)
=
(pictureframe palette frame) # The "3D" pictureframe surround.
@
[ gd::COLOR (body_color, [ gd::BOXES [ g2d::box::make_nested_box (box, thick) ] ]) ]; # The interior fill.
fun rounded_pictureframe
( { upperleft_bevel_color, lowerright_bevel_color, ... }: Gadget_Palette)
( { box, thick, relief, corner_high, corner_wide }: Rounded_Pictureframe)
=
case relief
#
FLAT => make_rounded_pictureframe ( upperleft_bevel_color, upperleft_bevel_color, thick, corner_wide, corner_high, box);
RAISED => make_rounded_pictureframe ( upperleft_bevel_color, lowerright_bevel_color, thick, corner_wide, corner_high, box);
SUNKEN => make_rounded_pictureframe (lowerright_bevel_color, upperleft_bevel_color, thick, corner_wide, corner_high, box);
RIDGE => make_rounded_pictureframe' ( upperleft_bevel_color, lowerright_bevel_color, thick, corner_wide, corner_high, box);
GROOVE => make_rounded_pictureframe' (lowerright_bevel_color, upperleft_bevel_color, thick, corner_wide, corner_high, box);
esac;
fun polygon3d
( { upperleft_bevel_color, lowerright_bevel_color, ... }: Gadget_Palette)
( { points, thick, relief }: Polygon3d)
=
case relief
#
FLAT => make_polygon3d ( upperleft_bevel_color, upperleft_bevel_color, thick, points);
RAISED => make_polygon3d ( upperleft_bevel_color, lowerright_bevel_color, thick, points);
SUNKEN => make_polygon3d (lowerright_bevel_color, upperleft_bevel_color, thick, points);
RIDGE => make_polygon3d' ( upperleft_bevel_color, lowerright_bevel_color, thick, points);
GROOVE => make_polygon3d' (lowerright_bevel_color, upperleft_bevel_color, thick, points);
esac;
end;
stipulate
fun fontname (spec: String, pointsize: Int)
=
if (string::length_in_bytes spec < 10 or
string::extract(spec,0,THE 10) != "lucidasans"
)
sfp::sprintf' spec [ sfp::INT (pointsize * 10) ]; # The X font naming system works in tenths of a point, not in points, hence the "*10",
else sfp::sprintf' spec [ sfp::INT (pointsize ) ]; # except that lucidasans just HAD to be different from everything else.
fi;
herein
fun get_roman_fontname (pointsize: Int) = fontname (*theme.roman_font_spex, pointsize); # PUBLIC.
fun get_italic_fontname (pointsize: Int) = fontname (*theme.italic_font_spex, pointsize); # PUBLIC.
fun get_bold_fontname (pointsize: Int) = fontname (*theme.bold_font_spex, pointsize); # PUBLIC.
end;
fun get_roman_font (pointsize: Int) # PUBLIC.
=
{ fontname = *theme.get_roman_fontname pointsize;
#
g = get__guiboss_to_hostwindow theme;
g.get_font [ fontname ];
};
fun get_italic_font (pointsize: Int) # PUBLIC.
=
{ fontname = *theme.get_italic_fontname pointsize;
#
g = get__guiboss_to_hostwindow theme;
g.get_font [ fontname ];
};
fun get_bold_font (pointsize: Int) # PUBLIC.
=
{ fontname = *theme.get_bold_fontname pointsize;
#
g = get__guiboss_to_hostwindow theme;
g.get_font [ fontname ];
};
# Finally we replace the dummy theme fns with the real ones:
#
theme.text_color := text_color;
theme.textfield_color := textfield_color;
#
theme.surround_color := surround_color;
#
theme.body_color := body_color;
theme.body_color_when_on := body_color_when_on;
theme.body_color_with_mousefocus := body_color_with_mousefocus;
theme.body_color_when_on_with_mousefocus := body_color_when_on_with_mousefocus;
#
theme.sunny_bevel_color := sunny_bevel_color;
theme.shady_bevel_color := shady_bevel_color;
theme.current_gadget_colors := current_gadget_colors;
#
theme.pictureframe := pictureframe;
theme.filled_pictureframe := filled_pictureframe;
theme.rounded_pictureframe := rounded_pictureframe;
theme.polygon3d := polygon3d;
#
theme.get_roman_fontname := get_roman_fontname;
theme.get_italic_fontname := get_italic_fontname;
theme.get_bold_fontname := get_bold_fontname;
#
theme.get_roman_font := get_roman_font;
theme.get_italic_font := get_italic_font;
theme.get_bold_font := get_bold_font;
to = make_replyqueue();
#
put_in_oneshot (reply_oneshot, (me_slot, { theme })); # Return value from widget_theme_egg'().
(take_from_mailslot me_slot) # Input args from widget_theme_egg'().
->
{ me, imports, run_gun', end_gun' };
block_until_mailop_fires run_gun'; # Wait for the starting gun.
run (theme_q, { me, imports, to, end_gun' }); # Will not return.
}
where
theme_q = make_mailqueue (get_current_microthread()): Theme_Q;
fun do_something (i: Int) # PUBLIC.
=
put_in_mailqueue (theme_q,
#
\\ ({ me, imports, ... }: Runstate)
=
imports.int_sink i # Demonstrate use of imports.
);
# fun widgetspace (options: gt::Widgetspace_Arg) # PUBLIC.
# =
# { reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( psi::Widgetspace_Egg );
# #
# put_in_mailqueue (theme_q,
# #
# \\ ({ me, ... })
# =
# { (psi::make_widgetspace_egg options NULL) -> widgetspace_egg;
# #
# put_in_oneshot (reply_oneshot, widgetspace_egg);
# }
# );
#
# get_from_oneshot reply_oneshot;
# };
end;
fun process_options (options: List(Option), { name })
=
{ my_name = REF name;
#
apply do_option options
where
fun do_option (MICROTHREAD_NAME n) = my_name := n;
end;
{ name => *my_name };
};
##########################################################################################
# PUBLIC.
#
fun make_widget_theme_egg (options: List(Option)) # PUBLIC. PHASE 1: Construct our state and initialize from 'options'.
=
{ (process_options (options, { name => "tmp" }))
->
{ name };
me = REF ();
\\ () = { reply_oneshot = make_oneshot_maildrop(): Oneshot_Maildrop( (Me_Slot, Exports) ); # PUBLIC. PHASE 2: Start our microthread and return our Exports to caller.
#
xlogger::make_thread name (startup reply_oneshot); # Note that startup() is curried.
(get_from_oneshot reply_oneshot) -> (me_slot, exports);
fun phase3 # PUBLIC. PHASE 3: Accept our Imports, then wait for Run_Gun to fire.
(
imports: Imports,
run_gun': Run_Gun,
end_gun': End_Gun
)
=
{
put_in_mailslot (me_slot, { me, imports, run_gun', end_gun' });
};
(exports, phase3);
};
};
};
end;