Skip to content
This repository was archived by the owner on Oct 26, 2023. It is now read-only.

Commit 470a46d

Browse files
committed
doc: add rough custom view section
1 parent 25a2c89 commit 470a46d

File tree

3 files changed

+153
-0
lines changed

3 files changed

+153
-0
lines changed

Diff for: gui-easy/gui/easy/scribblings/custom-views.scrbl

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#lang scribble/manual
2+
3+
@(require (for-label racket/base
4+
racket/class
5+
(prefix-in gui: racket/gui)
6+
racket/gui/easy
7+
racket/gui/easy/operator))
8+
9+
@title{Custom Views}
10+
11+
@(define canvas-list-link
12+
(link "https://github.com/massung/racket-canvas-list/" (tt "canvas-list<%>")))
13+
14+
You can create your own views by implementing the @racket[view<%>]
15+
interface.
16+
17+
As an example, let's wrap Jeffrey Massung's @|canvas-list-link|. I find
18+
it helps to work backwards from the API you'd like to end up with. In
19+
this case, that would be:
20+
21+
@racketblock[
22+
(canvas-list
23+
|@entries|
24+
(λ (item state dc w h)
25+
(draw-item ...))
26+
(λ (item)
27+
(printf "double-clicked ~s~n" item)))
28+
]
29+
30+
A @racketid[canvas-list] should take an observable of a list of
31+
entries, a function that knows how to draw each entry to a
32+
@racket[gui:dc<%>] and a callback for when the user double-clicks an
33+
entry. The @racketid[canvas-list] function should probably then look
34+
something like this:
35+
36+
@racketblock[
37+
(define (canvas-list |@entries| draw [action void])
38+
(new canvas-list-view%
39+
[|@entries| |@entries|]
40+
[draw draw]
41+
[action action]))
42+
]
43+
44+
Next, we can define a skeleton implementation of
45+
@racketid[canvas-list-view%]:
46+
47+
@racketblock[
48+
(define canvas-list-view%
49+
(class* object% (view<%>)
50+
(init |@entries| draw action)
51+
(super-new)
52+
53+
(define/public (dependencies)
54+
(error 'create "not implemented"))
55+
56+
(define/public (create parent)
57+
(error 'create "not implemented"))
58+
59+
(define/public (update v what val)
60+
(error 'update "not implemented"))
61+
62+
(define/public (destroy v)
63+
(error 'destroy "not implemented"))))
64+
]
65+
66+
Views must communicate what @tech{observables} they depend on to their
67+
parents. In our case, that's straightforward:
68+
69+
@racketblock[
70+
(define canvas-list-view%
71+
(class* object% (view<%>)
72+
...
73+
74+
(define/public (dependencies)
75+
(list |@entries|))
76+
77+
...))
78+
]
79+
80+
When a view is rendered, its parent is in charge of calling its
81+
@method[view<%> create] method. The create method must instantiate a
82+
GUI object, associate it the passed-in @racketid[parent], perform any
83+
initialization steps and then return it. In our case:
84+
85+
@racketblock[
86+
(define canvas-list-view%
87+
(class* object% (view<%>)
88+
...
89+
90+
(define/public (create parent)
91+
(new canvas-list%
92+
[parent parent]
93+
[items (obs-peek |@entries|)]
94+
[paint-item-callback (λ (self entry state dc w h)
95+
(draw entry state dc w h))]
96+
[action-callback (λ (self item event)
97+
(action item))]))
98+
99+
...))
100+
]
101+
102+
When the @tech{observables} the view depends on change, its parent
103+
will call its @method[view<%> update] method with the GUI object that
104+
the view returned from its @method[view<%> create] method, the
105+
observable that changed and the observable's value when it changed.
106+
The view is then in charge of modifying its GUI object appropriately.
107+
108+
@racketblock[
109+
(define canvas-list-view%
110+
(class* object% (view<%>)
111+
...
112+
113+
(define/public (update v what val)
114+
(case/dep what
115+
[|@entries| (send v set-items val)]))
116+
117+
...))
118+
]
119+
120+
Finally, when a view is no longer visible, its @method[view<%>
121+
destroy] method is called to dispose of the GUI object and perform any
122+
teardown actions. In our case, there's nothing to do. We can let
123+
garbage collection take care of destroying the @racketid[canvas-list%]
124+
object:
125+
126+
@racketblock[
127+
(define canvas-list-view%
128+
(class* object% (view<%>)
129+
...
130+
131+
(define/public (destroy v)
132+
(void))))
133+
]
134+
135+
When the view becomes visible again, its @method[view<%> create]
136+
method is called again and the whole cycle repeats itself.
137+
138+
That's all there is to it. See the "hn.rkt" example for a program
139+
that uses a custom view.

Diff for: gui-easy/gui/easy/scribblings/gui-easy.scrbl

+1
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ so expect some breaking changes.
2929

3030
@include-section["quickstart.scrbl"]
3131
@include-section["geometry.scrbl"]
32+
@include-section["custom-views.scrbl"]
3233
@include-section["reference.scrbl"]

Diff for: gui-easy/gui/easy/scribblings/reference.scrbl

+13
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,19 @@ using @racket[obs-update!].
526526
}
527527

528528

529+
@section{View Helpers}
530+
531+
@defform[(case/dep what-expr
532+
[dep-expr body ...] ...+)
533+
#:contracts ([what-expr obs?]
534+
[dep-expr obs?])]{
535+
536+
Executes the body of the first clause @racket[body] whose
537+
@racket[dep-expr] is @racket[eq?] to @racket[what-expr]. Logs the
538+
@racket[dep-expr] that matched to the @racket['gui-easy] topic.
539+
}
540+
541+
529542
@section{Observable Operators}
530543
@defmodule[racket/gui/easy/operator]
531544

0 commit comments

Comments
 (0)