diff --git a/examples/tilt-sensor/app.asd b/examples/tilt-sensor/app.asd new file mode 100644 index 0000000..8cb40d5 --- /dev/null +++ b/examples/tilt-sensor/app.asd @@ -0,0 +1,8 @@ +(defsystem :app + :serial t + :depends-on () + :components ((:file "lisp/package") + (:file "lisp/ui-vars") + (:file "lisp/maze") + (:file "lisp/main"))) + diff --git a/examples/tilt-sensor/build-android/install-run.sh b/examples/tilt-sensor/build-android/install-run.sh new file mode 100755 index 0000000..91a86c7 --- /dev/null +++ b/examples/tilt-sensor/build-android/install-run.sh @@ -0,0 +1,3 @@ +# install/update (keeps app data) +adb install -r android-build/*.apk +adb shell am start -n org.lqml.example.maze/org.qtproject.qt5.android.bindings.QtActivity # Qt5 diff --git a/examples/tilt-sensor/lisp/main.lisp b/examples/tilt-sensor/lisp/main.lisp new file mode 100644 index 0000000..a5ac065 --- /dev/null +++ b/examples/tilt-sensor/lisp/main.lisp @@ -0,0 +1,57 @@ +(in-package :maze) + +(defconstant +w+ 13) ; ball width + +(defvar *x*) ; ball x +(defvar *y*) ; ball y + +(defun move (x-rotation y-rotation) ; called from QML + (labels ((add (x) + (truncate (signum x))) + (to-pos (x) + (truncate (/ (+ x (/ +w+ 2)) + +w+))) + (normalize (x) + (* (to-pos x) +w+))) + (let* ((dx (min (* 0.2 x-rotation) (1- +w+))) + (dy (min (* 0.2 y-rotation) (1- +w+))) + (add-x (add dx)) + (add-y (add dy))) + (if (aref *maze* + (+ add-x (to-pos *x*)) + (to-pos *y*)) + (setf *x* (normalize *x*)) + (incf *x* dx)) + (if (aref *maze* + (to-pos *x*) + (+ add-y (to-pos *y*))) + (setf *y* (normalize *y*)) + (incf *y* dy)))) + (move-ball) + (values)) + +(defun move-ball () + (if (and (= *x* +w+) + (= *y* 0)) + (new-game) + (let ((ball (find-quick-item ui:*ball*))) + ;; 'qset' is faster than 'q>', but can't trigger animations + (qset ball |x| (- *x* 0.5)) + (qset ball |y| (- *y* 0.5))))) + +(defun start () + (setf *x* (* +w+ (- *width* 1)) + *y* (* +w+ (- *height* 2))) + (move-ball) + (q> |running| ui:*timer* t)) + +(defun stop () + (q> |running| ui:*timer* nil)) + +(defun new-game () + (new-maze) + (start)) + +(progn + (start) + (qlater 'ini)) diff --git a/examples/tilt-sensor/lisp/maze.lisp b/examples/tilt-sensor/lisp/maze.lisp new file mode 100644 index 0000000..8bf281b --- /dev/null +++ b/examples/tilt-sensor/lisp/maze.lisp @@ -0,0 +1,61 @@ +;;; original copyright: CL Maze 20030311 by Joe Wingbermuehle +;;; +;;; this is a reviewed version + +(in-package :maze) + +;; both must be odd +(defconstant *width* 25) +(defconstant *height* 39) + +(defvar *maze*) + +(defun carve-maze (x y) + (let ((d (random 4))) + (dotimes (c 4) + (let* ((cd (mod (+ c d) 4)) + (dv (case cd + (0 (list 1 0)) + (1 (list 0 1)) + (2 (list -1 0)) + (t (list 0 -1)))) + (x1 (+ x (car dv))) + (y1 (+ y (cadr dv))) + (x2 (+ x1 (car dv))) + (y2 (+ y1 (cadr dv)))) + (when (and (< 0 x2 *width*) + (< 0 y2 *height*) + (aref *maze* x1 y1) + (aref *maze* x2 y2)) + (set-visible x1 y1 nil) + (set-visible x2 y2 nil) + (qsleep 0.001) ; slow down to make visible + (carve-maze x2 y2)))))) + +(defun generate-maze () + (set-visible 1 1 nil) + (carve-maze 1 1) + (set-visible 1 0 nil) + (set-visible (- *width* 1) (- *height* 2) nil)) + +(defun set-visible (x y visible) + (setf (aref *maze* x y) visible) + ;; |visible| doesn't work inside Repeater + (q> |opacity| (q! |itemAt| ui:*maze* + (+ x (* y *width*))) + (if visible 1 0))) + +(defun display-maze () + (dotimes (y *height*) + (dotimes (x *width*) + (set-visible x y (aref *maze* x y))))) + +(defun new-maze () + (setf *maze* (make-array (list *width* *height*) + :initial-element t)) + (display-maze) + (qlater 'generate-maze)) + +(defun ini () + (setf *random-state* (make-random-state t)) + (new-maze)) diff --git a/examples/tilt-sensor/lisp/package.lisp b/examples/tilt-sensor/lisp/package.lisp new file mode 100644 index 0000000..5388faa --- /dev/null +++ b/examples/tilt-sensor/lisp/package.lisp @@ -0,0 +1,7 @@ +(defpackage :maze + (:use :cl :qml) + (:export + #:*maze* + #:ini + #:new-maze)) + diff --git a/examples/tilt-sensor/lisp/ui-vars.lisp b/examples/tilt-sensor/lisp/ui-vars.lisp new file mode 100644 index 0000000..061470d --- /dev/null +++ b/examples/tilt-sensor/lisp/ui-vars.lisp @@ -0,0 +1,13 @@ +(defpackage ui + (:use :cl :qml) + (:export + #:*ball* + #:*maze* + #:*timer*)) + +(in-package :ui) + +(defparameter *ball* "ball") +(defparameter *maze* "maze") +(defparameter *timer* "timer") + diff --git a/examples/tilt-sensor/platforms/android/AndroidManifest.xml b/examples/tilt-sensor/platforms/android/AndroidManifest.xml new file mode 100644 index 0000000..f3ce14b --- /dev/null +++ b/examples/tilt-sensor/platforms/android/AndroidManifest.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/tilt-sensor/qml/main.qml b/examples/tilt-sensor/qml/main.qml new file mode 100644 index 0000000..33f33de --- /dev/null +++ b/examples/tilt-sensor/qml/main.qml @@ -0,0 +1,74 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtSensors 5.15 + +Rectangle { + width: 400 + height: 600 + color: "#333" + + Rectangle { + anchors.centerIn: parent + anchors.verticalCenterOffset: -25 + width: grid.width + height: grid.height + color: "white" + + Rectangle { + objectName: "ball" + width: 13 + height: width + radius: width / 2 + color: "#c33" + } + } + + Grid { + id: grid + anchors.centerIn: parent + anchors.verticalCenterOffset: -25 + columns: 25 + rows: 39 + columnSpacing: 1 + rowSpacing: 1 + + Repeater { + id: maze + objectName: "maze" + model: parent.columns * parent.rows + + Rectangle { + width: 12 + height: width + color: "steelblue" + radius: width / 5 + } + } + } + + RoundButton { + id: button + anchors.bottom: parent.bottom + anchors.bottomMargin: 7 + anchors.horizontalCenter: parent.horizontalCenter + text: "Change Maze" + + onClicked: Lisp.call("maze:new-game") + } + + // sensor and timer + + TiltSensor { + id: tilt + active: true + } + + Timer { + objectName: "timer" + interval: 30 + repeat: true + + // reversed: x axis changes y position, y axis changes x position + onTriggered: Lisp.call("maze:move", tilt.reading.yRotation, tilt.reading.xRotation) + } +} diff --git a/examples/tilt-sensor/readme.md b/examples/tilt-sensor/readme.md new file mode 100644 index 0000000..4d87059 --- /dev/null +++ b/examples/tilt-sensor/readme.md @@ -0,0 +1,35 @@ + +Prepare +------- + +Please copy the app template files first: +``` +$ cd .. +$ ./copy.sh tilt-sensor +``` + + +Info +---- + +This shows how to read mobile device sensor data from QML, combined with a +`Timer`. Note also how a `Repeater` simplifies the UI. + + + +Run +--- +``` +lqml run.lisp +``` +Optionally pass `-slime` to start a Swank server, and connect from Emacs with +`M-x slime-connect`. + +During development you can pass `-auto`, which will releoad all QML files after +you made a change to any of them and saved it. For re-initialization after +reloading, file `lisp/qml-reload/on-reloaded` will be loaded. + +Closing the window quits the app. If you try to kill it with `ctrl-c`, you need +an additional `ctrl-d` to exit from ECL. To quit from Slime, do `(qq)` which is +short for `(qquit)`. +