bohonghuang / cl-gtk4

GTK4/Libadwaita/WebKit2 bindings for Common Lisp.
GNU Lesser General Public License v3.0
217 stars 9 forks source link

cl-gtk4/example:simple Doesn't Run Well #5

Closed dansommers closed 1 year ago

dansommers commented 1 year ago

I started with my own toy application, a clock. I get a window, the label widget, and initial content, but I can't update the label. Sort of. The code executes, but the window doesn't change, unless I resize it or maximize it, or somehow convince GTK4 to redraw it. Simply hiding it behind another window and re-exposing it doesn't work, nor does minimizing it and un-minimizing it. The tooltip works (and shows the progression of time), presumably because it's reconstructed and drawn fresh every time rather than updated.

Then I tried running cl-gtk4/example:simple, and it does the same thing: the application runs, but the window doesn't update until I do something with the window manager to force a redraw.

Same thing from a shell or slime.

SBCL 2.1.11, Arch Linux, jwm window manager, gtk4 4.8.3, 2022-11-07 quicklisp distribution.

I used quicklisp to install cl-glib and cl-gobject-introspection-wrapper; and cloned cl-gtk4 into my local projects directory.

What other information can I provide?

bohonghuang commented 1 year ago

Then I tried running cl-gtk4/example:simple, and it does the same thing: the application runs, but the window doesn't update until I do something with the window manager to force a redraw.

Can you provide some more specific information on this issue? The label didn't get updated when the "Add" button was clicked, right?

dansommers commented 1 year ago

On 2023-01-02 at 07:37:20 -0800, bohonghuang @.***> wrote:

Then I tried running cl-gtk4/example:simple, and it does the same thing: the application runs, but the window doesn't update until I do something with the window manager to force a redraw.

Can you provide some more specific information on this issue? The label didn't get updated when the "Add" button is clicked, right?

Yes, exactly. When I click the Add button, nothing happens. When I resize the window, then the label changes.

bohonghuang commented 1 year ago

Yes, exactly. When I click the Add button, nothing happens. When I resize the window, then the label changes.

Hmm... It doesn't seem to be a problem on the Lisp side. Can you install gtk4-demos or other GTK4-based applications to check for that?

dansommers commented 1 year ago

On 2023-01-02 at 07:51:23 -0800, bohonghuang @.***> wrote:

Yes, exactly. When I click the Add button, nothing happens. When I resize the window, then the label changes.

Hmm... It doesn't seem to be a Lisp problem. Can you install gtk4-demos or other GTK4-based applications to check for that?

Wow. You're right. gtk-demo doesn't update until I resize its window, either.

FWIW, I had enough trouble with iwgtk that I abandoned it in favor of iwctl. When gtk4 4.8.3, iwgtk worked much better, so I tried cl-gtk4.

I won't make that mistake again. :-)

Thank you for your quick help.

dansommers commented 1 year ago

On 2023-01-02 at 10:57:25 -0500, @.*** wrote:

FWIW, I had enough trouble with iwgtk that I abandoned it in favor of iwctl. When gtk4 4.8.3, iwgtk worked much better, so I tried cl-gtk4.

I won't make that mistake again. :-)

To clarify: this isn't a problem with cl-gtk4, there seems to be something going on with gtk4 itself, iwgtk notwithstanding.

bigos commented 1 year ago

https://github.com/bigos/Pyrulis/blob/master/Lisp/cl-gtk4.lisp#L135

you need to add the widget to the redraw queue https://docs.gtk.org/gtk4/method.Widget.queue_draw.html

dansommers commented 1 year ago

On 2023-01-02 at 11:38:41 -0800, Jacek Podkanski @.***> wrote:

https://github.com/bigos/Pyrulis/blob/master/Lisp/cl-gtk4.lisp#L135

you need to add the widget to the redraw queue https://docs.gtk.org/gtk4/method.Widget.queue_draw.html

Thanks, Jacek.

Who is "you"?

Is that something an application has to do every time it updates a widget, or is that something cl-gtk4 should be doing "automatically"?

I added that call, in several variations (with-main-loop, add-idle, directly in the function that updates my widget, on the widget, on the entire window), and none worked.

I've written a handful of gtk2 and gtk3 applications in Python (the language, not the Lisp compiler), and I didn't have to do that. Do you (Jacek) have a reference to more documentation regarding when that function should be called? I see a few recommendations to call it, and the bare bones API reference, but I can't find (quickly) any sort of how-to or guide type documentation about it.

bigos commented 1 year ago

The "you" in your question is the one who can not understand why Gtk4 does not redraw without the resize.

It appears the application can guess you need a redraw on resize. But in other cases, you need to tell GTK4 you want a redraw. Here I tell gtk4 to redraw a widget called canvas. You may want to tell gtk4 to redraw the window and then experiment. (widget-queue-draw canvas)

I can only guess what you have done wrong without seeing your code. In my previous response, I provided the link to a working example.

dansommers commented 1 year ago

On 2023-01-02 at 14:58:38 -0800, Jacek Podkanski @.***> wrote:

The "you" in your question is the one who can not understand why Gtk4 does not redraw without the resize.

I also cannot understand why other applications (iwgtk, gtk-demos, the cl-gtk4 example) fail in exactly the same way mine does, or why none of the tutorials I've found address gtk_widget_queue_draw until they get to custom drawing areas like canvas and cairo.

I suppose it could be an issue with my computing environment, but that seems less likely right now than a GTK4 issue.

I can only guess what you have done wrong without seeing your code. In my previous response, I provided the link to a working example.

I agree; here's an SSCCE, although you'll have to have cl-glib and cl-gtk4 loaded. (I note that your code uses a canvas (which the documentation say is subject to widget-queue-draw) rather than "plain" GTK4 widgets.)

(defpackage #:clock (:use #:cl #:gtk4))

(in-package #:clock)

(defun clock-update (clock) (let* ((universal-time (get-universal-time)) (decoded (multiple-value-list (decode-universal-time universal-time)))) (setf (label-text clock) (princ-to-string universal-time)) (setf (widget-tooltip-text clock) (princ-to-string decoded))))

(defun main () (let ((app (make-application :application-id "zz.yy.xx" :flags gio:+application-flags-default-flags+))) (connect app "activate" (lambda (app) (let ((window (make-application-window :application app)) (clock (make-label :str "Easter Egg!"))) (setf (window-child window) clock) (window-present window) (glib:timeout-add 999 (lambda () (clock-update clock)))))) (gio:application-run app nil)))

Now I'm getting mixed results: sometimes I see a few updates, sometimes it skips an update and then resumes, sometimes I see no updates at all. The tooltip works. If I trace clock-update, I can see it running. I can't see a difference with or without calling (widget-queue-draw clock) in clock-update.

I tried adding hexpand and vexpand to the clock widget, and adding a box between the window and the widget, but neither made a difference.

It might have something to do with the width of the text; the updates seem to stop when the string ends in 6 or 0 (because 7 and 1 are often narrower?). FWIW, if I make that Easter Egg much wider (e.g., a string of 20 or so percent signs), I don't see any updates at all.

bigos commented 1 year ago

Before looking at your code

I also cannot understand why other applications (iwgtk, gtk-demos, the cl-gtk4 example) fail in exactly the same way mine does, or why none of the tutorials I've found address gtk_widget_queue_draw until they get to custom drawing areas like canvas and cairo.

Possibly, because they copy one another and a single person is the author. The best tutorials I have seen were those made by a pair. An experienced programmer working with a beginner

I suppose it could be an issue with my computing environment, but that seems less likely right now than a GTK4 issue.

You may need to look at the documentation of the events. Some events, as you seen in resize trigger redraws automatically.

bigos commented 1 year ago

For some reason, your glib:timeout-add calls only once. I need to look into it.

bigos commented 1 year ago

You had a problem witth the return value of timeout-add function. And the priority does not seem to be needed in modern GTK4.

                 (glib:timeout-add 1000
                                   (lambda (&rest args) (format t "calling update clock ~S~%" args)
                                     (clock-update clock) (widget-queue-draw clock)
                                     ;; the documentation says: https://docs.gtk.org/glib/func.timeout_add.html
                                     ;; The given function is called repeatedly
                                     ;; until it returns G_SOURCE_REMOVE or
                                     ;; FALSE, at which point the timeout is
                                     ;; automatically destroyed and the function
                                     ;; will not be called again.
                                     glib:+source-continue+))
dansommers commented 1 year ago

On 2023-01-03 at 11:20:27 -0800, Jacek Podkanski @.***> wrote:

you had problem witth the return value of timeout function

Yes I did. :-) I missed that detail earlier; that one is on me. My apologies, and good job spotting it. That's fixed now, but I'm still getting odd results.

With (trace clock-update) on, and noticing the tooltip, I can see that my code is running as intended, but that the visible updates skip certain numbers, and then inevitably stop when the final digit in the clock label is a zero. And then I get one more update when the final two digits are both zeros.

Okay, so I changed the timeout from 999 to somehting much smaller and random, removed the tooltip, changed get-universal-time to get-internal-real-time, made the intiial text smaller, and now I see the pattern: the window only gets larger, and there's no updates when the new text is narrower than the widest text. It's more noticeable with the default font (Cantarell 11) than with a font where all the digits are pretty much the same width (I had been using DejaVu Serif Condensed).

I tried adding a box in between the window and the clock, and calling hexpand and vexpand on the clock, but that didn't make a difference.

That code looks like this, and fails in exactly the same way even if I call (widget-queue-draw clock), (widget-queu-draw box), (widget-queu-draw window), or all three inside the timeout lambda.

(defpackage #:clock (:use #:cl #:gtk4))

(in-package #:clock)

(defun clock-update (clock) (let* ((irt (get-internal-real-time))) (setf (label-text clock) (princ-to-string irt)) irt))

;; (trace clock-update)

(defun main () (let ((app (make-application :application-id "zz.yy.xx" :flags gio:+application-flags-default-flags+))) (connect app "activate" (lambda (app) (let ((window (make-application-window :application app)) (clock (make-label :str "Easter!"))) (setf (window-child window) clock) (window-present window) (glib:timeout-add (random 100) (lambda () (clock-update clock) ;; (widget-queue-draw clock) ;; (widget-queue-draw box) ;; (widget-queue-draw window) t))))) (gio:application-run app nil)))

bigos commented 1 year ago

I will answer tomorrow.

dansommers commented 1 year ago

The following code shows the issue I described (that the window only grows, and never shrinks) more clearly:

(defpackage #:clock (:use #:cl #:gtk4))

(in-package #:clock)

(defvar x 2)

(defun clock-update (clock) (incf x (- 2 (random 4))) (setf (label-text clock) (make-string x :initial-element #.)) (format t " ~:W" x) (finish-output))

(defun main () (let ((app (make-application :application-id "zz.yy.xx" :flags gio:+application-flags-default-flags+))) (connect app "activate" (lambda (app) (let ((window (make-application-window :application app)) (clock (make-label :str "!"))) (setf (window-child window) clock) (window-present window) (glib:timeout-add 400 (lambda () (clock-update clock) T))))) (gio:application-run app nil)))

The more I look around, the more this seems like a GTK4 issue rather than a cl-gtk4 issue. Thank you (all of you) for your help.

bigos commented 1 year ago

There are 2 problems. Your code would not shink x. Possibly Gtk4 will not shrink the window programmatically. I may be wrong, but I could not find a way to do it.

dansommers commented 1 year ago

The issues with my code notwithstanding (thank you again for your patience, indulgence, and assistance), both examples at https://github.com/bohonghuang/cl-gtk4/blob/master/examples/gtk4.lisp exhibit the same symptom on my system; i.e., in "simple," the window comes up, and I can click the Add button, but the counter in the window doesn't update unless/until I resize the window (larger or smaller).

I tried all the programs under evilwm, and got the same results. Something is getting lost between calling (setf (label-text ...) ...) and the on-screen image, and given that gtk-demos (which (1) is not written in Lisp, and (2) uses Client Side Decorations) fails the same way, it seems to me to be somewhere in Gtk4.

On 2023-01-04 at 07:10:00 -0800, Jacek Podkanski @.***> wrote:

There are 2 problems. Your code would not shink x. Possibly Gtk4 will not shrink the window programmatically. I may be wrong, but I could not find a way to do it.

When (random 4) evaluates to 3, then my code decrements x by 1.

I agree, Gtk4 does not shrink the window. In that light, the following code adjusts the margin of the widget, and demonstrates that the clock only updates when the margins grow beyond their previous collective maximum. At this time, the window (as opposed to the widget) seems to be a red herring.

(defpackage #:clock (:use #:cl #:gtk4))

(in-package #:clock)

(defvar x 2) (defvar y 2)

(defun clock-update (clock) (incf x (- 2 (random 4))) (incf y (- 2 (random 4))) (setf (label-text clock) (princ-to-string (get-universal-time))) (setf (widget-margin-bottom clock) x) (setf (widget-margin-end clock) y) (print (list x y)) (finish-output))

(defun main () (let ((app (make-application :application-id "zz.yy.xx" :flags gio:+application-flags-default-flags+))) (connect app "activate" (lambda (app) (let ((window (make-application-window :application app)) (clock (make-label :str "!"))) (setf (window-child window) clock) (window-present window) (glib:timeout-add 600 (lambda () (clock-update clock) T))))) (gio:application-run app nil)))

bigos commented 1 year ago

I do not understand you. I have tried your code and after minimal changes I got it working. What you say does not make sense without a discussion with screen-sharing. Do you have any suggestions?

dansommers commented 1 year ago

On 2023-01-05 at 05:06:25 -0800, Jacek Podkanski @.***> wrote:

I do not understand you. I have tried your code and after minimal changes I got it working. What you say does not make sense without a discussion with screen-sharing. Do you have any suggestions?

Will you please post those change(s)? My immediately previous code (updating the margins from x and y), with or without a call to (widget-queue-draw clock) in the timeout, does not update the window every cycle. Changing the timeout from 400ms to 999ms makes that easier to see.

Does the latest version of gtk4.example:simple work out of the box for you? It fails on my system, with exactly the same symptoms. What about gtk4-demos?

At the risk of repeating myself, I believe that what I'm seeing is either a bug in Gtk4, a bug in something else in my system, or some weird interaction between Gtk4 and something else in my system. I will run some more experiments (other language bindings, a virtual machine), and come back when I have something more definitive (positive or negative).

If you consider this issue against cl-gtk4 closed, I understand.

If applications should be calling wiget-queue-draw, will you update the examples at https://github.com/bohonghuang/cl-gtk4/blob/master/examples/gtk4.lisp?

Thanks.

bigos commented 1 year ago

Please note different application flags.

;;; clock2
(eval-when (:compile-toplevel :load-toplevel :execute)
  (ql:quickload '(cl-gtk4
                  ;; cl-gdk4
                  cl-glib
                  ;; cl-cairo2
                  ;; serapeum
                  ;; defclass-std
                  ;; fiveam
                  )))

(defpackage #:clock
  (:use #:cl #:gtk4))

;; (load "~/AAA/clock2.lisp")
(in-package #:clock)

(defvar *x* 2)
(defvar *y* 2)

(defun clock-update (clock)
  (incf *x* (- 2 (random 4)))
  (incf *y* (- 2 (random 4)))
  (setf (label-text clock) (princ-to-string (get-universal-time)))
  (setf (widget-margin-bottom clock) *x*)
  (setf (widget-margin-end clock) *y*)
  (print (list *x* *y*))
  (finish-output))

(defun main ()
  (let ((app (make-application :application-id "zz.yy.xx"
                               :flags gio:+application-flags-flags-none+)))
    (connect app "activate"
             (lambda (app)
               (let ((window (make-application-window :application app))
                     (clock (make-label :str "!")))
                 (setf (window-child window) clock)
                 (window-present window)
                 (glib:timeout-add 600 (lambda ()
                                         (clock-update clock)
                                         T)))))
    (gio:application-run app nil)))