Racket/gui fullscreen and parameters

I have a simple program like:

#lang racket/gui

(define current-width (make-parameter #f))

(define (run-fullscreen proc)
  (define window
    (new
     (class frame%
       (super-new)
       (define/override (on-size new-width new-height)
         (current-width new-width)
         (writeln (list new-width new-height))))
     (label "Test")))
  (send window fullscreen #t)
  (send window show #t)
  (define main-thread (thread proc))
  (thread-wait main-thread)
  (send window show #f))

(displayln (current-width))
(run-fullscreen
 (thunk
  (displayln (current-width))
  (sleep 1)
  (displayln (current-width))
  (displayln "In run-fullscreen.")))

Running it produces:

#f
#f
#f
In run-fullscreen.
(1 1)
(2160 1350)

Yes, without parameterized eventspace, the GUI thread does not have a chance to update the parameter, but with a small change, it (in theory) should do the trick:

#lang racket/gui

(define current-width (make-parameter #f))

(define (run-fullscreen proc)
  (parameterize ((current-eventspace (make-eventspace)))
    (define window
      (new
       (class frame%
         (super-new)
         (define/override (on-size new-width new-height)
           (current-width new-width)
           (writeln (list new-width new-height))))
       (label "Test")))
    (send window fullscreen #t)
    (send window show #t)
    (define main-thread (thread proc))
    (thread-wait main-thread)
    (send window show #f)))

(displayln (current-width))
(run-fullscreen
 (thunk
  (displayln (current-width))
  (sleep 1)
  (displayln (current-width))
  (displayln "In run-fullscreen.")))

But it doesn't:

#f
#f
(1 1)
(2160 1350)
#f
In run-fullscreen.

Should I abandon parameters altogether in this case or do I overlook something basic?
The code shown is just a minimal example. In the real application the frame% also contains one and only child canvas% instance.
I want to be able to set/update some values in the GUI initialization, some in some GUI callbacks (on-size of frame%, on-char and on-event of canvas%) and the proc being run (mainly the canvas - immediate and only frame% child in the real application).

1 Like

In your first example, the GUI thread is the same as the main thread, so your code reads the same parameter value that would be set by the callback. GUI callbacks are executed only when the thread is idle and this happens after run-fullscreen returns, which explains why the program prints "In run-full-screen" before showing the screen dimensions.

When you create a new event space a different thread will be created by the event space, and the parameter values are copied for the new thread, so in your second example, the frame%/on-size method now modifies a copy of the parameter.

It is not clear to me what you want to do, so I cannot offer any real suggestions, but your first example would be "fixed" by adding a (sleep/yield 0.1) immediately after (send window show #t). In the second example, you will need a different mechanism to pass the value between two distinct threads, since parameters are not going to work for that purpose.

EDIT: I just noticed that your code creates a new thread to display the full screen message, so in the first example, a parameter copy would still be created, but sleep/yield will ensure that the parameter will have the value you want before copying it.

Alex.

1 Like

Thank you @alexh!
The sleep/yield really solves the core of the issue as the GUI gets chance to set everything up just before I start setting up whatever depends on that, but still the parameters are a bit tricky to get them right. Basically I opted for parameterize'ing them just before creating new eventspace and included #:mutable fields that are shared between multiple thread cells. Not a nice - but still a working solution.

I used sleep/yield as an illustration only, and I advise against using it.

The problem is that there is no way to know how much time to wait -- for a simple example like that, 0.1 works most of the time, but it might not be enough once you start creating other UI elements. For example, if I tried to do that in my GUI application, I would need to sleep about 8 seconds.

As I mentioned in my original message, I cannot offer an alternative solution without knowing what you are trying to do. In your original example, if you want to print out the size of the window to the terminal, it is more correct to just printf the values in the on-size method call. Other use cases would require other approaches.

Alex.

Currently I just have the width and height hardcoded as parameters to frame% and I think that may cause the frame to have the correct size faster. In the future I may try to replace the hardcoded values (that only work on my computer) with values derived from a call to (get-display-size #t #:monitor 0) get-display-size.

Currently I construct the frame with width and height then I use:

(send frame move screen-0-width 0)
(send frame fullscreen #t)

To place the window on the second screen and fullscreen it, before calling (send frame show #t). I think doing these calls before the first show might cause less dynamic resize events that need to be handled. (but I haven't done comprehensive testing of this)

Although I think that may get you a window of the desired size faster, you maybe still need a strategy for handling on-size events for example if you want to turn off fullscreen later or it seems to be needed for switching the window to other screens. From my experiments it seems that requires switching out of fullscreen, moving the window and activating fullscreen again.

From my experiments (get-display-size #t #:monitor 1) gives me the correct display size, I don't know why the documentation specifically mentions monitor 0 as if it is a requirement to get the correct size with fullscreen #t. Maybe the description is just misleading.
Currently both my screens have the same size, so further testing may be required.

get-display-left-top-inset and get-display-count may be the remaining pieces to get the top-left corner for moving the window to the screen and get the number of screens respectively.

The documentation also mentions display-changed might may sense to use that to react to plugging in a new display or removing one. For example move the window to a new screen if the one it was on was removed.

I actually changed some of the hardcoded stuff, I now use this to create a fullscreen window on the second monitor:

(define-values (mx my) (get-display-left-top-inset #f #:monitor 1))
(define-values (w h) (get-display-size #t #:monitor 1))
(define x (- mx))
(define y (- my))
(displayln (~a "x: " x))
(displayln (~a "y: " y))
(new frame% [label "example"] [x x] [y y] [width w] [height h])
(send frame fullscreen #t)
(send frame show #t)

About the parameters, to me it seems like they aren't particular useful for global mutable settings like this, maybe mutable fields (maybe in conjunction with a semaphore to make updates look atomic or some other synchronization mechanism) are the simplest solution here.

For runtime changes (e.g. monitor disappears) you need to propagate the change to all threads that are dependent on this change, for some threads it may be enough if the updated fields are just accessed as new values, but for others some things may be calculated based on sizes (e.g. some screen sized texture buffer etc.) those probably need to either get some explicit update call or receive some signal that informs them to update themselves.

functional updates

In some ways this is probably similar to the Observables used in gui-easy? However I haven't looked at their implementation and there are many different ways to do these things depending on circumstances, another common one seems to be to use some kind of dirty-flag (in more imperative code).

If you want to get back to code that is more functional you maybe want to feed these changes into your update function I think taking a look at elm and how it handles effects may be helpful here. Commands and Subscriptions

I also find FrTime: A Language for Reactive Programs interesting.
And also this paper Push-pull functional reactive programming (however it has been a while since I have read it, time for a re-read)