Skip to content

knilink/reed.el

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

53 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

React-like Emacs Elisp Design

Aim to streamline the process of building model interactive flex layout app that resemble the react development experience.

Get Started

Building Dynamic Module

Assume you already have rust installed.

git clone https://github.com/knilink/reed.el.git
cd reed.el
cargo build --release

If success, libreed.so should be generated at target/release/libreed.so

First App

Create a file my-first-app.el with the following content

;; -*- lexical-binding: t -*-
(require 'reed-elx)
(require 'reed-hooks)
(require 'reed-style)
(require 'reed-render-helper)

(fc! Container (children)
     (elx!
      (div
       :style (style!*
               (size (width . 100%) (height . AUTO))
               (flex_direction . 'Column)
               (align_items . '(Center)))
       ({} children))))

(fc! Row (children)
     (elx!
      (div
       :style (style!*
               (justify_content . '(Center))
               (size (width . 100%) (height . AUTO)))
       ({} children))))

(fc! App ()
     (let ((current-time-sig (use-signal (lambda () (current-time-string))))
           (buffer-name-ref (use-ref (lambda () (buffer-name)))))
       (use-hook-with-cleanup
        (lambda ()
          (run-with-timer
           0 1
           (lambda ()
             (funcall current-time-sig (current-time-string))
             (reed-handle-render (funcall buffer-name-ref)))))
        (lambda (timer-handle) (cancel-timer timer-handle)))
       (elx!
        (Container
         (Row (p (span
                  :face '(:foreground "red" :background "yellow")
                  "Hello, ")
                 (span
                  :face '(:foreground "green" :background "blue")
                  "World!")))
         (Row (p "Time is now: " ({} (funcall current-time-sig))))))))

(reed-render-buffer "my-first-app" #'App)

Then run the app with

emacs -l reed.el/target/release/libreed.so -L reed.el/lisp -l ./my-first-app.el -Q

If everything goes well, result bellow can be seen. image

Example App

Elements

Tags

  • div The fundamental layout container. Used for structuring UI components with flexible styling options.

  • p (Paragraph) Text container that serves as a leaf node in the layout hierarchy.

    • Children can only be span elements
    • Primarily used for text content with styling
  • span Inline text styling element.

    • Only valid as a child of p elements
    • Supports only the face property for text styling
    • Used for applying different styles to portions of text

Attributes

  • style S-expressions serialized taffy layout Style.

  • face Face text property for styling text.

  • ref Provides a way to obtain element id, subsequently can be use to retrieve element position etc. A ref is created with use-ref hook.

Event Listeners

Event listeners will be triggered accordingly depends on event type and position attribute, event payload will be forwarded directly to listeners.

  • onclick: Triggered when cursor event click is emitted and the position is inside the target element.
  • onfocus: Triggered when click inside an element while the target element isn't focused.
  • onblur: Triggered when click outside an element while the target element is focused.
  • onhover: Triggered when cursor move is emitted and the position is inside the target element.
  • onleave: Triggered when cursor move is emitted and the position is outside the target element.

To emit a cursor event:

(reed-handle-cursor-event my-registered-app-name 'click my-event-position my-event-payload)
(reed-handle-cursor-event "my-first-app" 'move 123 '(:position 123))

Elx Macros

elx!

Provides a convenient way to create react element. Tag starts with lower case will be treated as a native element tag otherwise a component. Syntax: (elx! (tag ,@attr-plist ,@children) (Component ,@props-plist ,@children) &rest)

{} Curly Brace Syntax

The body of {} will be treated as dynamic content and evaluated during runtime. For example,

(elx! (div ({} (if flag (elx! (p "true")) "false"))))

is equivalent to

<div>{flag ? <p>true</p> : "false"}</div>

fc! Functional Component

Used to define reusable React-like components that returns a element, use keyword symbol to define alias Example:

(fc! MyComponent (foo :bar bar-alias)
  (message "[bar-alias] %s" bar-alias)
  (elx! ...))

(elx! (MyComponent :foo "foo-value" :bar "bar-value"))

Style

S-expressions serialized taffy layout Style.

Length

To create the 4 different type of serialized length attribute, use reed-taffy-length.

  • percent: (reed-taffy-length 'percent 0.5)
  • length: (reed-taffy-length 'length 80.0)
  • auto: (reed-taffy-length 'auto 0.0)
  • zero: (reed-taffy-length 'zero 0.0)

Optional Attribute

When specifying attribute declared as Optional in rust, e.g. justify_content: Option<JustifyContent>. The value need to be wrapped with brackets () to represent Some(attr), e.g. (justify_content . '(Center)).

style! macro

Helper to compose style property, main to help expanding short handed length. Length value needs to be ended either pt or %, e.g.

(style!
  (margin
    (left . 100pt)
    (right . 20%)
    (top . ZERO)
    (bottom . AUTO)))

style!* macro

Same as style! but style will be expanded into static string during macro expansion. Recommended if style is static throughout the entire component lifetime.

Hooks

use-signal

(fc! App ()
     (let ((count-sig (use-signal (lambda() 0))))
       (elx!
        (p
         "Count: "
         ({} (funcall count-sig)))
        (p " ")
        (p
         :face '(:background "gray80" :box (:line-width 1 :style released-button))
         :onclick (lambda (e)
                    (funcall count-sig (1+ (funcall count-sig))))
           "click me!"))))

use-effect

(fc! App ()
     (let ((element-ref (use-ref (lambda())))
           (location-sig (use-signal (lambda())))
           (buffer-name-ref (use-ref (lambda () (buffer-name)))))
       (use-effect
        (lambda ()
          (run-with-timer
           0 nil
           (lambda ()
             (unless (funcall location-sig)
               (funcall
                location-sig
                (reed-get-absolut-location
                 (funcall buffer-name-ref)
                 (funcall element-ref)))
               (reed-handle-render (funcall buffer-name-ref)))))))
       (elx!
        (div
         :style (style!*
                 (padding (left . ZERO) (right . ZERO) (top . 10pt) (bottom . ZERO))
                 (justify_content . '(Center))
                 (size (width . 100%) (height . AUTO)))
         (p
          :ref element-ref
          ({} (format "this element located at %s" (funcall location-sig))))))))

About

write react text ui app with elisp

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published