EQL5/examples/X-extras/move-blocks/move-blocks.lisp

392 lines
15 KiB
Common Lisp

;;; This is a (slightely extended) port of the Qt example "moveblocks".
;;; Depends on plugin in "cpp/", needed for custom easing curve function.
;;;
;;; Exploring the features is left as an exercise...
;;;
;;; N.B: If you load this file in Slime, you need to use QLOAD instead of LOAD!
(in-package :eql-user)
(setf *break-on-errors* t)
;;;
;;; cpp plugin
;;;
(defvar *c++* (qload-c++ (in-src "examples/X-extras/move-blocks/cpp/easing_curve")))
(defvar *custom-easing-curve* (! "easingCurve" (:qt *c++*)))
(let ((sub 0)
(div 1)
function)
(defun custom-easing-function (progress)
(let ((y (ignore-errors
(eval (subst progress 'x function)))))
(if y
(/ (- y sub) div)
progress)))
(defun easing-function-edited ()
(labels ((call (x)
(ignore-errors (eval (subst x 'x function))))
(normalize ()
(setf sub (or (call 0) 0)
div (or (- (call 1) sub) 1))
(when (zerop div)
(setf div 1))))
(let* ((fun (ignore-errors
(read-from-string (format nil "(progn ~A)" (|toPlainText| *custom*)))))
(y (ignore-errors
(eval (subst 1 'x fun)))))
(when (numberp y)
(setf function fun)
(normalize)
(update-graph-pixmap *custom-easing-curve*))
(qset-color *custom* |QPalette.Base| (if y "white" "peachpuff"))))))
;;;
;;; user interface
;;;
(defvar *main* (qload-ui (in-src "examples/X-extras/move-blocks/move-blocks.ui")))
(defvar-ui *main*
*custom*
*duration*
*easing-curve*
*graph*
*items*
*pause*
*view*)
(defun easing-curve-names ()
(let ((custom "Custom"))
(cons custom (sort (remove custom (mapcar 'car (cdadr (qenums "QEasingCurve" "Type"))) :test 'string=)
'string<))))
(defun ini-ui ()
;; easing curve
(x:do-with *easing-curve*
(|setToolTip| "Change easing curve of selected items")
(|addItems| (easing-curve-names)))
(|setCurrentIndex| *easing-curve* (|findText| *easing-curve* "InElastic"))
(qconnect *easing-curve* "activated(QString)" 'change-easing-curve)
;; custom easing curve function
(x:do-with *custom*
(|setFont| (qnew "QFont(QString,int)"
#+darwin "Monaco" #+darwin 12
#+linux "Monospace" #+linux 9
#+windows "Courier New" #+windows 10))
(|setPlainText| (format nil ";; \"Custom\" easing function~
~%~
~%(defun ease (s)~
~% (- (* (expt x 3) (1+ s))~
~% (* (expt x 2) s)))~
~%~
~%(ease (- (* 15 x) 7))")))
(qconnect *custom* "textChanged()" 'easing-function-edited)
(easing-function-edited)
;; items
(x:do-with *items*
(|setColumnCount| 2)
(|setRootIsDecorated| nil)
(|setSelectionMode| |QAbstractItemView.ExtendedSelection|))
(|hide| (|header| *items*))
(|setStretchLastSection| (|header| *items*) t)
(qlater (lambda () (|resizeColumnToContents| *items* 0)))
;; graph
(qlet ((curve "QEasingCurve(QEasingCurve::Type)" |QEasingCurve.OutElastic|))
(update-graph-pixmap curve :ini))
(qoverride *graph* "paintEvent(QPaintEvent*)" 'paint-graph)
;; duration
(x:do-with (qset *duration*)
("minimum" 1)
("maximum" 4000)
("value" 1500))
(qconnect *duration* "valueChanged(int)" 'change-duration)
;; pause
(x:do-with (qset *pause*)
("minimum" 1)
("maximum" 1000)
("value" 150))
(qconnect *pause* "valueChanged(int)" 'change-pause)
;; sizes
(|setMinimumSize| *view* '(250 250)) ; initial size, see below
(|setMinimumWidth| *items* 200)
(|setMinimumWidth| *custom* 250)
(|resize| *main* '(0 0))
(qlater (lambda ()
(|show| *main*)
(|setMinimumSize| *view* '(10 10)))))
(let ((n 0))
(defun add-to-items (color)
(let ((item (qnew "QTreeWidgetItem(QStringList)" (list (format nil "item ~D" (incf n))))))
(|setIcon| item 0 (qnew "QIcon(QPixmap)"
(x:let-it (qnew "QPixmap(int,int)" 10 10)
(|fill| x:it color))))
(|setText| item 1 (if (oddp n) "InElastic" "OutElastic")) ; initial values
(|addTopLevelItem| *items* item))))
;;; graph pixmap (easing curve)
(let* ((steps 70)
(bx 5)
(by 30)
(progress bx)
pixmap)
(defun update-graph-pixmap (curve &optional ini)
(when pixmap
(qdel pixmap))
(setf pixmap (qnew "QPixmap(int,int)" (+ (* 2 bx) steps) (+ (* 2 by) steps)))
(when ini
(|setFixedSize| *graph* (|size| pixmap)))
(qlet ((painter "QPainter(QPixmap*)" pixmap)
(brush1 "QBrush(QColor)" "lightgray")
(brush2 "QBrush(QColor)" "cornflowerblue")
(pen1 "QPen(QBrush,qreal,Qt::PenStyle)" brush1 1 |Qt.DashLine|)
(pen2 "QPen(QBrush,qreal)" brush2 2))
(|fill| pixmap "lightyellow")
(x:do-with painter
(|setRenderHint| |QPainter.Antialiasing|)
(|setPen| pen1)
(|drawLine| (list bx by (+ bx steps) by))
(|drawLine| (let ((y (+ steps by))) (list bx y (+ bx steps) y)))
(|setPen| pen2))
(let (p*)
(dotimes (x (1+ steps))
(let ((p (list (+ bx x) (- (+ by steps)
(* steps (|valueForProgress| curve (/ x steps)))))))
(|drawLine| painter (append (or p* p) p))
(setf p* p))))))
(defun paint-graph (event)
(qlet ((p "QPainter(QWidget*)" *graph*))
(|drawPixmap| p '(0 0) pixmap)
(when progress
(x:do-with p
(|setPen| "red")
(|drawLine| (list progress 0 progress (+ (* 2 by) steps)))))))
(defun update-graph-progress (ms)
(let ((max (|value| *duration*)))
(setf progress (if (= max ms)
nil
(+ bx (* steps (/ ms max))))))
(|update| *graph*)
(qcall-default)))
;;;
;;; move blocks
;;;
(defconstant +state-switch-event+ (+ |QEvent.User| 256))
(defvar *timer* (qnew "QTimer"
"singleShot" t))
;;; state-switch-event
(let (event-rand)
(defun new-state-switch-event (rand)
(setf event-rand rand)
(qnew "QEvent(QEvent::Type)" +state-switch-event+))
(defun event-rand ()
event-rand))
;;; state-switch-transition
(defstruct (state-switch-transition (:conc-name transition-))
(q (qnew "QAbstractTransition"))
(rand 0))
(defmethod the-qt-object ((object state-switch-transition))
(transition-q object))
(defun new-state-switch-transition (rand)
(let ((trans (make-state-switch-transition :rand rand)))
(qoverride trans "eventTest(QEvent*)"
(lambda (event)
(and (= +state-switch-event+
(|type| event))
(= (transition-rand trans)
(event-rand)))))
trans))
;;; state-switcher
(defstruct (state-switcher (:conc-name :switcher-))
(q nil)
(state-count 0)
(last-index 0))
(defmethod the-qt-object ((object state-switcher))
(switcher-q object))
(defun new-state-switcher (machine name)
(let ((switch (make-state-switcher :q (qnew "QState(QState*)" machine
"objectName" name))))
(qoverride switch "onEntry(QEvent*)"
(lambda (event)
(let (n)
(x:while (= (setf n (1+ (random (switcher-state-count switch))))
(switcher-last-index switch)))
(setf (switcher-last-index switch) n)
(|postEvent| (|machine| switch)
(new-state-switch-event n)))))
switch))
;;; main
(defun new-graphics-rect-widget (color)
(let ((grect (qnew "QGraphicsWidget")))
(qoverride grect "paint(QPainter*,QStyleOptionGraphicsItem*,QWidget*)"
(lambda (painter s w)
(|fillRect| painter (|rect| grect) color)))
(add-to-items color) ; see *items*
grect))
(defun create-geometry-state (parent objects rects)
(let ((result (qnew "QState(QState*)" parent)))
(mapc (lambda (object rect)
(|assignProperty| result object "geometry" (qnew "QVariant(QRect)" rect)))
objects rects)
result))
(defun add-state (state-switcher state animation)
(let ((trans (new-state-switch-transition (incf (switcher-state-count state-switcher)))))
(x:do-with trans
(|setTargetState| state)
(|addAnimation| animation))
(|addTransition| state-switcher trans)))
(defmacro push* (item list)
`(setf ,list (nconc ,list (list ,item))))
(let (animations groups)
(defun add-property-animation (anim-group button property curve-type duration &optional pause)
(let ((anim (qnew "QPropertyAnimation(QObject*,QByteArray)" button (x:string-to-bytes property)))
(group (if pause
(let ((group (qnew "QSequentialAnimationGroup(QObject*)" anim-group)))
(|addPause| group pause)
(push* group groups)
group)
anim-group)))
(x:do-with anim
(|setDuration| duration)
(|setEasingCurve| (qnew "QEasingCurve(QEasingCurve::Type)" curve-type)))
(push* anim animations)
(|addAnimation| group anim)
anim))
(defun change-easing-curve (name)
(let ((type (symbol-value (intern (x:cc "QEasingCurve." name)))))
(dotimes (i (|topLevelItemCount| *items*))
(let ((item (|topLevelItem| *items* i))
(curve (if (string= "Custom" name)
*custom-easing-curve*
(qnew "QEasingCurve(QEasingCurve::Type)" type))))
(when (|isSelected| item)
(|setText| item 1 name)
(|setEasingCurve| (nth i animations) curve))
(update-graph-pixmap curve)))))
(defun change-duration (msec)
(dolist (anim animations)
(|setDuration| anim msec))
(update-timer))
(defun change-pause (msec)
(let ((n 0))
(dolist (group groups)
(let ((anim (|takeAnimation| group 1)))
(x:do-with group
(|clear|)
(|addPause| (* (incf n) msec))
(|addAnimation| anim)))))
(update-timer))
(defun update-timer ()
(|setInterval| *timer* (+ (|value| *duration*)
(* 4 (|value| *pause*))
500))))
(defun ini ()
(let* ((item1 (new-graphics-rect-widget "tomato"))
(item2 (new-graphics-rect-widget "lightgreen"))
(item3 (new-graphics-rect-widget "lightblue"))
(item4 (new-graphics-rect-widget "lightyellow"))
(items (list item1 item2 item3 item4))
(scene (qnew "QGraphicsScene"
"sceneRect" '(0 0 300 300)))
(machine (qnew "QStateMachine"))
(group (qnew "QState"
"objectName" "group"))
(anim-group (qnew "QParallelAnimationGroup")))
(|setScene| *view* scene)
(|setZValue| item2 1)
(|setZValue| item3 2)
(|setZValue| item4 3)
(x:do-with scene
(|setBackgroundBrush| (qnew "QBrush(QColor)" "darkslategray"))
(|addItem| item1)
(|addItem| item2)
(|addItem| item3)
(|addItem| item4))
(x:do-with *view*
(|setAlignment| (logior |Qt.AlignLeft| |Qt.AlignTop|))
(|setHorizontalScrollBarPolicy| |Qt.ScrollBarAlwaysOff|)
(|setVerticalScrollBarPolicy| |Qt.ScrollBarAlwaysOff|))
(let ((state1 (create-geometry-state group items
'((100 0 50 50)
(150 0 50 50)
(200 0 50 50)
(250 0 50 50))))
(state2 (create-geometry-state group items
'((250 100 50 50)
(250 150 50 50)
(250 200 50 50)
(250 250 50 50))))
(state3 (create-geometry-state group items
'((150 250 50 50)
(100 250 50 50)
(50 250 50 50)
(0 250 50 50))))
(state4 (create-geometry-state group items
'((0 150 50 50)
(0 100 50 50)
(0 50 50 50)
(0 0 50 50))))
(state5 (create-geometry-state group items
'((100 100 50 50)
(150 100 50 50)
(100 150 50 50)
(150 150 50 50))))
(state6 (create-geometry-state group items
'((50 50 50 50)
(200 50 50 50)
(50 200 50 50)
(200 200 50 50))))
(state7 (create-geometry-state group items
'((0 0 50 50)
(250 0 50 50)
(0 250 50 50)
(250 250 50 50))))
(state-switcher (new-state-switcher machine "stateSwitcher")))
(let ((anim (add-property-animation anim-group item1 "geometry" |QEasingCurve.OutElastic| 1500)))
(qoverride anim "updateCurrentTime(int)" 'update-graph-progress))
(add-property-animation anim-group item2 "geometry" |QEasingCurve.InElastic| 1500 150)
(add-property-animation anim-group item3 "geometry" |QEasingCurve.OutElastic| 1500 225)
(add-property-animation anim-group item4 "geometry" |QEasingCurve.InElastic| 1500 300)
(|setInterval| *timer* 2500)
(dolist (state (list state1 state2 state3 state4 state5 state6 state7))
(add-state state-switcher state anim-group))
(x:do-with group
(|setInitialState| state1)
(|addTransition| *timer* (qsignal "timeout()") state-switcher)))
(x:do-with machine
(|addState| group)
(|setInitialState| group)
(|start|))
(qconnect group "entered()" *timer* "start()")
(qoverride *view* "resizeEvent(QResizeEvent*)"
(lambda (event)
(|fitInView| *view* (|sceneRect| scene))
(qcall-default)))))
(progn
(ini-ui)
(ini))