rabbibotton / clog

CLOG - The Common Lisp Omnificent GUI
Other
1.48k stars 101 forks source link

Review: Numpad Component #218

Closed ghost closed 1 year ago

ghost commented 1 year ago

Hi @rabbibotton, at your suggestion I am sharing a component that builds an on-screen numpad. I learnt enough about Clog and Javascript to get it working but am unsure if the approach (dispatching keyboard events via execute) is a good one. All feedback welcome :D

(cl:defpackage #:clog-numpad-test
  (:use #:common-lisp #:clog))

(cl:in-package #:clog-numpad-test)

(defun create-numpad
    (parent
     &key
       (numpad-key-class "bg-near-black shadow-3 white grow pointer w3 h3 br3 flex items-center justify-center")
       (listener (error "Must supply clog-obj listener to route numpad events")))
  (with-clog-create parent
      (div (:class "w-100 pa2 flex flex-column f2 tc code")
           (div (:class "w-100 flex justify-center")
                (div (:class "pa2")
                     (div (:bind numpad-7
                            :class numpad-key-class)
                          (span (:content "7"))))
                (div (:class "pa2")
                     (div (:bind numpad-8
                            :class numpad-key-class)
                          (span (:content "8"))))
                (div (:class "pa2")
                     (div (:bind numpad-9
                            :class numpad-key-class)
                          (span (:content "9")))))
           (div (:class "w-100 flex justify-center")
                (div (:class "pa2")
                     (div (:bind numpad-4
                            :class numpad-key-class)
                          (span (:content "4"))))
                (div (:class "pa2")
                     (div (:bind numpad-5
                            :class numpad-key-class)
                          (span (:content "5"))))
                (div (:class "pa2")
                     (div (:bind numpad-6
                            :class numpad-key-class)
                          (span (:content "6")))))
           (div (:class "w-100 flex justify-center")
                (div (:class "pa2")
                     (div (:bind numpad-1
                            :class numpad-key-class)
                          (span (:content "1"))))
                (div (:class "pa2")
                     (div (:bind numpad-2
                            :class numpad-key-class)
                          (span (:content "2"))))
                (div (:class "pa2")
                     (div (:bind numpad-3
                            :class numpad-key-class)
                          (span (:content "3")))))
           (div (:class "w-100 flex justify-center")
                (div (:class "pa2")
                     (div (:bind numpad-0
                            :class numpad-key-class)
                          (span (:content "0"))))))
    ;; Fire keyboard event when numpad key is clicked
    (loop for numpad-key in (list numpad-0 numpad-1 numpad-2 numpad-3 numpad-4 numpad-5 numpad-6 numpad-7 numpad-8 numpad-9)
          for i from 48
          do (set-on-click numpad-key
                           (lambda (obj)
                             (execute listener
                                      (format nil "dispatchEvent(new KeyboardEvent('keypress', {'key': '~A', 'code': '~A', 'charCode': '~A'}))"
                                              (text (first-child obj)) i i)))))))

(defun on-new-window (body)
  (setf (title (html-document body)) "Numpad test")
  (load-css (html-document body) "https://unpkg.com/tachyons@4.12.0/css/tachyons.min.css")
  (add-class (body-element (html-document body)) "bg-near-white near-black")
  (with-connection-cache (body)
    (with-clog-create body
        (div (:bind main-container
               :class "mw5 mt5 center sans-serif")
             (div (:class "w-100 h4 flex flex-column justify-center items-center f1")
                  (div (:bind display
                         :class "code bb bw1"
                         :content " ")))
             (numpad (:listener display)))

      ;; Register update on numpad event
      (set-on-key-press
       display
       (lambda (obj data)
         (let ((key (getf data :key)))
           (setf (text obj) key))))

      ;; Also allow update on display when physical key pressed
      (set-on-key-press
       body
       (lambda (obj data)
         (declare (ignore obj))
         (let ((key-code (getf data :key-code))
               (key (getf data :key)))
           (when (<= 48 key-code 57)
             (setf (text display) key))))))))

(defun launch (&key (host "0.0.0.0"))
  (clog:initialize 'on-new-window :host host)
  (clog:open-browser))
rabbibotton commented 1 year ago

Would be better to use a custom event - to fire:

                             (jquery-execute listener (format nil "trigger('my-event', ~A)"
                                                              (text (first-child obj)))))))))

To catch:

      (set-on-event-with-data display "my-event"
                              (lambda (obj data)
                                (setf (text obj) data)))

I would not have done this at all as why not pass the message on the server side? The listener (bound to display) is already passed to your create-numpad, just directly set the value (setf (text-obj listener) (text-obj obj)) or pass a function if want to decouple it more - (numpad (:listener (lambda (value) (setf (text-obj display) value))))

ghost commented 1 year ago

Ahh okay, yeah it would be simpler to just do it server side. I couldn't really see the forest for the trees so I just went with the first thing that I could think of (adding a listener was a late addition too, before the keyboard events were just being triggered on the document body). It should be more responsive that way too as there wont be multiple round trips occurring. Thanks

ghost commented 1 year ago

Also, if I wanted to make the client interface as responsive as possible would I need to ship JavaScript over the wire to register the on-click event and update the necessary elements client-side? If so it would be great to be able to compile common lisp to JavaScript/Web Assembly or something to eliminate the need to learn and write JavaScript, but I think that may be outside Clog's scope at the moment (and no doubt a large undertaking at that).

rabbibotton commented 1 year ago

In truth you rarely ever need to know anything about javascript with CLOG. The approach I would have taken for the same app would have from the start not have included any thing non-lisp. You can use parascript with CLOG (in the discussions on github there is an example how that is done), that compiles Lisp to JS.

I personally use CLOG in general more like a GUI toolkit than a web framework. I'll see if I can put together soon how I would have done the same thing from scratch, not to say one should or that it is the best way, but to show an alternative that may help you thinking in a different direction also.

ghost commented 1 year ago

Oh right! I've heard about Parenscript, but it slipped my mind.

Sure, a demo would be greatly appreciated, only if you're not too busy already! :D

rabbibotton commented 1 year ago

This is a simple rewrite more the way I generally approach things. Like I said, not saying is right or wrong or better, and is not per se even that much different, but to me simpler. (I didn't spend too much time on looks, yours is cooler for sure)

(defun my-page (body)
  (clog-web:clog-web-initialize body)
  (add-class body "w3-dark-grey")  
  (let* ((calc         (create-div body :class "w3-monospace w3-xxlarge w3-center"))
         (display      (create-div calc :class "w3-center" :content "0"))  
         (button-class "w3-border w3-margin w3-padding w3-ripple"))
    (labels ((on-click (obj)
               (setf (text display) (text obj)))
             (on-key (obj data)
               (let ((key-code (getf data :key-code))
                     (key      (getf data :key)))
                 (when (<= 48 key-code 57)
                   (setf (text display) key)))))
      (set-on-key-press body #'on-key)
      (dotimes (row 3)
        (dotimes (col 3)
          (let* ((loc (+ (+ (* row 3) col) 1))
                 (key (create-span calc :content loc :class button-class)))
            (set-on-click key #'on-click)))
        (create-br calc))
      (create-span calc :content "&nbsp;" :class button-class)
      (set-on-click (create-span calc :content 0 :class button-class) #'on-click)
      (create-span calc :content "&nbsp;" :class button-class))))
ghost commented 1 year ago

Fantastic, thanks! I have a much better idea about where Clog sits now too. I misunderstood Clog because of the use of web technologies I automatically thought 'websites/web-apps' when, like you said, it's better to think 'GUI toolkit'. Not to say that Clog can't be used to make the former but due to Clog being server-side naturally there are challenges to overcome when there is a medium to high latency link between the client and server. Anyway, I just want to say that I really like Clog and I appreciate you porting it to Common Lisp. I've honestly never had a more enjoyable time constructing GUI's than with Clog :D

rabbibotton commented 1 year ago

It only gets better :)

I look forward to seeing the amazing things you create!