Open Filipp-Druan opened 1 year ago
I understand what you mean. You're saying that the make-instance
style constructor allows us to directly pass object properties during construction, right? However, GTK classes often have their own constructors, such as new
, new_from_file
, new_from_gicon
, and so on. I prefer not to mix properties with the parameters of these constructors, so currently, I'm using the defstruct
style constructor.
If we want to achieve declarative UI, I believe we should use separate macros, just like in Relm4, instead of mixing them with the imperative API.
I looked at the examples on Reml4, I like the view! macro, would it be difficult to write something like this? How long will it take? Unfortunately, I won't be able to write code for the library myself.
I looked at the examples on Reml4, I like the view! macro, would it be difficult to write something like this? How long will it take? Unfortunately, I won't be able to write code for the library myself.
Implementing macros in Lisp is always easier than in other languages. I might work on it after I finish dealing with my job hunting. Of course, you are also welcome to try implementing it yourself and submit a pull request.
By any chance, are you looking for a declarative menu?
I'm guess, what I can write view!-like macro myself, but I'm afraid, what I'll write a bad variant, and nobody more competent won't write better interface because my already is.
I'm guess, what I can write view!-like macro myself, but I'm afraid, what I'll write a bad variant, and nobody more competent won't write better interface because my already is.
Don't worry. You can try implementing your own version first, and I believe you will learn a lot of Lisp techniques in the process. After I finish my busy period, I will also implement one, and then we can discuss the strengths and weaknesses of both and learn from each other.
I have written a simple declarative macro for defining UIs with cl-gtk4, i'm not really good with macros or anything and i haven't done exhaustive testing, but i've been using it successfully in my projects, so i thought I might share it here.
The code is here: https://codeberg.org/seigakaku/gtk4-defui
Here's the fibonacci example adapted to use it:
(define-interface fibonacci
(container box (make-box :orientation +orientation-vertical+ :spacing 4)
(label label (make-label :str "0")
:properties (:hexpand t :vexpand t))
(nil box (make-box :orientation +orientation-horizontal+ :spacing 4)
:properties (:hexpand t :halign +align-center+)
(nil label (make-label :str "n: "))
(nil entry (make-entry)
:init (lambda (entry)
(setf (entry-buffer-text (entry-buffer entry)) (format nil "~A" n)))
:properties (:hexpand t :halign +align-fill+)
:connect (("changed" (lambda (entry)
(setf n (ignore-errors (parse-integer
(entry-buffer-text
(entry-buffer entry))))))))))
(nil button (make-button :label "Calculate")
:connect (("clicked" (lambda (button)
(bt:make-thread
(lambda ()
(when n
(run-in-main-event-loop ()
(setf (button-label button) "Calculating..."
(widget-sensitive-p button) nil))
(let ((result (fib n)))
(run-in-main-event-loop ()
(setf (label-text label) (format nil "~A" result)
(button-label button) "Calculate"
(widget-sensitive-p button) t)))))))))))
(n :type (or null fixnum) :initform 40))
(define-application (:name fibonacci :id "org.bohonghuang.gtk4-example.fibonacci")
(defun fib (n)
(if (<= n 2) 1 (+ (fib (- n 1)) (fib (- n 2)))))
(define-main-window (window (make-application-window :application *application*))
(setf (window-title window) "Fibonacci Calculator")
(let* ((fibonacci (make-instance 'fibonacci))
(container (fibonacci-container fibonacci)))
(setf (window-child window) container)
(unless (widget-visible-p window)
(window-present window)))))
Thanks!
Hello! Do you have some progress in declarativety?
There is a good example: https://docs.racket-lang.org/gui-easy/index.html
This is a draft I completed earlier:
(ql:quickload :symbol-munger)
(ql:quickload :trivial-arguments)
(in-package #:gtk4.example)
(defun expand-gui-definition (name fields &aux (package (symbol-package name)))
(alexandria:when-let* ((name-space-symbol (find-symbol (symbol-name '#:*ns*) package))
(gir-class (gir:nget (symbol-value name-space-symbol) (symbol-munger:lisp->studly-caps name)))
(constructor (find-symbol (format nil "~A-~A" '#:make name) package)))
(alexandria:with-gensyms (instance)
`(let ((,instance (,constructor . ,(loop :with keywords := (mapcar #'caar (nth-value 3 (alexandria:parse-ordinary-lambda-list
(trivial-arguments:arglist constructor))))
:for (key value) :on fields :by #'cddr
:when (member key keywords)
:nconc (list key value)))))
,@(loop :for (key value) :on fields :by #'cddr
:nconc (loop :for class := gir-class :then (gir:parent-of class)
:for symbol := (when class
(find-symbol
(format
nil "~A-~A"
(symbol-munger:camel-case->lisp-name
(gir:info-get-name (gir::info-of class))
:capitalize t)
key)
package))
:for setf-function := (ignore-errors (fdefinition `(setf ,symbol)))
:for function := (ignore-errors (fdefinition symbol))
:for value-form := (or (and (listp value) (expand-gui-definition (car value) (cdr value))) value)
:while class
:when setf-function
:return (list `(setf (,symbol ,instance) ,value-form))
:when (and function (= (length (trivial-arguments:arglist function)) 2))
:return (list `(,symbol ,instance ,value-form))))
,instance))))
(defmacro gui ((name &body body))
(expand-gui-definition name body))
(define-application (:name simple-counter
:id "org.bohonghuang.gtk4-example.simple-counter")
(define-main-window (window (make-application-window :application *application*))
(setf (window-title window) "Simple Counter"
(window-child window) (gui (box
:orientation +orientation-vertical+
:spacing 4
:append (label :str "0"
:hexpand-p t
:vexpand-p t)
:append (button :label "Add"))))
(unless (widget-visible-p window)
(window-present window))))
Perhaps you can continue to improve it.
Hello! I'm comparing application code on your library and on CL-CFFI-GTK4. The second library provides a more declarative interface. For example:
Your code, sorry for the directness, merges into one whole when you look at it. It is very difficult to distinguish one part from another. I wish the code was more like a tree:
So you can immediately see what is responsible for what. Best wishes Filipp