bogdanp / racket-gui-easy Goto Github PK
View Code? Open in Web Editor NEWDeclarative GUIs in Racket.
Home Page: https://docs.racket-lang.org/gui-easy/index.html
Declarative GUIs in Racket.
Home Page: https://docs.racket-lang.org/gui-easy/index.html
The docs say that group
accepts these values in the #:style
argument:
(listof (or/c 'border 'deleted
'hscroll 'auto-hscroll 'hide-hscroll
'vscroll 'auto-vscroll 'hide-vscroll))
…but of these, only 'deleted
is actually accepted. Using any of the other values results in:
initialization for group-box-panel%: invalid symbol in given style list
invalid symbol: 'border
given: '(border)
This makes sense, given that the underlying group-box-panel%
also only accepts 'deleted
for its style argument.
In the dependencies of gui-easy
, the package gui-easy-lib
is listed twice -- once as deps
and once as build-deps
. This ended up creating duplicate dependencies.
I am not sure whether this is related, but the last few lines seem to suggest that duplicate dependencies cause problems when updating packages:
Resolving "gui-easy-lib" via https://pkgs.racket-lang.org
The following out-of-date packages are listed as dependencies of gui-easy
and they will be automatically updated:
gui-easy-lib
gui-easy-lib
Using cached17005413591700541359311 for https://github.com/Bogdanp/racket-gui-easy.git?path=gui-easy-lib
...
Uninstalling to prepare re-install of gui-easy-lib
Moving gui-easy-lib to trash: <PATH>
Uninstalling to prepare re-install of gui-easy-lib
raco pkg update: package not currently installed
package: gui-easy-lib
current scope: installation
from https://racket.discourse.group/t/package-removed-after-trying-to-update-them/2521
So (list-view empty (const (text "hi")))
[a toy example] should work according to the docs but doesn't.
Which is correct? For now I will make sure the first argument is observable…
GUI easy is really nice, being new to reactive programming, it somehow feels intuitive, so I think the design is quite user friendly!
On to the question I had- I ran into a curious behavior using the input
UI element. Using the following code sample, when I type return, the input box text remains the same, though the observable the input box I think is observing has been cleared. Is there something different I could try?
Thanks!
$ racket --version
Welcome to Racket v8.4 [cs].
#lang racket
(require racket/gui/easy
racket/gui/easy/operator)
(define/obs @input "")
(define/obs @message "Type return to send a message")
(render
(window
(text @message)
(input @input
(λ (kind s)
(when (symbol=? kind 'return)
(:= @message (string-append "You typed: " s))
(:= @input "")))))) ; Input box still shows text after updating this observable.
Noticed this when trying to set #:color
on a text
component.
This is from the examples folder:
% racket text-color.rkt
send: no such method
method name: set-color
class name: message%
context...:
/Applications/Racket v8.2/collects/racket/private/class-internal.rkt:4680:0: obj-error
/Users/kyushu/Library/Racket/8.2/pkgs/gui-easy-lib/gui/easy/private/view/text.rkt:20:4: create method in text%
/Users/kyushu/Library/Racket/8.2/pkgs/gui-easy-lib/gui/easy/private/view/window.rkt:38:4: create method in window-like%
.../private/arrow-higher-order.rkt:379:33
/Users/kyushu/Library/Racket/8.2/pkgs/gui-easy-lib/gui/easy/private/renderer.rkt:34:4: render method in renderer%
/Users/kyushu/Library/Racket/8.2/pkgs/gui-easy-lib/gui/easy/private/renderer.rkt:55:0: render
/Applications/Racket v8.2/collects/racket/contract/private/arrow-val-first.rkt:489:18
body of "/Users/kyushu/github/racket-gui-easy/examples/text-color.rkt"
In particular, let's say @e
can be either a cat?
or a dog?
after something like
(struct cat [meows] #:transparent)
(struct dog [woofs] #:transparent)
And let's say we have two views cat-view
and dog-view
that expect (obs/c cat?)
and (obs/c dog?)` respectively.
These are simple, and it would be easy to write a single view that mapped values into one of the struct's fields and then did something else, but imagine a much more complicated disparity (say, human game players and non-human played monster groups, with very different internal structures and views).
if-view
and derived friends like cond-view
immediately evaluate their sub-views, so they aren't as useful for heterogeneous data (the views that aren't being used will often still raise exceptions and break things).
I have found dyn-view
, but it is tricky to use correctly, if I understand my problem correctly. The idea would be
(dyn-view @e
(lambda (e)
(cond
[(cat? e) (cat-view @e)]
[(dog? e) (dog-view @e)])))
since the following won't work
(cond-view
[(~> @e cat?) (cat-view @e)]
[(~> @e dog?) (dog-view @e)])
So far this all seems fine, but I have the following problem: if cat-view
or dog-view
create their own internal state for updating parts of the view (say, a tabs
view with a selection), that internal state can lead to bugs in the application.
Why? It has taken me some time to reason through this, so I could be wrong, but:
dyn-view
only remakes the view if @e
changes, so if the internal state changes the view will not be re-made and therefore won't update (?). This I'm less sure about, but it's what I am observing in practice. That is, I can update the internal state and see it propagated to relevant observables, but sub-views are not correctly updated.dyn-view
recreates the entire view if @e
does change, it also resets any internal state. For example, a tab selection would be reset. This makes sense, because it re-creates the internal state after throwing away the old one, since we are calling the function to create the view anew, and that function creates the internal state.In my actual situation, I have a observable list of (cons/c id? (or/c cat? dog?))
(though again the structures are more complicated and not really unifiable), and I want to use dyn-view
to build the entry-views for each item in the list (because of the issue with if-view
I mentioned above). But the entry-view for one of the types (say dog?
) of things in the list does maintain such internal state (recall the monster example we discussed, where the selection was internal to the view). Both types of entry-view allow callbacks; the actually used callbacks update the observable list by (functionally) modifying the corresponding entry in some way; this causes that entry to be remade by the dyn-view
IIUC. For the cat?
things, everything works pretty normally. For the dog?
things, changing tabs updates the "highlighted" tab, but not the child view (which depends on an observable that is updating, as I have confirmed with obs-observe!
). In addition, interacting with elements that trigger the callbacks which modify the dog?
s does correctly modify them, but the state of that entry-view behaves oddly (usually resetting the highlighted tab to be the first one, as expected based on point 2 above, but without change to the child view as described here).
Without the dyn-view
(say, creating a single (obs/c dog?)
, its dog-view
, and then rendering it in a window) everything works fine.
This is a lot to digest, and I apologize for the lack of straightforward code examples, but it would take me some time to extract useful examples from my actual code. Unfortunately I will also be too busy to respond very much for the next couple of weeks :(
I suppose my question is: I can extract the internal tab-selection state so that the dyn-view
correctly remakes the view when the selection state changes, too, but it's going to be a fair amount of effort. Is that really my only option? Will it solve both problems above?
I think I can solve (2) by having the dyn-view
depend on a combined observable that mixes @e
with the previously internal state, although in the cat?
case that internal state is actually unused.
For (1), though, it's less clear why the sub-views aren't updating when the observables that control them are, so I'm not sure if this would help or not.
An alternate idea might be to have the id
capture both the uniqueness criterion that it does now and the distinction of type; i.e., the new state would be (listof (cons/c (cons/c id? type?) (or/c cat? dog?)))
. The reason is that we have access to the key value in the list-view
callback to make entry-views. I have two issues with this:
id?
will always have the same type?
, but future designs could possibly change that by allowing entries to be added, removed, or edited in some way that might change the type?
.type?
) that effectively already present just by asking if the entry is cat?
or dog?
.Do you have experience with this kind of heterogeneity and internal state in gui-easy? Am I understanding my problem and my options right? Would my first solution solve the problem, or could there be a bug in the propagation of events to children of dyn-view
(so that maybe the tabs
should be updating but isn't)?
Right now closing a rendered dialog is non-trivial, or is too ugly.
Maybe there is a way, but I can only think of either using a mixin or this which doesn't work on dialog but does on frames.
(define (d r)
(define (close-dialog) (send (renderer-root (force r)) show #f))
(dialog
(text (format "~a: first ~a characters" identity len))
(input "" (λ (evt str)
(displayln evt)
(when (eq? evt 'return)
(cond
[(string=? str (substring password 0 len))
(close-dialog)
(next-dialog)]
[else
(close-dialog)
(error-dialog)]))))))
(define r (render (d (delay r)) (get-parent)))
This throws an error since r
isn't defined because of how dialog's yield
work.
The current default is to have no keybinds, but I don't think this is very helpful. We all expect Ctrl+C/Ctrl+V et al to be available everywhere!
(keymap:get-editor) includes all the keybinds that I would expect for a plain text field and it makes sense as a default.
This would save me a couple lines of code and memorisation each time I make a little application with gui-easy :)
I have a use-case where I would like to turn Racket parameters into observables. I can wrap the parameter in an observable, and then manage all the patterns of using it myself, but I managed to extract those patterns. The trouble is, I can't create my own thing and tell racket/gui/easy that it should count as observable and how it behaves under all the observable operations. The best I can do currently is provide a set of macros that I call obsp
for observable parameter. Now the management is hidden away, but I still have to remember the right thing to call (<~
or <~p
?).
The fundamental reason for this is that my custom observable parameter is really three values: the parameter itself, an observable of the parameter, and a derived observable that uses the parameter's value. This third one is what we are usually concerned with when reading, and the value we want to use when writing via an update function, but we can only do the writes through the second value.
If there were a prop:
or gen:
interface I could implement on a struct and have it co-operate with all of racket/gui/easy's observable procedures, that would be my ideal world. As it is, here's the set of macros for observable parameters (they essentially hide the second value from you, though it is accessible; in my examples, it would be @internal-@op
).
#lang racket
(require racket/gui/easy
racket/gui/easy/operator
syntax/parse/define
(for-syntax racket/syntax))
(define-for-syntax (@internal stx id)
(format-id stx "@internal-~a" id #:source stx))
(define-syntax-parse-rule (define/obsp n:id p:expr)
#:with @internal (@internal #'n (syntax-e #'n))
(begin
(define/obs @internal p)
(define/obs n (@internal . ~> . (λ (the-p) (the-p))))))
(define (internal-obsp-update! internal o f)
(internal . <~ . (λ (the-p) (the-p (f (the-p))) the-p))
(obs-peek o))
(define-syntax-parse-rule (obsp-update! o:id f:expr)
#:with @internal (@internal #'o (syntax-e #'o))
(internal-obsp-update! @internal o f))
;; don't need p~>/obsp-map because ~>/obs-map already work
(define-syntax <~p (make-rename-transformer #'obsp-update!))
(define-syntax-parse-rule (:=p o:id v:expr) (<~p o (const v)))
(define-syntax-parse-rule (λ:=p o:id f:expr) (λ (v) (:=p o (f v))))
(define-syntax-parse-rule (λ<~p o:id f:expr) (thunk (<~p o f)))
(define p (make-parameter #f))
(define/obsp @op p)
(obs-observe! @op (λ (x) (printf "op: Value: Got ~s~n" x)))
(equal? (obs-peek @op) (p))
;; bypass @op
(p 1)
;; new versions
(obsp-update! @op add1) ;; 2
(<~p @op add1) ;; 3
(:=p @op 4) ;; 4
((λ:=p @op add1) (obs-peek @op)) ;; 5
((λ<~p @op add1)) ;; 6
At least :=p
looks cute…
The render-popup-menu
function takes x-y coordinates and renders a popup-menu (pum) relative to the root widget of a renderer.
Canvases are windows and can react to mouse events, so I can use a mixin with, say, pict-canvas
to make right-clicks shows a popup-window if I have a renderer. The mouse-event's x-y coordinates are relative to the canvas (!).
I get a renderer only by calling render
on the full tree of views, so its root widget is from the (window …)
view.
This combines to result in the x-y coordinates being relative to the wrong part of the GUI, so the pum is in the wrong place.
Possible solutions I've come up with:
render-pum
via reflection (benknoble/frosthaven-manager@53c567c)(renderer-root …)
. This would probably be much nicer, but I can't figure out how to do it.Have you run into this in the past? How did you solve it?
Almost all of the observable "operators" are synonyms for named functions, but one common one is not: :=
.
By convention with other observable functions, and with the rest of Racket, I think the obvious name for this would be obs-set!
.
Maybe I'm just an old Lisp fart but I don't use the (a . f . b)
syntax and I don't find it easy to mentally switch between prefix and infix notation, and I have a hard time remembering all the ><~=#
characters, so a simple (obs-set! o v)
would be really nice to have. Yeah, it's easy to write myself, or I could use (obs-update! o (λ (_) v))
... I know this is a silly little request.
The contract for progress
's #:range
is documented as (maybe-obs gui:dimension-integer?)
which should be equivalent to (maybe-obs (integer-in 0 1000000))
.
However:
(render (window (progress 0 #:range 0)))
gives the following, which references positive-dimension-integer?
in the #:range
argument
progress: contract violation
expected: positive-dimension-integer?
given: 0
in: a part of the or/c of
the #:range argument of
(->*
((or/c
position-integer?
(obs/c position-integer?)))
(#:enabled?
(or/c boolean? (obs/c boolean?))
#:label
(or/c label-string? (obs/c label-string?))
#:min-size
(or/c
(list/c
(or/c #f dimension-integer?)
(or/c #f dimension-integer?))
(obs/c
(list/c
(or/c #f dimension-integer?)
(or/c #f dimension-integer?))))
#:range
(or/c
positive-dimension-integer?
(obs/c positive-dimension-integer?))
#:stretch
(or/c
(list/c boolean? boolean?)
(obs/c (list/c boolean? boolean?)))
#:style
(listof
(or/c
'horizontal
'vertical
'plain
'vertical-label
'horizontal-label
'deleted)))
(is-a?/c view<%>))
contract from:
<pkgs>/gui-easy-lib/gui/easy/view.rkt
blaming: top-level
(assuming the contract is correct)
at: <pkgs>/gui-easy-lib/gui/easy/view.rkt:145:3
P.S. Hopefully you are taking these reports as signs of an enthusiastic user rather than gripes and complaints. As I play with things, often in the setting of a personal but real project, I run into questions or concerns. I plan to keep playing, because gui-easy is far more ergonomic for me than racket/gui ❤️
My program runs a timer in the background to periodically refresh the information displayed in the GUI. When the window is closed, the timer remains running and the process keeps running, invisibly.
Is it possible to detect when the main window closes, so that I can stop the timer and end the process?
As always, thank you so much for everything you've done for the Racket world!
I had some code in a button handler that did the equivalent of
(render
(dialog
(apply hpanel (map make-view (obs-peek @xs)))))
When auditing my use of obs-peek
, I converted this to
(render
(dialog
(observable-view @xs (lambda (xs) (apply hpanel (map make-view xs))))))
This works, but with the following message when closing the dialog:
change-children: cannot delete non-window area
area: (object:wx-make-pane% ...)
container: (object:context-mixin ...)
context...:
/Applications/Racket v8.8/collects/ffi/unsafe/atomic.rkt:73:13
/Users/Knoble/Library/Racket/8.8/pkgs/gui-easy-lib/gui/easy/private/view/window.rkt:91:4: destroy method in window-like%
/Users/Knoble/Library/Racket/8.8/pkgs/gui-easy-lib/gui/easy/private/renderer.rkt:63:7
/Applications/Racket v8.8/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:435:6
/Applications/Racket v8.8/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:486:32
/Applications/Racket v8.8/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:634:3
Here's a minimal example:
(require racket/gui/easy racket/gui/easy/operator)
(define/obs @xs (list 1 2 3 4))
(define (make-x-button x)
(button (~a x) (lambda () (displayln x))))
(render
(dialog
(observable-view @xs (lambda (xs) (apply hpanel (map make-x-button xs))))))
; interact with the buttons if you wish, then close the dialog
output (I think the object
line appearing after button outputs is an artifact of timing or something)
1
3
2
(object:renderer% ...)
change-children: cannot delete non-window area
area: (object:wx-make-pane% ...)
container: (object:context-mixin ...)
append: contract violation
expected: list?
given: #<void>
[,bt for context]
I think something must be using a pane%
or subclass somewhere, since those are the only default GUI widgets on which change-children
would fail (https://docs.racket-lang.org/gui/area-container___.html#%28meth._%28%28%28lib._mred%2Fmain..rkt%29._area-container~3c~25~3e%29._change-children%29%29 and https://docs.racket-lang.org/gui/Windowing_Classes.html).
Every variation on the following program I have tried has failed:
#lang racket
(require racket/gui/easy)
(define root (window (button "Pop" (thunk (render-popup-window root (popup-menu (menu-item "Click Me" (thunk (displayln "Hi")))))))))
A blur of "invalid memory reference" errors flies by, and not even C-\ can stop them (I have to kill
the process). This was based partly on the popup in your video in #13.
Platform details:
Welcome to Racket v8.4 [cs].
OS: macOS Catalina 10.15.7 19H1824 x86_64
Host: MacBookPro16,1
Kernel: 19.6.0
Uptime: 8 days, 4 hours, 21 mins
Packages: 211 (brew)
Shell: zsh 5.7.1
Resolution: 1792x1120, 3008x1692
DE: Aqua
WM: Quartz Compositor
WM Theme: Purple (Dark)
Terminal: alacritty
CPU: Intel i9-9880H (16) @ 2.30GHz
GPU: Intel UHD Graphics 630, AMD Radeon Pro 5500M
Memory: 10087MiB / 16384MiB
I had a function that looked like this:
(define (make-creature-view k @e)
(define make-player-or-monster-group-view
(match-lambda
[(cons _ (? player?)) (make-player-view k @e)]
[(cons _ (cons _ (? monster-group?))) (make-monster-group-view k @e)]))
(dyn-view @e make-player-or-monster-group-view))
[Ignore the (cons _
patterns; I am still deciding how best to restructure the data for that part.]
The make-creature-view
function is used in a list-view
over an observable @creatures
which holds items that are either player?
or monster-group?
(again, ignoring the initial cons
bits).
The dyn-view
lets me do the job of an if-view
/cond-view
but without constructing the sub-views of all conditional branches when the data doesn't match what is needed (e.g., with a cond-view
here, I can't avoid that the monster-group-view
gets a player?
or vice-versa, because the sub-views are constructed greedily). At least, I think this is why I am using dyn-view
; I can't remember.
But here's where things get interesting: the view returned by make-player-view
has no dependencies other than @e
, which (conveniently) the dyn-view
also monitors and reacts to. On the other hand, make-monster-group-view
creates a view that depends on an observable not mentioned here. I update it in response to other events and was expecting the GUI to update with new information, but it doesn't (presumably because dyn-view
doesn't know about it, so can't forward updates, so nothing is redrawn).
There's a very silly hack to fix this (notice the obs-combine
and the extra cons
patterns):
(define (make-creature-view k @e)
(define make-player-or-monster-group-view
(match-lambda
[(cons _ (cons _ (? player?))) (make-player-view k @e)]
[(cons _ (cons _ (cons _ (? monster-group?)))) (make-monster-group-view k @e)]))
(dyn-view
;; HACK: Combine @e with @ability-decks to register dyn-view dependency on
;; @ability-decks; but, the actual observable we care about is still just
;; @e. (You can see that we ignore the car of the resulting pair in the
;; match patterns above.) This is because make-monster-group-view creates
;; a view that depends on @ability-decks, but dyn-view isn't aware of this
;; dependency.
(obs-combine cons @ability-decks @e)
make-player-or-monster-group-view))
This works (!) but isn't scalable or maintainable: if more dependencies are added to make-monster-group-view
, they need to balloon into the obs-combine
and match
, too. And if one is forgotten, there's a sort of "spooky action at a distance" bug like the lack of updates I described above.
I actually first tried a slightly better version that used (obs-combine (lambda (a e) e) @ability-decks @e)
, but that didn't work. It only improved the match
, too; my points above still apply.
Have you encountered something like this before? Do you have a suggestion to alleviate this? It's like I want the static parts of if-view
but the dynamic parts of dyn-view
(again, because of the issue I have with disjoint data).
I haven't found a small or deterministic reproduction for this yet, but you should be able to follow a few steps and see the issue.
First, drop the following in ring1.rkt
:
#lang frosthaven-manager/aoe
x x
x x x
x x
Then, drop the following in bestiary.rkt
:
#lang frosthaven-manager/bestiary
begin-monster "Ancient Artillery" ("Ancient Artillery")
[0 normal [HP 4] [Move 0] [Attack 2] [Immunities {"Muddle"}]]
[0 elite [HP 7] [Move 0] [Attack 3] [Immunities {"Muddle"}]]
[1 normal [HP 6] [Move 0] [Attack 2] [Immunities {"Muddle"}]]
[1 elite [HP 9] [Move 0] [Attack 3] [Immunities {"Muddle"}]]
[2 normal [HP 8] [Move 0] [Attack 2] [Immunities {"Muddle"}]]
[2 elite [HP 12] [Move 0] [Attack 3] [Immunities {"Muddle"}]]
[3 normal [HP 9] [Move 0] [Attack 3] [Immunities {"Muddle"}]]
[3 elite [HP 14] [Move 0] [Attack 4] [Immunities {"Muddle"}]]
[4 normal [HP 12] [Move 0] [Attack 3] [Immunities {"Muddle"}]]
[4 elite [HP 16] [Move 0] [Attack 5] [Immunities {"Muddle"}]]
[5 normal [HP 15] [Move 0] [Attack 3] [Immunities {"Muddle"}]]
[5 elite [HP 21] [Move 0] [Attack 5] [Immunities {"Muddle"}]]
[6 normal [HP 18] [Move 0] [Attack 4] [Immunities {"Muddle"}]]
[6 elite [HP 26] [Move 0] [Attack 6] [Immunities {"Muddle"}]]
[7 normal [HP 21] [Move 0] [Attack 5] [Immunities {"Muddle"}]]
[7 elite [HP 34] [Move 0] [Attack 7] [Immunities {"Muddle"}]]
end-monster
begin-ability-deck "Ancient Artillery"
["Massive Blast" 57 {"Push 1 all adjacent" "Attack -1, Range 3, aoe(ring1.rkt)"}]
["Massive Blast" 57 {"Push 1 all adjacent" "Attack -1, Range 3, aoe(ring1.rkt)"}]
["Massive Blast" 57 {"Push 1 all adjacent" "Attack -1, Range 3, aoe(ring1.rkt)"}]
["Massive Blast" 57 {"Push 1 all adjacent" "Attack -1, Range 3, aoe(ring1.rkt)"}]
["Massive Blast" 57 {"Push 1 all adjacent" "Attack -1, Range 3, aoe(ring1.rkt)"}]
["Massive Blast" 57 {"Push 1 all adjacent" "Attack -1, Range 3, aoe(ring1.rkt)"}]
["Massive Blast" 57 {"Push 1 all adjacent" "Attack -1, Range 3, aoe(ring1.rkt)"}]
["Massive Blast" 57 {"Push 1 all adjacent" "Attack -1, Range 3, aoe(ring1.rkt)"}]
end-ability-deck
Now, if you install the Frosthaven Manager and run it connected to a terminal window (to see error messages), you should be able to
I see (for one click)
ctx: undefined;
cannot use field before initialization
context...:
/Users/Knoble/Library/Racket/8.8/pkgs/gui-easy-lib/gui/easy/private/view/view.rkt:31:4: get-context method in context-mixin
/Users/Knoble/Library/Racket/8.8/pkgs/gui-easy-lib/gui/easy/private/view/canvas.rkt:36:29
/Applications/Racket v8.8/collects/racket/private/more-scheme.rkt:148:2: call-with-break-parameterization
/Applications/Racket v8.8/collects/ffi/unsafe/atomic.rkt:73:13
/Applications/Racket v8.8/share/pkgs/gui-lib/mred/private/wx/common/canvas-mixin.rkt:144:4: do-on-paint method in canvas-mixin
/Applications/Racket v8.8/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:435:6
/Applications/Racket v8.8/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:486:32
/Applications/Racket v8.8/collects/racket/private/more-scheme.rkt:148:2: call-with-break-parameterization
/Applications/Racket v8.8/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:370:11: eventspace-handler-thread-proc
ctx: undefined;
cannot use field before initialization
context...:
/Users/Knoble/Library/Racket/8.8/pkgs/gui-easy-lib/gui/easy/private/view/view.rkt:31:4: get-context method in context-mixin
/Users/Knoble/Library/Racket/8.8/pkgs/gui-easy-lib/gui/easy/private/view/canvas.rkt:36:29
/Applications/Racket v8.8/collects/racket/private/more-scheme.rkt:148:2: call-with-break-parameterization
/Applications/Racket v8.8/collects/ffi/unsafe/atomic.rkt:73:13
/Applications/Racket v8.8/share/pkgs/gui-lib/mred/private/wx/common/canvas-mixin.rkt:144:4: do-on-paint method in canvas-mixin
/Applications/Racket v8.8/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:435:6
/Applications/Racket v8.8/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:486:32
/Applications/Racket v8.8/collects/racket/private/more-scheme.rkt:148:2: call-with-break-parameterization
/Applications/Racket v8.8/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:370:11: eventspace-handler-thread-proc
ctx: undefined;
cannot use field before initialization
context...:
/Users/Knoble/Library/Racket/8.8/pkgs/gui-easy-lib/gui/easy/private/view/view.rkt:31:4: get-context method in context-mixin
/Users/Knoble/Library/Racket/8.8/pkgs/gui-easy-lib/gui/easy/private/view/canvas.rkt:36:29
/Applications/Racket v8.8/collects/racket/private/more-scheme.rkt:148:2: call-with-break-parameterization
/Applications/Racket v8.8/collects/ffi/unsafe/atomic.rkt:73:13
/Applications/Racket v8.8/share/pkgs/gui-lib/mred/private/wx/common/canvas-mixin.rkt:144:4: do-on-paint method in canvas-mixin
/Applications/Racket v8.8/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:435:6
/Applications/Racket v8.8/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:486:32
/Applications/Racket v8.8/collects/racket/private/more-scheme.rkt:148:2: call-with-break-parameterization
/Applications/Racket v8.8/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:370:11: eventspace-handler-thread-proc
I can't explain the error, though, nor why it only shows up sometimes. Race condition?
Once you can reproduce the error, I recommend using File > Save Game to save the current game. Then you can reload it anytime with File > Load Game or, when launching the game through the command-line, by passing the save file as the first argument (e.g., frosthaven-manager <save-file>
). Even here, sometimes it takes multiple "AoE" clicks to trigger the issue.
[Unfortunately the save files are not totally agnostic since they contain user-specific paths. If you wanted to create /Users/Knoble
and paths under it for testing, I could send you a save file.]
I'm not sure how to debug this myself, but I do have the full error message and code for you to take a look at. I'm pretty sure this is a bug in your library rather than a bug in my sample code here.
Thank you!
The error message:
; send: target is not an object
; target: #f
; method name: remove-dependencies
; Context (plain; to see better errortrace context, re-run with C-u prefix):
; /home/cadence/.racket/collects/racket/private/class-internal.rkt:4681:0 obj-error
; /home/cadence/.racket/8.6/pkgs/gui-easy-lib/gui/easy/private/view/if.rkt:103:4 remove&destroy-then-view
; /home/cadence/.racket/8.6/pkgs/gui-easy-lib/gui/easy/private/view/if.rkt:45:4 update method in if-view%
; /home/cadence/.racket/8.6/pkgs/gui-easy-lib/gui/easy/private/view/container.rkt:21:4 update-children method in container%
; /home/cadence/.racket/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:435:6
; /home/cadence/.racket/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:460:2 yield
Full code to reproduce:
#lang racket/base
(require racket/gui/easy
racket/gui/easy/operator)
;; queue is a list of pairs. car of the pair is the name. cdr of the pair is the state.
;; queue goes into the list-view. each pair is rendered as an hpanel with text.
;; --- OBSERVABLES ---
(define/obs @queue '(("Row one" . 1) ("Row two" . 2)))
;; --- MUTATORS ---
(define (do-add-to-queue item)
(<~ @queue
(λ (queue)
(append queue (list item)))))
(define (do-set-item-state target-item-name new-state)
(@queue . <~ . (λ (queue)
(for/list ([item queue])
(if (equal? (car item) target-item-name)
(cons (car item) new-state)
item)))))
;; --- INTERFACE ---
(void
(render
(window #:size '(400 300)
(list-view
@queue
#:key car
(λ (k @item)
(hpanel #:stretch '(#t #f)
;; car of the pair is the name
(text k)
(spacer)
;; cdr of the pair is the state. branch off it.
(if-view (@item . ~> . (λ (item) (eq? (cdr item) 1)))
(text "is in state one")
(text "is in state two")))))
(button "row one -> state two" (λ () (do-set-item-state "Row one" 2)))
(text "^ clicking this button shows an error in the logs ^")
(text "Same outcome when calling (do-set-item-state ...) in the REPL."))))
Issue also occurs with cond-view and case-view. Just swap if-view out for one of these replacements and you'll see the same issue. (They probably call one another internally.)
(cond-view
[(@item . ~> . (λ (item) (eq? (cdr item) 1)))
(text "is in state one")]
[else
(text "is in state two")])
(case-view (@item . ~> . cdr)
[(1) (text "is in state one")]
[else (text "is in state two")]))))
Demo:
#lang racket
(require racket/gui/easy
racket/gui/easy/operator)
(define/obs @counters '((0 . 0)
(1 . 10)
(2 . 30)))
(define (update-count counts k proc)
(for/list ([entry (in-list counts)])
(if (eq? (car entry) k)
(cons k (proc (cdr entry)))
entry)))
(define (counter @count action)
(hpanel
#:stretch '(#t #f)
(text (@count . ~> . number->string))
(button "Update" (thunk (render (dialog (slider @count action)))))))
(let ([@c (@ 0)])
(render
(window
(counter @c (λ (new-count) (:= @c new-count))))))
(render
(window
#:size '(#f 200)
(list-view
@counters
#:key car
(λ (k @entry)
(counter
(~> @entry cdr)
(λ (count)
(<~ @counters (λ (counts)
(update-count counts k (const count))))))))))
Interact with the single counter window and you'll notice the slider and the count are (effectively) perfectly sync'd.
Interact with the other window and you'll noticed that only the first mouse click in a click-and-drag on the slider updates the display. Worse, closing the dialog and opening another sometimes updates the display of a previous counter to the correct value.
Adding "debug prints" shows that the state is in fact updated correctly; it is only the display that is not updated.
This is a MRE of a problem I'm facing in another project, hence the toy counters.
A quick glance at the racket/gui docs didn't get me too far, so: what would it take to make the view in list-view
scrollable with the mouse (in addition to dragging the scrollbar)?
Is this a fundamental limitation of racket/gui that such things aren't supported automatically or even easily? Or is there a simple piece of plugin code that I can add as a mixin or even a PR here?
DrRacket's editor scrolls with the mouse, so I'm sure it must be possible. But if it's intrinsically difficult code, I can live without it.
The underlying button% handles a bitmap% argument just fine, even when it's an observable of a bitmap! Try this minimal example where I import the private button file directly, bypassing the contract:
#lang racket/base
(require racket/class
racket/function
images/icons/control
images/icons/style
images/icons/stickman
(only-in racket/gui timer%)
(except-in racket/gui/easy button)
racket/gui/easy/private/view/button
racket/gui/easy/operator)
(define/obs @ms (current-inexact-milliseconds))
(new timer%
[notify-callback (λ () (:= @ms (current-inexact-milliseconds)))]
[interval 10])
(define/obs @stickman
(@ms . ~> . (λ (ms)
(running-stickman-icon
(/ ms 1000)
#:height 32
#:head-color run-icon-color
#:arm-color "white"
#:body-color run-icon-color))))
(render
(window
#:size '(200 200)
(button @stickman (λ () (println (obs-peek @ms))))))
Your button contract should allow bitmap% too.
The original button% class can also accept another special thing that you may wish to support, see https://docs.racket-lang.org/gui/button_.html for the full contract.
It might be better to adjust the docs? Changing this might change a lot of GUI appearances without warning to people. Then again, breaking changes are allowed pre-1.0 :P
Hello! When I say something like this:
(cond-view
[@foo? (text "foo is set!")]
[else (text "no foo here!")])
then internally it will instantiate an if-view%
, which in turn calls:
(new gui:panel%
[parent parent]
[min-width #f]
[min-height #f]
[stretchable-width #t]
[stretchable-height #t]))
There's no way to inject your own arguments for this panel. That is, if you use a cond-view
(or any of the similar containers), you can't have a non-stretchable containee. Even if you have a simple 1-line label (which isn't vertically stretchable), putting it in a cond-view
causes it to become vertically stretchable, which is not what I want.
Whenever the library creates secret automatic panels, they should either mimic the layout arguments of their containees, or allow me to explicitly pass arguments for them.
Thanks!
case-view
documentation says that it uses equal?
to compare the observable against the lit, but the implementation uses memv
which is the eqv?
condition. This makes it impossible to switch on strings because they can't be compared by eqv?
.
Suggested solution: Change case-view
's implementation to use member
as the test.
Relevant code: private/view/if.rkt
line 137.
As always, thank you for your amazing work!
When I run the following program and close the dialog, I consistently get the following output, with an error message:
(object:renderer% ...)
hash-ref: no value found for key
key: (object:text% ...)
context...:
/Users/$USER/Library/Racket/8.5/pkgs/gui-easy-lib/gui/easy/private/view/container.rkt:22:4: update-children method in container%
/Applications/Racket v8.5/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:435:6
/Applications/Racket v8.5/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:486:32
/Applications/Racket v8.5/share/pkgs/gui-lib/mred/private/wx/common/queue.rkt:634:3
Program:
#lang racket
(require racket/gui/easy
racket/gui/easy/operator)
;; calls `proc` when the window closes
(define ((make-on-close-mixin proc) %)
(class %
(super-new)
(define/augment (on-close)
(proc))))
(define/obs @thing (list 1))
(render
(dialog
#:mixin (make-on-close-mixin (thunk (<~ @thing rest)))
(text (~> @thing (λ (t) (if (empty? t)
""
(~a (first t))))))))
Even more bizarrely the indicated line (22 in gui-easy-lib/gui/easy/private/view/container.rkt
) is
(for ([c (in-list (hash-ref deps-to-children what null))])
which has a default value for the hash-ref
? So perhaps this is an odd racket bug… unfortunately I cannot come up with a small hash-ref
test-case that demonstrates the same bug, and the "context" elision is useless.
The documentation mentions 'new-button
, but the implementation does not yet support it. Fortunately I can add it to my application in other ways, so perhaps a quick update to the doc is all that is needed?
As title
Hiya! This is an open-ended issue without a strict definition of completed.
I'm coding a couple of approaches to make a list of checkboxes. My example program lets people select foods they like from a list. Changes to the interface are stored in @foods
, and changes to @foods
are reflected back to the interface. My goal is to write code that looks nice without writing too much.
Hopefully the insights from this will either make me better at using gui-easy, or will help gui-easy become easier to use.
First attempt:
#lang racket
(require racket/gui/easy
racket/gui/easy/operator)
(struct food^ (name checked?) #:transparent)
(define/obs @foods `((1 . ,(food^ "Apple" #t))
(2 . ,(food^ "Banana" #f))
(3 . ,(food^ "Broccoli" #f))
(4 . ,(food^ "Ice Cream" #t))))
(obs-observe! @foods println)
(render
(window
#:size '(250 250)
(list-view
@foods
#:key car
(λ (k @food)
(define name (@food . ~> . (λ (food) (food^-name (cdr food)))))
(define checked? (@food . ~> . (λ (food) (food^-checked? (cdr food)))))
(checkbox
#:label name
#:checked? checked?
(λ (checked?)
(<~ @foods
(λ (foods)
(dict-update foods k (λ (food) (struct-copy food^ food [checked? checked?])))))))))))
;; C-x C-e this to toggle a checkbox: (@foods . <~ . (λ (foods) (dict-update foods 2 (λ (food) (struct-copy food^ food [checked? (not (food^-checked? food))])))))
(My style is to use ^ to notate struct definitions.)
Things I don't like about this:
@food
has to be extracted separately so it can be used in the checkbox. (I've encountered a similar frustration with list-view in other project.)@food
includes the key as its car
, but I already have the key in k
. If the key was excluded from @food
, I wouldn't need to cdr
, so the next line could be shortened down to (define name (@food . ~> . food^-name))
, which is much better. (Though this wouldn't matter if the prior point could be solved directly.)@food
already available, but then having to dict-update on foods
, adds more code. It would be nice if there was a shortcut to update @food
itself. (This can't be done directly because it's derived, but maybe some kind of helper...?)Second attempt:
#lang racket
(require racket/gui/easy
racket/gui/easy/operator)
(define/obs @foods `((1 . "Apple")
(2 . "Banana")
(3 . "Broccoli")
(4 . "Ice Cream")))
(define/obs @foods-checked (set 1 4))
(obs-observe! @foods-checked println)
(render
(window
#:size '(250 250)
(list-view
@foods
#:key car
(λ (k @food)
(define name (@food . ~> . cdr))
(define checked? (@foods-checked . ~> . (curryr set-member? k)))
(checkbox
#:label name
#:checked? checked?
(λ _ (@foods-checked . <~ . (curry set-symmetric-difference (set k)))))))))
;; C-x C-e to toggle some checkboxes: (@foods-checked . <~ . (λ (fc) (set-symmetric-difference fc (set 1 2))))
@foods-checked
status separately from @foods
makes it much easier to extract and update the relevant properties inside list-view.(define name (@food . ~> . cdr))
(though could be easier still if it was unpacked for me)@foods
and @foods-checked
together in order to use them fully.Overall I think there's still some room for improvement here, both in my code and in gui-easy, but I don't know what to change in order to improve it. One idea that I think has potential is if there was a version of list-view that included pattern-matching in order to unpack the list items for me. For example:
(define/obs @items '((1 "Red" "#ff0000" #t) (2 "Green" "#00ff00" #f)))
(list-view/match
@items
#:key car
[(list k name hex checked?)
(hpanel (text name #:color hex) (checkbox #:checked? checked?))]
or
(struct color^ (id name hex) #:transparent)
(define/obs @items (list (color^ 1 "Red" "#ff0000" #t) (color^ 2 "Green" "#00ff00" #f)))
(list-view/match
@items
#:key color^-id
[(color^ k name hex checked?)
(hpanel (text name #:color hex) (checkbox #:checked? checked?))]
But that's just a theory. Do you have any thoughts or ideas on my long-winded post?
As always, keep up the great work :)
Hello,
is it possible to build a grid layout / panel with racket-gui-easy? I looked at the documentation and examples. But it seems there is currently no grid panel available. The only options seems to rap https://docs.racket-lang.org/table-panel/index.html in a custom view. Anyways, I would preview a native solution.
If there currently is no grid panel in this library, I would like to request one as a feature.
Seems to be supported by racket/gui: https://docs.racket-lang.org/gui/checkable-menu-item_.html
I imagine the API would look something like:
(define/obs @high-contrast? #f)
(menu "View"
(menu-item "High Contrast"
(λ (checked) (:= @high-contrast? checked)) ; <-- adding a checked parameter
#:checked @high-contrast? ; <-- #:checked observable
#:enabled? #t))
The callback action could do anything (but most likely be used to set an observable), and likewise, updating the #:checked observable would update the visible state of the menu item.
The function could be named menu-item
or checkable-menu-item
, but I think it's cuter to have checkable and non-checkable items both be called menu-item
because then all the menu labels will align in the source code:
(menu-item "Debug..." ; <-- the left edge of this string...
(λ () (show-debug-window)))
(menu-item "High Contrast" ; <-- ...lines up with the left edge of this string!
(λ (checked) (:= @high-contrast? checked))
#:checked @high-contrast?)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.