guile-scene/scene.scm
2024-10-11 21:35:51 -04:00

128 lines
4.6 KiB
Scheme

(use-modules (reactive)
(ice-9 match)
(srfi srfi-9)
(hoot ffi))
(define-foreign get-element-by-id
"document" "getElementById"
(ref string) -> (ref null extern))
(define-foreign document-body
"document" "body"
-> (ref null extern))
(define-foreign make-text-node
"document" "createTextNode"
(ref string) -> (ref null extern))
(define-foreign make-element
"document" "createElement"
(ref string) -> (ref null extern))
(define-foreign element-inner-text
"element" "innerText"
(ref null extern) -> (ref string))
(define-foreign set-element-inner-text!
"element" "setInnerText"
(ref null extern) (ref string) -> none)
(define-foreign add-event-listener!
"element" "addEventListener"
(ref null extern) (ref string) (ref null extern) -> none)
(define-foreign append-child!
"element" "appendChild"
(ref null extern) (ref null extern) -> (ref null extern))
(define-foreign element-parent
"element" "parentElement"
(ref null extern) -> (ref null extern))
(define-foreign replace-child!
"element" "replaceChild"
(ref null extern) (ref null extern) (ref null extern) -> (ref null extern))
(define-foreign set-attribute!
"element" "setAttribute"
(ref null extern) (ref string) (ref string) -> none)
(define-record-type <element-ref>
(make-element-ref elem deriv children)
element-ref?
(elem element-ref-element element-ref-set-element!)
(deriv element-ref-deriv element-ref-set-deriv!)
(children element-ref-children element-ref-set-children!))
(define (sxml->dom-ref exp)
(match exp
((or (? cell?) (? deriv?))
(let* ((ref (make-element-ref #f #f '()))
(deriv (~ (let ((old-elem (element-ref-element ref))
(inner-ref (sxml->dom (! exp)))
(elem (make-element "deriv")))
(append-child! elem (element-ref-element inner-ref))
(when old-elem
(replace-child! (element-parent old-elem) old-elem elem))
(element-ref-set-element! ref elem)
(element-ref-set-children! ref (list inner-ref))))))
(element-ref-set-deriv! ref deriv)
ref))
((? number? num)
(make-element-ref (make-text-node (number->string num)) #f '()))
;; The simple case: a string representing a text node.
((? string? str)
(make-element-ref (make-text-node str) #f '()))
;; An element tree. The first item is the HTML tag.
(((? symbol? tag) . body)
;; Create a new element with the given tag.
(let* ((elem (make-element (symbol->string tag)))
(ref (make-element-ref elem #f '())))
(define (add-children children)
;; Recursively call sxml->dom for each child node and
;; append it to elem.
(element-ref-set-children!
ref
(map (lambda (child)
(let ((child-ref (sxml->dom child)))
(append-child! elem (element-ref-element child-ref))
child-ref))
children)))
(match body
;; '@' denotes an attribute list. Child nodes follow.
((('@ . attrs) . children)
;; Set attributes.
(for-each (lambda (attr)
(match attr
;; Attributes are (symbol string) tuples.
(((? symbol? name) (? string? val))
(set-attribute! elem
(symbol->string name)
val))
(((? symbol? name) (? procedure? proc))
(add-event-listener! elem
(symbol->string name)
(procedure->external proc)))))
attrs)
(add-children children))
;; No attributes, just a list of child nodes.
(children (add-children children)))
ref))))
(define counter (make-cell 0))
(define square (~ (* (! counter) (! counter))))
(define root-ref
(sxml->dom-ref
`(div (@ (class "foo"))
(p "this is a test")
(button (@ (id "button")
(type "button")
(click ,(lambda (e)
(! counter (1+ (! counter)))
;; Hacky hack to ensure root-ref isn't GC'd too early...
(display root-ref))))
"Counter: "
,counter)
(p "The square is: " ,square))))
(append-child! (document-body) (element-ref-element root-ref))