2014-11-19 15:17:36 +00:00
|
|
|
#lang scribble/manual
|
|
|
|
@require[@for-label[lux
|
2014-11-22 18:54:08 +00:00
|
|
|
lux/chaos
|
|
|
|
lux/chaos/gui
|
|
|
|
lux/chaos/gui/val
|
|
|
|
lux/chaos/gui/key
|
|
|
|
lux/chaos/gui/mouse
|
2014-11-23 19:20:22 +00:00
|
|
|
racket/contract
|
|
|
|
pict/convert
|
2014-11-22 18:54:08 +00:00
|
|
|
racket/gui/base
|
2014-11-19 15:17:36 +00:00
|
|
|
racket/base]]
|
|
|
|
|
2014-11-22 18:54:08 +00:00
|
|
|
@title{lux: brilliant interactive programs}
|
|
|
|
@author{Jay McCarthy}
|
2014-11-19 15:17:36 +00:00
|
|
|
|
|
|
|
@defmodule[lux]
|
|
|
|
|
2014-11-22 18:54:08 +00:00
|
|
|
The @racketmodname[lux] module provides an efficient way to build
|
|
|
|
interactive programs that consist of plain mathematical functions. It
|
|
|
|
is comparable to @racketmodname[2htdp/universe], although designed to
|
|
|
|
allow more parameterization of how the program interacts.
|
|
|
|
|
2016-04-08 10:19:29 +00:00
|
|
|
Check out some examples in the
|
|
|
|
@link["https://github.com/jeapostrophe/lux/tree/master/examples"]{examples}
|
|
|
|
directory in the source.
|
|
|
|
|
2014-11-22 18:54:08 +00:00
|
|
|
@local-table-of-contents[]
|
|
|
|
|
|
|
|
@section{Structure of a @racketmodname[lux] Program}
|
|
|
|
|
|
|
|
A @racketmodname[lux] program chooses how it will interact be
|
|
|
|
selecting a @tech{chaos} and calling @racket[call-with-chaos] with
|
|
|
|
that @tech{chaos} and a thunk that calls @racket[fiat-lux] with a
|
|
|
|
@tech{word}. @racket[fiat-lux] may be called any number of nested
|
|
|
|
times within a call to @racket[call-with-chaos]. Each subsequent
|
|
|
|
@racket[fiat-lux] takes over the @tech{chaos} until the @tech{word}
|
|
|
|
completes. It is not typically possible to use @tech{word}s with
|
|
|
|
arbitrary @tech{chaos}es, as the @tech{chaos} specifies how the
|
|
|
|
@tech{word} interacts through events and output values.
|
|
|
|
|
|
|
|
When designing @tech{word}s, it is important to realize that
|
|
|
|
@tech{word} updating functions like @racket[word-tick] do not have to
|
|
|
|
return a @tech{word} of the same kind.
|
|
|
|
|
|
|
|
@defproc[(call-with-chaos [c chaos?] [t (-> any)]) any]{
|
|
|
|
|
|
|
|
Runs @racket[t] with @racket[c] as the current @tech{chaos}.}
|
|
|
|
|
|
|
|
@defproc[(fiat-lux [w word?]) any]{
|
|
|
|
|
|
|
|
Runs @racket[w] with the current @tech{chaos}.}
|
|
|
|
|
|
|
|
@section{The Word}
|
|
|
|
|
|
|
|
A @deftech{word} is a generic interface the encapsulates the
|
|
|
|
interactive behavior of a @racketmodname[lux] program.
|
|
|
|
|
|
|
|
@defproc[(word? [x any/c]) boolean?]{
|
|
|
|
|
|
|
|
Identifies @tech{word}s.}
|
|
|
|
|
|
|
|
@defthing[gen:word any/c]{
|
|
|
|
|
|
|
|
The generic interface binding for @tech{word}s.}
|
|
|
|
|
|
|
|
The @tech{word} methods are as follows:
|
|
|
|
|
|
|
|
@defproc[(word-fps [w word?]) flonum?]{
|
|
|
|
|
|
|
|
Returns the desired rate of updating for @racket[w] as
|
|
|
|
frames-per-second. By default, @racket[60.0] is returned.}
|
|
|
|
|
|
|
|
@defproc[(word-label [w word?] [frame-time flonum?]) string?]{
|
|
|
|
|
|
|
|
Returns a label for @racket[w] that could use @racket[frame-time] to
|
|
|
|
show the performance of the @tech{word} rendering. By default, returns
|
|
|
|
@racket[(lux-standard-label "Lux" frame-time)].}
|
|
|
|
|
2015-04-14 18:00:59 +00:00
|
|
|
@defproc[(word-evt [w word?]) evt?]{
|
|
|
|
|
|
|
|
Returns a synchronizable event that the @tech{word} @racket[w]
|
|
|
|
requires notification of. By default, returns @racket[never-evt].}
|
|
|
|
|
2014-11-22 18:54:08 +00:00
|
|
|
@defproc[(word-event [w word?] [e any/c]) word?]{
|
|
|
|
|
|
|
|
Returns a @tech{word} based on @racket[w] that integrates the
|
|
|
|
information from the event @racket[e]. The type of @racket[e] is
|
|
|
|
dependent on the current @tech{chaos}. If this returns @racket[#f],
|
|
|
|
then the @racketmodname[lux] programs stops. By default, returns
|
|
|
|
@racket[w].}
|
|
|
|
|
|
|
|
@defproc[(word-tick [w word?]) word?]{
|
|
|
|
|
|
|
|
Returns a @tech{word} based on @racket[w] after one tick of abstract
|
|
|
|
time. This will be called @racket[(word-fps w)] times per second. If
|
|
|
|
this returns @racket[#f], then the @racketmodname[lux] programs stops.
|
|
|
|
By default, returns @racket[w].}
|
|
|
|
|
|
|
|
@defproc[(word-output [w word?]) any/c]{
|
|
|
|
|
|
|
|
Returns the output value of @racket[w]. The type that this returns is
|
|
|
|
dependent on the current @tech{chaos}. By default, returns
|
|
|
|
@racket[#f]. @tech{chaos}es should always allow @racket[#f] and use it
|
|
|
|
to mean "no output".}
|
|
|
|
|
|
|
|
@defproc[(word-return [w word?]) any/c]{
|
|
|
|
|
|
|
|
Returns a value for @racket[w] when the @racketmodname[lux] programs
|
|
|
|
stops, which happens if @racket[word-event] or @racket[word-tick]
|
2018-12-12 18:45:39 +00:00
|
|
|
return @racket[#f]. By default, returns @racket[w].}
|
|
|
|
|
|
|
|
@subsection{Word Construction}
|
|
|
|
|
|
|
|
A @tech{word} can be created by defining a new @racket[struct] that
|
|
|
|
implements the @racket[gen:word] generic interface@";" or, it can be
|
|
|
|
defined using @racket[word]. The first method is best when it is easy
|
|
|
|
to capture the state of the creation in a structure and the second is
|
|
|
|
preferable when it is better to capture the state implicitly in the
|
|
|
|
captured closures. In the author's experience, the second is also best
|
|
|
|
for creations with complex control flow, because different sorts of
|
|
|
|
@tech{word}s can be returned in different circumstances.
|
|
|
|
|
|
|
|
@defproc[(word [base (or/c #f word?) #f]
|
2019-07-12 23:28:08 +00:00
|
|
|
[#:fps fps real? ....]
|
|
|
|
[#:label label (or/c string? (-> real? string?)) ....]
|
|
|
|
[#:evt evt evt? ....]
|
|
|
|
[#:event event (-> any/c (or/c #f word?)) ....]
|
|
|
|
[#:tick tick (-> (or/c #f word?)) ....]
|
|
|
|
[#:output output any/c ....]
|
|
|
|
[#:return return any/c ....])
|
2018-12-12 18:45:39 +00:00
|
|
|
word?]{
|
|
|
|
|
|
|
|
Return a @tech{word} where the implementations of the methods are as
|
|
|
|
given or inherited from @racket[base] or the defaults (described
|
|
|
|
above). The only subtleties are that: (1) @racket[label] may be a
|
|
|
|
string which is used directly and the frame time is not
|
|
|
|
available@";" (2) @racket[event] is a function that only receives the
|
|
|
|
generated event@";" (3) @racket[tick] is a thunk. The assumption is
|
|
|
|
that the caller of @racket[word] can arrange for the value returned to
|
|
|
|
be captured by these closures if necessary.
|
|
|
|
|
|
|
|
}
|
2014-11-22 18:54:08 +00:00
|
|
|
|
2019-07-12 23:28:08 +00:00
|
|
|
@defform[(word/rec x:id word-args ...)]{Expands to @racket[(letrec ([x (word word-args ...)]) x)].}
|
|
|
|
|
2014-11-22 18:54:08 +00:00
|
|
|
@subsection{Helpers}
|
|
|
|
|
|
|
|
@defproc[(lux-standard-label [s string?] [frame-time flonum?]) string?]{
|
|
|
|
|
|
|
|
Returns @racket[(string-append s ": " _fts)] where @racket[_fts]
|
|
|
|
formats @racket[frame-time] as milliseconds and as frames per
|
|
|
|
second.}
|
|
|
|
|
|
|
|
@section{Chaos}
|
|
|
|
|
|
|
|
A @deftech{chaos} is generic interface for an empty manifestation of
|
|
|
|
an interactive space that is given form by the @tech{word} and
|
|
|
|
@racket[fiat-lux].
|
|
|
|
|
|
|
|
@subsection{Racket GUI Chaos}
|
|
|
|
|
|
|
|
@defmodule[lux/chaos/gui]
|
|
|
|
|
|
|
|
This module provides the standard @tech{chaos} that most users of
|
|
|
|
@racketmodname[lux] will use.
|
|
|
|
|
|
|
|
@defproc[(make-gui [#:mode mode (or/c (one-of/c 'draw 'gl-compat 'gl-core)
|
|
|
|
(is-a?/c gl-config%))
|
2015-05-03 12:11:46 +00:00
|
|
|
'draw]
|
|
|
|
[#:opengl-hires? opengl-hires? boolean? #f]
|
2014-11-26 22:21:55 +00:00
|
|
|
[#:start-fullscreen? start-fullscreen? boolean?
|
2015-05-03 12:11:46 +00:00
|
|
|
#f]
|
|
|
|
[#:frame-style frame-style (listof symbol?) '()]
|
2014-11-22 18:54:08 +00:00
|
|
|
[#:icon icon
|
|
|
|
(or/c #f path-string? (is-a?/c bitmap%))
|
|
|
|
#f]
|
2017-08-30 13:34:32 +00:00
|
|
|
[#:x x
|
|
|
|
(or/c exact-nonnegative-integer? (one-of/c 'left 'center 'right))
|
|
|
|
'center]
|
|
|
|
[#:y y
|
|
|
|
(or/c exact-nonnegative-integer? (one-of/c 'top 'center 'bottom))
|
|
|
|
'center]
|
2014-11-22 18:54:08 +00:00
|
|
|
[#:width width
|
|
|
|
exact-nonnegative-integer?
|
|
|
|
800]
|
|
|
|
[#:height height
|
|
|
|
exact-nonnegative-integer?
|
2017-08-30 13:34:32 +00:00
|
|
|
600]
|
|
|
|
[#:monitor monitor
|
|
|
|
(or/c false/c exact-nonnegative-integer?)
|
|
|
|
#f])
|
2014-11-22 18:54:08 +00:00
|
|
|
chaos?]{
|
|
|
|
|
2017-08-30 13:34:32 +00:00
|
|
|
Returns a @tech{chaos} that opens a GUI frame with a canvas to draw on.
|
|
|
|
The frame is placed at position @racket[x],@racket[y] on monitor @racket[monitor];
|
|
|
|
if @racket[monitor] is @racket[#f], the monitor containing the mouse pointer is used.
|
|
|
|
The default size of the frame is
|
2014-11-22 18:54:08 +00:00
|
|
|
@racket[width]x@racket[height]. The icon for the application is set to
|
2014-11-26 22:21:55 +00:00
|
|
|
@racket[icon]. If @racket[start-fullscreen?] is true, then the frame
|
2015-05-03 12:11:46 +00:00
|
|
|
is initially fullscreen. The frame's style is set to
|
|
|
|
@racket[frame-style].
|
2014-11-22 18:54:08 +00:00
|
|
|
|
|
|
|
The canvas is set up for drawing based on @racket[mode]. If
|
|
|
|
@racket[mode] is @racket['draw], then the canvas assumes that
|
|
|
|
@racketmodname[racket/draw] is used. If other values are used, then
|
|
|
|
the canvas is drawn with OpenGL. If @racket[mode] is
|
|
|
|
@racket['gl-compat], then a compatibility OpenGL profile is used. If
|
|
|
|
@racket[mode] is @racket['gl-core], then a core OpenGL profile is
|
|
|
|
used. If @racket[mode] is a @racket[gl-config%] object, then it is
|
2015-05-03 12:11:46 +00:00
|
|
|
used to initialize the canvas. If @racket[opengl-hires?] is
|
|
|
|
@racket[#t], then the resulting @racket[gl-config%] object will have
|
|
|
|
high resolution mode set.
|
2014-11-22 18:54:08 +00:00
|
|
|
|
|
|
|
The values that @racket[word-event] is called with are either
|
|
|
|
@racket['close] (for when the window's close button is pressed), a
|
|
|
|
@racket[key-event%] object for when keys are pressed, or a
|
|
|
|
@racket[mouse-event%] object for when the mouse is used.
|
|
|
|
|
|
|
|
The values that @racket[word-output] should return are functions that
|
|
|
|
satisfy the contract @racket[(-> real? real? (is-a?/c dc<%>) any)]
|
|
|
|
where the first argument is the width of the canvas, the second is the
|
|
|
|
height, and the third is the canvas's drawing context.}
|
|
|
|
|
|
|
|
@subsubsection{Drawing Values}
|
|
|
|
|
|
|
|
@defmodule[lux/chaos/gui/val]
|
|
|
|
|
|
|
|
This module provides a helpful function for drawing functional images
|
|
|
|
with @racketmodname[lux/chaos/gui].
|
|
|
|
|
2016-12-28 00:22:05 +00:00
|
|
|
@defproc[(make-gui/val [#:scale? scale? boolean? #t])
|
2014-11-22 18:54:08 +00:00
|
|
|
(-> pict-convertible?
|
|
|
|
(-> real? real? (is-a?/c dc<%>) any))]{
|
|
|
|
|
|
|
|
Produces a function that draws @racket[pict-convertible?] values on to
|
|
|
|
@racketmodname[lux/chaos/gui]'s drawing context. If @racket[scale?] is
|
|
|
|
true, then the value will be scaled to file the drawing context (while
|
|
|
|
preserving aspect ratio), otherwise the value will be drawn in the
|
|
|
|
center as-is.}
|
|
|
|
|
|
|
|
@subsubsection{Tracking Keyboard State}
|
|
|
|
|
|
|
|
@defmodule[lux/chaos/gui/key]
|
|
|
|
|
|
|
|
This module provides a set of functions for tracking keyboard state
|
|
|
|
for use inside of @racket[word-tick], rather than updating word state
|
|
|
|
with each event as in @racket[word-event]. Such as system may be
|
|
|
|
appropriate for interactive programs where input is only has an impact
|
|
|
|
at a consistent tick rate.
|
|
|
|
|
|
|
|
@defstruct*[key-state ([keys hash?] [shift? boolean?] [control? boolean?]
|
|
|
|
[meta? boolean?] [alt? boolean?] [mod3? boolean?]
|
|
|
|
[mod4? boolean?] [mod5? boolean?])]{
|
|
|
|
|
|
|
|
Stores a mapping of which keys are presently pressed.}
|
|
|
|
|
|
|
|
@defproc[(make-key-state) key-state?]{
|
|
|
|
|
|
|
|
Produces a @racket[key-state?] object with appropriate defaults.}
|
|
|
|
|
|
|
|
@defproc[(key-event? [x any/c]) boolean?]{
|
|
|
|
|
|
|
|
Identifies key events.}
|
|
|
|
|
2014-11-30 21:47:42 +00:00
|
|
|
@defproc[(key-event-code [ke key-event?]) (or/c (cons/c 'release (or/c char? key-code-symbol?)) (or/c char? key-code-symbol?))]{
|
|
|
|
|
|
|
|
Returns the code of the key event.}
|
|
|
|
|
2014-11-22 18:54:08 +00:00
|
|
|
@defproc[(key-state-update! [ks key-state?] [ke key-event?]) any]{
|
|
|
|
|
|
|
|
Updates @racket[ks] with @racket[ke].}
|
|
|
|
|
|
|
|
@defproc[(key-state-set? [ks key-state?] [kc (or/c char? key-code-symbol?)]) boolean?]{
|
|
|
|
|
|
|
|
Returns true if @racket[kc] is pressed in @racket[kc].}
|
|
|
|
|
|
|
|
@defproc[(key-state-set?! [ks key-state?] [kc (or/c char? key-code-symbol?)]) boolean?]{
|
|
|
|
|
|
|
|
Returns true if @racket[kc] is pressed in @racket[kc] and sets its
|
|
|
|
pressed status to false..}
|
|
|
|
|
|
|
|
@subsubsection{Tracking Mouse State}
|
|
|
|
|
|
|
|
@defmodule[lux/chaos/gui/mouse]
|
|
|
|
|
|
|
|
This module provides a set of functions for tracking mouse state
|
|
|
|
for use inside of @racket[word-tick], rather than updating word state
|
|
|
|
with each event as in @racket[word-event]. Such as system may be
|
|
|
|
appropriate for interactive programs where input is only has an impact
|
|
|
|
at a consistent tick rate.
|
|
|
|
|
2014-11-30 21:47:42 +00:00
|
|
|
@defstruct*[mouse-state ([x real?]
|
|
|
|
[y real?]
|
2014-11-22 18:54:08 +00:00
|
|
|
[left? boolean?]
|
|
|
|
[right? boolean?]
|
|
|
|
[middle? boolean?]
|
|
|
|
[shift? boolean?]
|
|
|
|
[control? boolean?]
|
|
|
|
[meta? boolean?]
|
|
|
|
[alt? boolean?]
|
|
|
|
[mod3? boolean?]
|
|
|
|
[mod4? boolean?]
|
|
|
|
[mod5? boolean?])]{
|
|
|
|
|
|
|
|
Stores the active state of the mouse.}
|
|
|
|
|
|
|
|
@defproc[(make-mouse-state) mouse-state?]{
|
|
|
|
|
|
|
|
Produces a @racket[mouse-state?] object with appropriate defaults.}
|
|
|
|
|
|
|
|
@defproc[(mouse-event? [x any/c]) boolean?]{
|
|
|
|
|
|
|
|
Identifies mouse events.}
|
|
|
|
|
2014-11-30 21:47:42 +00:00
|
|
|
@defproc[(mouse-event-xy [me mouse-event?]) (values real? real?)]{
|
|
|
|
|
|
|
|
Returns the position of the mouse event.}
|
|
|
|
|
2014-11-22 18:54:08 +00:00
|
|
|
@defproc[(mouse-state-update! [ms mouse-state?] [me mouse-event?]) any]{
|
|
|
|
|
|
|
|
Updates @racket[ms] with @racket[me].}
|
|
|
|
|
|
|
|
@subsection{Pair Chaos}
|
|
|
|
|
|
|
|
@defmodule[lux/chaos/pair]
|
|
|
|
|
|
|
|
This module provides a @tech{chaos} that pairs two other @tech{chaos}
|
|
|
|
objects for @racketmodname[lux] programs with multiple interfaces.
|
|
|
|
|
|
|
|
@defproc[(make-pair [left chaos?] [right chaos?]) chaos?]{
|
|
|
|
|
|
|
|
Returns a @tech{chaos} where the input event type is the union of the
|
|
|
|
input events of @racket[left] and @racket[right] and the output type
|
|
|
|
is a pair of the output types of @racket[left] and @racket[right].}
|
|
|
|
|
|
|
|
@subsection{Implementing a Chaos}
|
|
|
|
|
|
|
|
@defmodule[lux/chaos]
|
|
|
|
|
|
|
|
Users of @racketmodname[lux] will probably not need to implement
|
|
|
|
@tech{chaos}es, but will use those that are standard.
|
|
|
|
|
|
|
|
@defproc[(chaos? [x any/c]) boolean?]{
|
|
|
|
|
|
|
|
Identifies @tech{chaos}s.}
|
|
|
|
|
|
|
|
@defthing[gen:chaos any/c]{
|
|
|
|
|
|
|
|
The generic interface binding for @tech{chaos}es.}
|
|
|
|
|
|
|
|
The @tech{chaos} methods are as follows:
|
|
|
|
|
2014-11-26 22:21:55 +00:00
|
|
|
@defproc[(chaos-start! [c chaos?]) any]{
|
|
|
|
|
|
|
|
Called at the start of using @racket[c] as the current
|
|
|
|
@tech{chaos}. By default, does nothing.}
|
|
|
|
|
2014-11-22 18:54:08 +00:00
|
|
|
@defproc[(chaos-yield [c chaos?] [e evt?]) any]{
|
|
|
|
|
|
|
|
Synchronizes on @racket[e] in a way safe for @racket[c]. By default,
|
|
|
|
calls @racket[sync].}
|
|
|
|
|
|
|
|
@defproc[(chaos-event [c chaos?]) evt?]{
|
|
|
|
|
|
|
|
Returns an event that when ready returns a @racket[c] event value. By
|
|
|
|
default, returns @racket[never-evt].}
|
|
|
|
|
|
|
|
@defproc[(chaos-output! [c chaos?] [o any/c]) any]{
|
|
|
|
|
|
|
|
Outputs @racket[o] to @racket[c]. @tech{chaos}es should always allow
|
|
|
|
@racket[#f] and use it to mean "no output". By default, does nothing.}
|
|
|
|
|
|
|
|
@defproc[(chaos-label! [c chaos?] [s string?]) any]{
|
|
|
|
|
|
|
|
Outputs @racket[s] as the label of @racket[c]. By default, does
|
|
|
|
nothing.}
|
|
|
|
|
|
|
|
@defproc[(chaos-swap! [c chaos?] [t (-> any)]) any]{
|
|
|
|
|
|
|
|
Calls @racket[t] while preparing @racket[c] to run a different
|
|
|
|
@tech{word}. By default, just calls @racket[t].}
|
2014-11-26 22:21:55 +00:00
|
|
|
|
|
|
|
@defproc[(chaos-stop! [c chaos?]) any]{
|
|
|
|
|
|
|
|
Called at the end of using @racket[c] as the current @tech{chaos}. By
|
|
|
|
default, does nothing.}
|