Commit 4f1595f7 authored by Daniel Kochmański's avatar Daniel Kochmański
Browse files

web: add tutorial #1

parent 7ea63c42
;;;;;
title: CLIM, the galaxy's most advanced graphics toolkit
tags: tutorial
date: 2016-08-11 17:00
format: md
author: Gabriel Laddel
;;;;;
## Setting up the basic environment
We assume that you already have configured the Common Lisp environment
(including Quicklisp) and that you know the basics of the Common Lisp
programming. The necessary systems to load are `mcclim` and
`clim-listener`, you may load them with the Quicklisp. After that
launch the listener in a separate thread:
```common-lisp
(ql:quickload '(mcclim clim-listener))
(clim-listener:run-listener :new-process t)
```
[![Listener](https://common-lisp.net/project/mcclim/static/media/tutorial-1/cap-tut1-listener-small.png
"Listener")](https://common-lisp.net/project/mcclim/static/media/tutorial-1/cap-tut1-listener.png)
## Finding your way around CLIM
CLIM is the global minimum in graphics toolkits. Geometry, and means
of abstracting it. Switch the listener into the CLIM-INTERNALS package
to get started. Type `(in-package climi)` in the `Listener REPL`.
### Animations
Evaluate the 4 following forms in `Slime REPL`, then call
`(cos-animation)` in the `Listener REPL` to demonstrate CLIM's
animation capabilities. You *cannot* evaluate (cos-animation) in the
`Slime REPL`, as `*standard-output*` is bound to the its output stream
which isn't a `sheet`, and thus cannot be drawn on.
```common-lisp
(in-package climi)
(defparameter *scale-multiplier* 150
"try modifying me while running!")
(defparameter *sleep-time* 0.0001
"modify and eval to speed or slow the animation, set to `nil' to stop")
(defun cos-animation ()
(let* ((range (loop for k from 0 to (* 2 pi) by 0.1 collect k)) ; length of 62
(idx 0)
(record
(updating-output (*standard-output*)
(loop for x from (nth idx range) to (+ (nth idx range) (* 2 pi)) by 0.01
with y-offset = 150
for x-offset = (- 10 (* *scale-multiplier* (nth idx range)))
for y-value = (+ y-offset (* *scale-multiplier* (cos x)))
for x-value = (+ x-offset (* *scale-multiplier* x))
do (draw-point* *standard-output*
x-value
y-value
:ink +green+
:line-thickness 3)))))
(loop while *sleep-time*
do (progn (sleep *sleep-time*)
(if (= 61 idx) (setq idx 0) (incf idx))
(redisplay record *standard-output*)))))
```
If you want to stop the animation, issue in the `Slime REPL`:
```common-lisp
(setf *sleep-time* nil)
```
If it wasn't already obvious, you can plot w/e.
```common-lisp
(CLIM-LISTENER::DRAW-FUNCTION-FILLED-GRAPH
#'tanh :min-x (- 0 pi pi) :max-x pi :min-y -1.1 :max-y 1.5 :ink +blue+)
```
### Drawning class hierarchy
Type `,clear output history` in the `Listener REPL` and RET to clear
the screen.
"," indicates that you are activating a command. Try typing comma, then C-/
to activate completion. C-c C-c to dismiss.
Children of the class `CLIMI::SHEET` can be drawn on using arbitrary
geometry. Try `(clim-listener::com-show-class-subclasses 'sheet)` in
the `Listener REPL` to view the subclasses of it.
### Commands and presentations
The name COM-whatever indicates that the function in question is a
clim command, which you can define in the `Slime REPL` like so,
```common-lisp
(in-package clim-listener)
;;; Runme! We will need these in a moment.
(dolist (image-name '("mp-avatar.png"
"vulpes-avatar.png"
"stas-avatar.png"
"suit-avatar.png"
"rainbow-dash-avatar.png"
"chaos-lord-avatar.png"))
(uiop:run-program (format nil "curl https://common-lisp.net/project/mcclim/static/media/tutorial-1/~A --output /tmp/~A"
image-name image-name)))
(define-listener-command (com-ls :name t)
((path 'string))
(clim-listener::com-show-directory path))
```
try `,ls /tmp/` -- then `,display image <SPACE>` and click on one of
the displayed paths to supply it as an argument. At the core of CLIM
is the notion of a presentation. Objects have presentation methods,
ie, some arbitrary rendering geometry, and when PRESENT'd on the
screen CLIM remembers the type. Thus one can supply objects of the
appropriate types as arguments to a command simply by clicking on
them. Read about `CLIMI::DEFINE-COMMAND` in the specification to learn
more. Let's define our first presentation method.
### Intermixing S-expressions with the presentation types
Evaluate the forms below in the `Slime REPL`:
```common-lisp
(in-package climi)
(defvar lords '("mircea_popescu" "asciilifeform" "ben_vulpes"))
(defclass wot-identity ()
((name :accessor name :initarg :name :initform nil)
(avatar :accessor avatar :initarg :avatar :initform nil)))
(defmethod lord? ((i wot-identity))
(member (name i) lords :test #'string=))
(define-presentation-type wot-identity ())
(defun make-identity (name avatar-pathname)
(make-instance 'wot-identity
:name name
:avatar avatar-pathname))
(defparameter *identities*
(mapcar (lambda (l) (apply #'make-identity l))
'(("mircea_popescu" #P"/tmp/mp-avatar.png")
("ben_vulpes" #P"/tmp/vulpes-avatar.png")
("asciilifeform" #P"/tmp/stas-avatar.png")
("Suit" #P"/tmp/suit-avatar.png")
("RainbowDash" #P"/tmp/rainbow-dash-avatar.png")
("ChaosLord" #P"/tmp/chaos-lord-avatar.png"))))
(define-presentation-method present (object (type wot-identity)
stream
(view textual-view)
&key acceptably)
(declare (ignorable acceptably))
(multiple-value-bind (x y)
(stream-cursor-position stream)
(with-slots (name avatar) object
(draw-pattern* stream
(climi::make-pattern-from-bitmap-file avatar :format :png)
(+ 150 x)
(+ 30 y))
(draw-text* stream name (+ 153 x) (+ 167 y)
:ink +black+
:text-size 20)
(draw-text* stream name (+ 152 x) (+ 166 y)
:ink (if (lord? object)
+gold+
+blue+)
:text-size 20))
(setf (stream-cursor-position stream)
(values x (+ 200 y)))
object))
(defun eval-and-then-call-me-in-the-listener ()
(let* ((n 8)
(sheet *standard-output*))
(labels ((gen (i)
(let* ((out-and-start '(f x)))
(loop
for k from 0 to i
do (setq out-and-start
(apply #'append
(mapcar
(lambda (s)
(case s
;; (x '(y f + f f + + x))
;; (y '(y f + f f x - - f f x))
(x '(+ y f + f f + y y +))
(y '(f - y f f x f f))
)) out-and-start))))
(remove-if (lambda (sym)
(member sym '(x y) :test 'eq))
out-and-start))))
(let* ((x 300) (y 300) (new-x x) (new-y y) (a 1) (step 15))
(loop
for r in (gen n)
do (progn (cond ((= a 1) (setq new-y (+ step y)))
((= a 2) (setq new-x (- x step)))
((= a 3) (setq new-y (- y step)))
((= a 4) (setq new-x (+ step x))))
(case r
(f (clim:draw-line* sheet x y new-x new-y
:ink clim:+blue+
:line-thickness 6
:line-cap-shape :round)
(setq x new-x y new-y))
(- (setq a (if (= 1 a) 4 (1- a))))
(+ (setq a (if (= 4 a) 1 (1+ a))))
(t nil)))))
(let* ((x 300) (y 300) (new-x x) (new-y y) (a 1) (step 15))
(loop
for r in (gen n)
do (progn (cond ((= a 1) (setq new-y (+ step y))) ; .5 pi
((= a 2) (setq new-x (- x step))) ; and onwards
((= a 3) (setq new-y (- y step)))
((= a 4) (setq new-x (+ step x))))
(case r
(f (clim:draw-line* sheet x y new-x new-y
:ink clim:+white+
:line-thickness 2
:line-cap-shape :round)
(setq x new-x y new-y))
(- (setq a (if (= 1 a) 4 (1- a))))
(+ (setq a (if (= 4 a) 1 (1+ a))))
(t nil))))))))
```
Now type `(dolist (i *identities*) (present i))` at the CLIM Listener.
Try typing `(lord?` at the listener and then clicking on one of the
identities. Add a closing paren and RET. Notice how objects can be
seamlessly intermixed with S-expressions. If this example fails for
you it may be that you have not recent enough version of McCLIM.
### Notes
Unripe fruits. The future isn't what it used to be - some assembly required.
* `(CLIM-DEMO::DEMODEMO)` (available with system `clim-examples`)
* The essential machinery of a 'live' [GUI builder](http://paste.lisp.org/display/320742#1)
* [Navigator](https://github.com/robert-strandh/McCLIM/issues/55#issuecomment-234805226)
(essentially an extended `apropos')
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment