mirror of
https://github.com/rabbibotton/clog.git
synced 2025-12-06 02:30:42 -08:00
309 lines
10 KiB
Common Lisp
309 lines
10 KiB
Common Lisp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;;; CLOG - The Common Lisp Omnificent GUI ;;;;
|
|
;;;; (c) 2020-2021 David Botton ;;;;
|
|
;;;; License BSD 3 Clause ;;;;
|
|
;;;; ;;;;
|
|
;;;; clog-docs.lisp ;;;;
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(in-package :clog)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Exports - clog documentation sections
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defsection @clog-getting-started (:title "CLOG Getting Started")
|
|
"CLOG - The Common Lisp Omnificent GUI
|
|
|
|
David Botton <david@botton.com>
|
|
|
|
License BSD 3-Clause License
|
|
|
|
- Introduction
|
|
|
|
The Common Lisp Omnificent GUI, CLOG for short, uses web technology to
|
|
produce graphical user interfaces for applications locally or remotely.
|
|
CLOG can take the place, or work alongside, most cross-platform GUI
|
|
frameworks and website frameworks. The CLOG package starts up the
|
|
connectivity to the browser or other websocket client (often a browser
|
|
embedded in a native template application.)
|
|
|
|
STATUS: CLOG is complete enough for most uses. See the README.md for
|
|
some enhacements bing worked on, CLOG is actually based on GNOGA, a
|
|
framework I wrote for Ada in 2013 and used in commercial production
|
|
code for the last 6 years, i.e. the techiniques CLOG uses are solid
|
|
and proven.
|
|
|
|
Some potential applications for CLOG:
|
|
|
|
* Cross-platform GUIs and Reports
|
|
* Secure websites and complex interactive web applications
|
|
* Mobile software
|
|
* Massive multiplayer online games
|
|
* Monitoring software for embedded systems
|
|
* A fun way to teach programming and advanced multi-tasking
|
|
parallel programming techniques. (CLOG is a parallel GUI)
|
|
* And the list goes on
|
|
|
|
The key to CLOG is the relationship it forms with a Browser window
|
|
or Browser control compiled to native code. CLOG uses websockets
|
|
for communications and the browser to render a GUI that maintains
|
|
an active soft realtime connection. For most CLOG applications all
|
|
programming logic, events and decisions are done on the server
|
|
which can be local or remote over the web.
|
|
|
|
CLOG is developed on an M1 MacBook with ECL, it is tested fairly
|
|
regulary with SCBL on Linux, Windows and Intel MacBook. It should
|
|
in theory work on any system Quicklisp and CLACK will load on to.
|
|
|
|
CLOG will be in Quicklisp in the next update, but because I am still
|
|
adding code daily, it is currently preferable to clone the github repo
|
|
into your ~/common-lisp directory:
|
|
|
|
```
|
|
cd ~/common-lisp
|
|
git clone https://github.com/rabbibotton/clog.git
|
|
```
|
|
|
|
To load this package and work through tutorials (assuming you
|
|
have Quicklisp configured):
|
|
|
|
1. cd to the CLOG dir (the dir should be one used by Quicklisp ex. ~/common-lisp/)
|
|
2. Start emacs/slime or your common lisp \"repl\" in _that_ directory.
|
|
3. In the REPL, run:
|
|
|
|
```
|
|
CL-USER> (ql:quickload :clog)
|
|
CL-USER> (load \"~/common-lisp/clog/tutorial/01-tutorial.lisp\")
|
|
CL-USER> (clog-user:start-tutorial)
|
|
```
|
|
|
|
Work your way through the tutorials. You will see how quick and easy it is
|
|
to be a CLOGer. The next section also covers the basic programming concepts
|
|
needed for mastering CLOG.")
|
|
|
|
(defsection @clog-programming-basics (:title "CLOG Programming Basics")
|
|
"
|
|
* Prerequisites
|
|
- You don't have to be an expert in Common Lisp but should know the basics
|
|
- You _don't_ need to know JavaScript
|
|
- You don't need to know HTML but it helps unless someone else is doing the
|
|
design work.
|
|
- You have installed CLOG and (ql:quickload :clog) is working for you.
|
|
|
|
|
|
|
|
* Simple REPL techniques Tutorial
|
|
|
|
We first need to load CLOG
|
|
|
|
```lisp
|
|
CL-USER> (ql:quickload :clog)
|
|
To load \"clog\":
|
|
Load 1 ASDF system:
|
|
clog
|
|
; Loading \"clog\"
|
|
................................................
|
|
(:CLOG)
|
|
```
|
|
|
|
Next, we need to use the INITIALIZE function to tell CLOG to start up the web
|
|
server, what to do when someone connects and where the static HTML files
|
|
are located.
|
|
|
|
```lisp
|
|
CL-USER> (clog:initialize (lambda (body)()) :static-root #P\"~/common-lisp/clog/static-files/\")
|
|
Hunchentoot server is started.
|
|
Listening on 0.0.0.0:8080.
|
|
HTTP listening on : 0.0.0.0:8080
|
|
HTML Root : /Users/dbotton/common-lisp/clog/static-files/
|
|
Boot file for path / : /boot.html
|
|
NIL
|
|
```
|
|
|
|
At this point our CLOG app doese very little. To see our CLOG app so far go to
|
|
http://127.0.0.1:8008 or in most common-list configurations you can use:
|
|
|
|
```lisp
|
|
CL-USER> (clog:open-browser)
|
|
```
|
|
|
|
Something more than an empty lambda function is needed to do more. The
|
|
tutorials are a good place to start with make CLOG apps in code, so
|
|
here we are going to demonstrate the concepts using some REPL tricks
|
|
to help developing CLOG apps in general.
|
|
|
|
We need to give ourselves easier access to CLOG and or an app we are
|
|
working one. Let's create a package that uses CLOG and of course
|
|
common lisp \"cl\" we will call it \"clog-user\".
|
|
|
|
```lisp
|
|
CL-USER> (defpackage #:clog-user
|
|
(:use #:cl #:clog))
|
|
(in-package :clog-user)
|
|
#<\"CLOG-USER\" package>
|
|
CLOG-USER>
|
|
```
|
|
|
|
Since we already initialized CLOG let's use SET-ON-NEW-WINDOW to change our
|
|
on-new-window handler (handler is just a made up name for a function that
|
|
will handle an event).
|
|
|
|
```lisp
|
|
CLOG-USER> (set-on-new-window (lambda (body) (create-div body :content \"Hello World!\")))
|
|
```
|
|
|
|
Now go ahead and resresh our browser and you should see the famous first words
|
|
of every app.
|
|
|
|
This of though is still not very REPL like, CLOG is a 'live' connection to a
|
|
browser. So lets redo our on-new-window handler to give us access to the
|
|
browser in the REPL.
|
|
|
|
```lisp
|
|
CLOG-USER> (defparameter *body* nil)
|
|
*BODY*
|
|
CLOG-USER> (set-on-new-window (lambda (body) (setf *body* body)))
|
|
```
|
|
|
|
Reset your browser again (or navigate to http://127.0.0.1:8080 and let's have
|
|
some fun.
|
|
|
|
(From here on, we will leave out the promps and responses in our quotes of
|
|
code.)
|
|
|
|
```lisp
|
|
(create-div *body* :content \"Hello World\")
|
|
```
|
|
|
|
If you have the browser on the screen you will see the results immediately. Try
|
|
this line and you can watch it happen:
|
|
|
|
```lisp
|
|
(dotimes (n 10) (create-div *body* :content (format nil \"Line ~A - Hello World\" n)) (sleep .3))
|
|
```
|
|
|
|
We can also set and respond to events and set properties etc:
|
|
|
|
```lisp
|
|
(let ((tmp (create-button *body* :content \"Click Me\")))
|
|
(set-on-click tmp (lambda (obj)(setf (hiddenp tmp) t))))
|
|
```
|
|
|
|
Important take aways to using CLOG from the REPL:
|
|
|
|
1. You will need to pass to a global from the running system whatever you want to tinker
|
|
with in the live system from the REPL.
|
|
2. Any time you recompile the on-new-window handler or want to use a different one
|
|
you will need to use SET-ON-NEW-WINDOW.
|
|
3. Similarily with all events, any time an event handler is recompiled or want to
|
|
change the even hander, set-on-* function will need to be called.")
|
|
|
|
|
|
(defsection @clog-event-data (:title "CLOG Event Data")
|
|
"
|
|
Some events in CLOG return in addition to the target event, event data.
|
|
The data is passed in the second argument to the event handler as a
|
|
property list. To retrieve the data use (getf data :property) the available
|
|
properties (to use for :property) are based on the event type.
|
|
|
|
From clog-base
|
|
|
|
:event-type :mouse
|
|
:x x relative to the target
|
|
:y y relative to the target
|
|
:screen-x x relative to the users screen
|
|
:screen-y y relative to the users screen
|
|
:which-button which mouse button clicked
|
|
:alt-key t or nil if alt-key held down
|
|
:ctrl-key t or nil if ctrl-key held down
|
|
:shift-key t or nil if shift-key held down
|
|
:meta-key t or nil if meta-key held down
|
|
|
|
|
|
:event-type :touch
|
|
:x x relative to the target
|
|
:y y relative to the target
|
|
:screen-x x relative to the users screen
|
|
:screen-y y relative to the users screen
|
|
:number-fingers number of fingers being used
|
|
:alt-key t or nil if alt-key held down
|
|
:ctrl-key t or nil if ctrl-key held down
|
|
:shift-key t or nil if shift-key held down
|
|
:meta-key t or nil if meta-key held down
|
|
|
|
:event-type :keyboard
|
|
:key-code A key code sometimes called a scan code for the key pressed
|
|
:char-code UTF-8 representation for key pressed when possible
|
|
:alt-key t or nil if alt-key held down
|
|
:ctrl-key t or nil if ctrl-key held down
|
|
:shift-key t or nil if shift-key held down
|
|
:meta-key t or nil if meta-key held down
|
|
|
|
From clog-window
|
|
|
|
:event-type :storage
|
|
:key local storage key that was updated (even in another window)
|
|
:old-value old key value
|
|
:value new key value
|
|
|
|
|
|
")
|
|
|
|
(defsection @clog-internals (:title "CLOG Framework internals and extensions")
|
|
"Responding to new java script DOM events
|
|
|
|
If there is no data for the event just changing the name of the event is
|
|
sufficient in this example:
|
|
|
|
```lisp
|
|
(defmethod set-on-click ((obj clog-obj) handler)
|
|
(set-event obj \"click\"
|
|
(when handler
|
|
(lambda (data)
|
|
(declare (ignore data))
|
|
(funcall handler obj)))))
|
|
```
|
|
|
|
If there is data for the event an additional string containing the needed
|
|
java-script to return the even data and a function to parse out the data.
|
|
|
|
Replace the event name with the correct name, parse-keyboard-even with the
|
|
parse function and the string containing the needed JavaScrip replaces
|
|
keyboard-event-script:
|
|
|
|
* The event handlers setter
|
|
|
|
```lisp
|
|
(defmethod set-on-key-down ((obj clog-obj) handler)
|
|
(set-event obj \"keydown\"
|
|
(when handler
|
|
(lambda (data)
|
|
(funcall handler obj (parse-keyboard-event data))))
|
|
:call-back-script keyboard-event-script))
|
|
```
|
|
|
|
* The script
|
|
|
|
```lisp
|
|
(defparameter keyboard-event-script
|
|
\"+ e.keyCode + ':' + e.charCode + ':' + e.altKey + ':' + e.ctrlKey + ':' +
|
|
e.shiftKey + ':' + e.metaKey\")
|
|
```
|
|
|
|
* The event parser
|
|
|
|
```lisp
|
|
(defun parse-keyboard-event (data)
|
|
(let ((f (ppcre:split \":\" data)))
|
|
(list
|
|
:event-type :keyboard
|
|
:key-code (parse-integer (nth 0 f) :junk-allowed t)
|
|
:char-code (parse-integer (nth 1 f) :junk-allowed t)
|
|
:alt-key (js-true-p (nth 2 f))
|
|
:ctrl-key (js-true-p (nth 3 f))
|
|
:shift-key (js-true-p (nth 4 f))
|
|
:meta-key (js-true-p (nth 5 f)))))
|
|
```
|
|
|
|
")
|