mirror of
https://gitlab.com/eql/lqml.git
synced 2025-12-06 02:30:38 -08:00
add new example 'advanced-qml-auto-reload' (single file reload); revisions
This commit is contained in:
parent
5f3b251a69
commit
1473ec8271
28 changed files with 1144 additions and 2 deletions
2
examples/advanced-qml-auto-reload/.gitignore
vendored
Normal file
2
examples/advanced-qml-auto-reload/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
10
examples/advanced-qml-auto-reload/app.asd
Normal file
10
examples/advanced-qml-auto-reload/app.asd
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
(defsystem :app
|
||||||
|
:serial t
|
||||||
|
:depends-on ()
|
||||||
|
:components ((:file "lisp/package")
|
||||||
|
(:file "lisp/ui-vars")
|
||||||
|
(:file "lisp/swank-quicklisp")
|
||||||
|
(:file "lisp/eval")
|
||||||
|
(:file "lisp/qml-reload/auto-reload-mobile")
|
||||||
|
(:file "lisp/main")))
|
||||||
|
|
||||||
69
examples/advanced-qml-auto-reload/app.pro
Normal file
69
examples/advanced-qml-auto-reload/app.pro
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
LISP_FILES = $$files(lisp/*) app.asd make.lisp
|
||||||
|
|
||||||
|
android {
|
||||||
|
lisp.commands = $$(ECL_ANDROID)/../ecl-android-host/bin/ecl \
|
||||||
|
-norc -shell $$PWD/make.lisp
|
||||||
|
} else:ios {
|
||||||
|
lisp.commands = $$(ECL_IOS)/../ecl-ios-host/bin/ecl \
|
||||||
|
-norc -shell $$PWD/make.lisp
|
||||||
|
} else:unix {
|
||||||
|
lisp.commands = /usr/local/bin/ecl -shell $$PWD/make.lisp
|
||||||
|
}
|
||||||
|
|
||||||
|
lisp.input = LISP_FILES
|
||||||
|
lisp.output = tmp/libapp.a
|
||||||
|
|
||||||
|
QMAKE_EXTRA_COMPILERS += lisp
|
||||||
|
PRE_TARGETDEPS += tmp/libapp.a
|
||||||
|
|
||||||
|
QT += quick qml
|
||||||
|
TEMPLATE = app
|
||||||
|
CONFIG += no_keywords release
|
||||||
|
DEFINES += INI_LISP
|
||||||
|
INCLUDEPATH = /usr/local/include
|
||||||
|
LIBS = -L/usr/local/lib -lecl
|
||||||
|
DESTDIR = .
|
||||||
|
TARGET = app
|
||||||
|
OBJECTS_DIR = tmp
|
||||||
|
MOC_DIR = tmp
|
||||||
|
|
||||||
|
linux: LIBS += -L../../../platforms/linux/lib
|
||||||
|
macx: LIBS += -L../../../platforms/macos/lib
|
||||||
|
|
||||||
|
android {
|
||||||
|
QT += androidextras
|
||||||
|
INCLUDEPATH = $$(ECL_ANDROID)/include
|
||||||
|
LIBS = -L$$(ECL_ANDROID)/lib -lecl
|
||||||
|
LIBS += -L../../../platforms/android/lib
|
||||||
|
|
||||||
|
ANDROID_ABIS = "arm64-v8a"
|
||||||
|
ANDROID_EXTRA_LIBS += $$(ECL_ANDROID)/lib/libecl.so
|
||||||
|
ANDROID_PACKAGE_SOURCE_DIR = ../platforms/android
|
||||||
|
}
|
||||||
|
|
||||||
|
ios {
|
||||||
|
DEFINES += INI_ECL_CONTRIB
|
||||||
|
INCLUDEPATH = $$(ECL_IOS)/include
|
||||||
|
ECL_VERSION = $$lower($$system($ECL_IOS/../ecl-ios-host/bin/ecl -v))
|
||||||
|
ECL_VERSION = $$replace(ECL_VERSION, " ", "-")
|
||||||
|
LIBS = -L$$(ECL_IOS)/lib -lecl
|
||||||
|
LIBS += -leclatomic -leclffi -leclgc -leclgmp
|
||||||
|
LIBS += -L$$(ECL_IOS)/lib/$$ECL_VERSION
|
||||||
|
LIBS += -lasdf -lecl-help -ldeflate -lecl-cdb -lecl-curl -lql-minitar -lsockets
|
||||||
|
LIBS += -L../../../platforms/ios/lib
|
||||||
|
|
||||||
|
assets.files = $$files($$PWD/platforms/ios/assets)
|
||||||
|
QMAKE_BUNDLE_DATA += assets
|
||||||
|
}
|
||||||
|
|
||||||
|
LIBS += -llqml -llisp -Ltmp -lapp
|
||||||
|
HEADERS += ../../src/cpp/main.h
|
||||||
|
SOURCES += ../../src/cpp/main.cpp
|
||||||
|
|
||||||
|
system(ecl -shell qml/.create-qml-loaders.lisp)
|
||||||
|
|
||||||
|
RESOURCES += $$files(qml/*)
|
||||||
|
RESOURCES += $$files(qml/.ext/*)
|
||||||
|
|
||||||
|
QMAKE_CXXFLAGS += -std=c++17
|
||||||
|
|
||||||
20
examples/advanced-qml-auto-reload/cgi-bin/qml-last-modified.py
Executable file
20
examples/advanced-qml-auto-reload/cgi-bin/qml-last-modified.py
Executable file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
max_secs = 0
|
||||||
|
edited_file = ''
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk('qml'):
|
||||||
|
for name in files:
|
||||||
|
if name.endswith('.qml'):
|
||||||
|
current_file = os.path.join(root, name)
|
||||||
|
secs = int(os.path.getctime(os.path.abspath(current_file)))
|
||||||
|
total += secs
|
||||||
|
if (secs > max_secs):
|
||||||
|
max_secs = secs
|
||||||
|
edited_file = current_file
|
||||||
|
|
||||||
|
print('Content-type:text/plain\r\n\r\n')
|
||||||
|
print(str(total) + '\r\n' + edited_file[4:])
|
||||||
28
examples/advanced-qml-auto-reload/lisp/curl.lisp
Normal file
28
examples/advanced-qml-auto-reload/lisp/curl.lisp
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
(in-package :qml)
|
||||||
|
|
||||||
|
(defun curl (url)
|
||||||
|
"args: (url)
|
||||||
|
Trivial download of UTF-8 encoded files, or binary files."
|
||||||
|
(multiple-value-bind (response headers stream)
|
||||||
|
(loop
|
||||||
|
(multiple-value-bind (response headers stream)
|
||||||
|
(ecl-curl::url-connection url)
|
||||||
|
(unless (member response '(301 302))
|
||||||
|
(return (values response headers stream)))
|
||||||
|
(close stream)
|
||||||
|
(setf url (header-value :location headers))))
|
||||||
|
(if (>= response 400)
|
||||||
|
(qlog "curl download error:" :url url :response response)
|
||||||
|
(let ((byte-array (make-array 0 :adjustable t :fill-pointer t
|
||||||
|
:element-type '(unsigned-byte 8)))
|
||||||
|
(type (pathname-type url)))
|
||||||
|
(x:while-it (read-byte stream nil nil)
|
||||||
|
(vector-push-extend x:it byte-array))
|
||||||
|
(close stream)
|
||||||
|
(if (or (search type "txt html lisp")
|
||||||
|
(search "/cgi-bin/" (namestring url)))
|
||||||
|
(qfrom-utf8 byte-array)
|
||||||
|
byte-array)))))
|
||||||
|
|
||||||
|
(export 'curl)
|
||||||
|
|
||||||
181
examples/advanced-qml-auto-reload/lisp/eval.lisp
Normal file
181
examples/advanced-qml-auto-reload/lisp/eval.lisp
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
(defpackage :eval
|
||||||
|
(:use :cl :qml)
|
||||||
|
(:export
|
||||||
|
#:*eval-thread*
|
||||||
|
#:append-output
|
||||||
|
#:eval-in-thread))
|
||||||
|
|
||||||
|
(in-package :eval)
|
||||||
|
|
||||||
|
(defvar *output-buffer* (make-string-output-stream))
|
||||||
|
(defvar *prompt* t)
|
||||||
|
(defvar *eval-thread* nil)
|
||||||
|
(defvar * nil)
|
||||||
|
(defvar ** nil)
|
||||||
|
(defvar *** nil)
|
||||||
|
|
||||||
|
(defun ini-streams ()
|
||||||
|
(setf *standard-output* (make-broadcast-stream *standard-output*
|
||||||
|
*output-buffer*))
|
||||||
|
(setf *trace-output* *standard-output*
|
||||||
|
*error-output* *standard-output*))
|
||||||
|
|
||||||
|
(defun current-package-name ()
|
||||||
|
(if (eql (find-package :cl-user) *package*)
|
||||||
|
"CL-USER"
|
||||||
|
(car (sort (list* (package-name *package*) (package-nicknames *package*))
|
||||||
|
(lambda (x y) (< (length x) (length y)))))))
|
||||||
|
|
||||||
|
(let ((n -1))
|
||||||
|
(defun eval-in-thread (text &optional (progress t)) ; called from QML
|
||||||
|
(let ((str (string-trim " " text)))
|
||||||
|
(unless (x:empty-string str)
|
||||||
|
(if *prompt*
|
||||||
|
(let ((pkg (if (zerop n) "QML-USER" (current-package-name)))
|
||||||
|
(counter (princ-to-string (incf n))))
|
||||||
|
(format t "~A [~A]~%~A"
|
||||||
|
pkg
|
||||||
|
counter
|
||||||
|
str))
|
||||||
|
(format t "~%~%~A" str))
|
||||||
|
;; run eval in its own thread, so UI will remain responsive
|
||||||
|
(update-output t)
|
||||||
|
(when progress
|
||||||
|
(show-progress-bar))
|
||||||
|
(qsingle-shot 50 (lambda ()
|
||||||
|
(setf *eval-thread*
|
||||||
|
(mp:process-run-function "LQML REPL top-level"
|
||||||
|
(lambda () (do-eval str))))))))))
|
||||||
|
|
||||||
|
(defvar *color-text* "#c0c0c0")
|
||||||
|
(defvar *color-values* "#80b0ff")
|
||||||
|
(defvar *color-read-error* "orange")
|
||||||
|
(defvar *color-error* "#ff8080")
|
||||||
|
|
||||||
|
#+ios
|
||||||
|
(defun escape-smart-quotation (string)
|
||||||
|
(dotimes (i (length string))
|
||||||
|
(case (char-code (char string i))
|
||||||
|
((8216 8217 8218)
|
||||||
|
(setf (char string i) #\'))
|
||||||
|
((171 187 8220 8221 8222)
|
||||||
|
(setf (char string i) #\"))))
|
||||||
|
string)
|
||||||
|
|
||||||
|
(defun do-eval (string)
|
||||||
|
(let ((str #+ios (escape-smart-quotation string)
|
||||||
|
#-ios string)
|
||||||
|
(color *color-read-error*))
|
||||||
|
(handler-case
|
||||||
|
(let ((exp (read-from-string str)))
|
||||||
|
(setf color *color-error*)
|
||||||
|
(let ((vals (multiple-value-list (eval exp))))
|
||||||
|
(setf *** ** ** * * (first vals))
|
||||||
|
(update-output)
|
||||||
|
(append-output (format nil "~{~S~^~%~}" vals) *color-values* t))
|
||||||
|
(q! |clear| ui:*repl-input*)
|
||||||
|
(history-add str))
|
||||||
|
(condition (c)
|
||||||
|
(show-error c color))))
|
||||||
|
(qsingle-shot 50 'eval-exited))
|
||||||
|
|
||||||
|
(defun eval-exited ()
|
||||||
|
(update-output)
|
||||||
|
(show-progress-bar nil))
|
||||||
|
|
||||||
|
(defun show-error (error color)
|
||||||
|
(let ((e1 (prin1-to-string error))
|
||||||
|
(e2 (princ-to-string error)))
|
||||||
|
(append-output e1 color)
|
||||||
|
(unless (string= e1 e2)
|
||||||
|
(append-output e2 color))))
|
||||||
|
|
||||||
|
(defun show-progress-bar (&optional (show t))
|
||||||
|
(q> |visible| ui:*progress* show))
|
||||||
|
|
||||||
|
;;; output
|
||||||
|
|
||||||
|
(defun update-output (&optional line)
|
||||||
|
(let ((text (get-output-stream-string *output-buffer*)))
|
||||||
|
(unless (x:empty-string text)
|
||||||
|
(let ((err (search "[LQML:err]" text)))
|
||||||
|
(qjs |appendText| ui:*repl-model*
|
||||||
|
(list :m-text (if err (subseq text err) text)
|
||||||
|
:m-color (if err *color-error* *color-text*)
|
||||||
|
:m-bold nil
|
||||||
|
:m-line line))))))
|
||||||
|
|
||||||
|
(defun append-output (text &optional (color *color-text*) bold)
|
||||||
|
(qjs |appendText| ui:*repl-model*
|
||||||
|
(list :m-text text
|
||||||
|
:m-color color
|
||||||
|
:m-bold bold
|
||||||
|
:m-line nil)))
|
||||||
|
|
||||||
|
;;; command history
|
||||||
|
|
||||||
|
(defvar *history* (make-array 0 :adjustable t :fill-pointer t))
|
||||||
|
(defvar *history-index* nil)
|
||||||
|
(defvar *history-file* ".lqml-repl-history")
|
||||||
|
(defvar *max-history* 100)
|
||||||
|
|
||||||
|
(defun read-saved-history ()
|
||||||
|
(when (probe-file *history-file*)
|
||||||
|
(let ((i -1))
|
||||||
|
(labels ((index ()
|
||||||
|
(mod i *max-history*))
|
||||||
|
(next-index ()
|
||||||
|
(incf i)
|
||||||
|
(index)))
|
||||||
|
(let ((tmp (make-array *max-history*))) ; ring buffer
|
||||||
|
(with-open-file (s *history-file*)
|
||||||
|
(x:while-it (read-line s nil nil)
|
||||||
|
(setf (svref tmp (next-index)) x:it)))
|
||||||
|
(let ((max (min (1+ i) *max-history*)))
|
||||||
|
(when (< max *max-history*)
|
||||||
|
(setf i -1))
|
||||||
|
(dotimes (n max)
|
||||||
|
(vector-push-extend (svref tmp (next-index))
|
||||||
|
*history*))
|
||||||
|
(setf *history-index* (length *history*)))))))) ; 1 after last
|
||||||
|
|
||||||
|
(let (out)
|
||||||
|
(defun history-ini ()
|
||||||
|
(read-saved-history)
|
||||||
|
(setf out (open *history-file* :direction :output
|
||||||
|
:if-exists :append :if-does-not-exist :create)))
|
||||||
|
(defun history-add (line)
|
||||||
|
(unless out
|
||||||
|
(history-ini))
|
||||||
|
(let ((len (length *history*)))
|
||||||
|
(when (or (zerop len)
|
||||||
|
(string/= line (aref *history* (1- len))))
|
||||||
|
(vector-push-extend line *history*)
|
||||||
|
(write-line line out)
|
||||||
|
(finish-output out)))
|
||||||
|
(setf *history-index* (length *history*))) ; 1 after last
|
||||||
|
(defun history-move (direction)
|
||||||
|
(unless out
|
||||||
|
(history-ini))
|
||||||
|
(when (and *history-index*
|
||||||
|
(plusp (length *history*)))
|
||||||
|
(setf *history-index* (if (string= "back" direction)
|
||||||
|
(max (1- *history-index*) 0)
|
||||||
|
(min (1+ *history-index*) (1- (length *history*)))))
|
||||||
|
(let ((text (aref *history* *history-index*)))
|
||||||
|
(q> |text| ui:*repl-input* text)
|
||||||
|
(q> |cursorPosition| ui:*repl-input*
|
||||||
|
(- (length text) (if (x:ends-with ")" text) 1 0)))))))
|
||||||
|
|
||||||
|
(defun qml::help ()
|
||||||
|
(format t "~%~
|
||||||
|
~% :s (start-swank)~
|
||||||
|
~% :q (quicklisp)")
|
||||||
|
(values))
|
||||||
|
|
||||||
|
(progn
|
||||||
|
(ini-streams)
|
||||||
|
(qlater (lambda ()
|
||||||
|
(in-package :qml-user)
|
||||||
|
(eval-in-thread "(qml::help)" nil))))
|
||||||
|
|
||||||
5
examples/advanced-qml-auto-reload/lisp/main.lisp
Normal file
5
examples/advanced-qml-auto-reload/lisp/main.lisp
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
(in-package :app)
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(when qml::*remote-ip*
|
||||||
|
(qsingle-shot 1000 'auto-reload-qml))
|
||||||
4
examples/advanced-qml-auto-reload/lisp/package.lisp
Normal file
4
examples/advanced-qml-auto-reload/lisp/package.lisp
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
(defpackage :app
|
||||||
|
(:use :cl :qml)
|
||||||
|
(:export))
|
||||||
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
;;; trivial QML auto reload during development for mobile
|
||||||
|
|
||||||
|
(in-package :qml)
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(defvar *reload-all* nil)
|
||||||
|
(defvar *edited-file* nil)
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(defun remote-ip ()
|
||||||
|
(terpri *query-io*)
|
||||||
|
(princ "Please enter WiFi IP of desktop computer (hit RET to skip): "
|
||||||
|
*query-io*)
|
||||||
|
(let ((ip (read-line *query-io*)))
|
||||||
|
(unless (x:empty-string ip)
|
||||||
|
(format nil "http://~A:8080/" ip))))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(defvar *remote-ip* #+interpreter nil
|
||||||
|
#-interpreter #.(remote-ip))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(defun qml:view-status-changed (status)
|
||||||
|
(when (and (= 1 status)
|
||||||
|
(reload-main-p))
|
||||||
|
(load (make-string-input-stream
|
||||||
|
(funcall (%sym 'curl :qml)
|
||||||
|
(x:cc *remote-ip* "lisp/qml-reload/on-reloaded.lisp"))))))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(defun reload-main-p ()
|
||||||
|
(or *reload-all*
|
||||||
|
(string= "main.qml" *edited-file*)))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(let ((load t)
|
||||||
|
(secs 0)
|
||||||
|
(ini t))
|
||||||
|
(defun auto-reload-qml ()
|
||||||
|
(when load
|
||||||
|
(setf load nil)
|
||||||
|
(require :ecl-curl)
|
||||||
|
(load "curl"))
|
||||||
|
(let ((curr/file (x:split (funcall (%sym 'curl :qml)
|
||||||
|
(x:cc *remote-ip* "cgi-bin/qml-last-modified.py"))
|
||||||
|
#.(coerce (list #\Return #\Newline) 'string))))
|
||||||
|
(when (= 2 (length curr/file))
|
||||||
|
(destructuring-bind (curr file)
|
||||||
|
curr/file
|
||||||
|
(setf curr (parse-integer curr)
|
||||||
|
*edited-file* (string-right-trim '(#\Return #\Newline) file))
|
||||||
|
(when (/= secs curr)
|
||||||
|
(if ini
|
||||||
|
(progn
|
||||||
|
(setf ini nil)
|
||||||
|
(qset *engine* |baseUrl| *remote-ip*)
|
||||||
|
(let ((src (qget *quick-view* |source|)))
|
||||||
|
(qset *quick-view* |source| (subseq src #.(length "qrc:///")))))
|
||||||
|
(if (reload-main-p)
|
||||||
|
(qml:reload)
|
||||||
|
(qjs |reload| *edited-file*)))
|
||||||
|
(setf secs curr)))))
|
||||||
|
(qsingle-shot 250 'auto-reload-qml)))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(export 'auto-reload-qml)
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
;;; trivial QML auto reload during development (desktop only), see:
|
||||||
|
;;;
|
||||||
|
;;; lqml run.lisp -auto
|
||||||
|
|
||||||
|
(in-package :qml-user)
|
||||||
|
|
||||||
|
(defvar *reload-all* nil)
|
||||||
|
(defvar *edited-file* nil)
|
||||||
|
|
||||||
|
(defparameter *dir* *load-truename*)
|
||||||
|
|
||||||
|
(defun reload-main-p ()
|
||||||
|
(or *reload-all*
|
||||||
|
(string= "main.qml" *edited-file*)))
|
||||||
|
|
||||||
|
(defun qml:view-status-changed (status)
|
||||||
|
(when (and (= 1 status)
|
||||||
|
(reload-main-p))
|
||||||
|
(load (merge-pathnames "on-reloaded" *dir*))))
|
||||||
|
|
||||||
|
(let ((secs 0))
|
||||||
|
(defun watch-files ()
|
||||||
|
(let ((sum 0)
|
||||||
|
(max 0)
|
||||||
|
files)
|
||||||
|
;; don't cache, files might be added/removed during development
|
||||||
|
(dolist (file (directory (merge-pathnames "../../qml/**/*.qml" *dir*)))
|
||||||
|
(unless (search "/." (namestring file))
|
||||||
|
(push file files)))
|
||||||
|
(dolist (file files)
|
||||||
|
(let ((date (file-write-date file)))
|
||||||
|
(when (> date max)
|
||||||
|
(setf max date
|
||||||
|
*edited-file* file))
|
||||||
|
(incf sum date)))
|
||||||
|
(let ((edited (namestring *edited-file*)))
|
||||||
|
(setf *edited-file*
|
||||||
|
(subseq edited (+ 5 (search "/qml/" edited)))))
|
||||||
|
(when (/= secs sum)
|
||||||
|
(unless (zerop secs)
|
||||||
|
(if (reload-main-p)
|
||||||
|
(qml:reload)
|
||||||
|
(qjs |reload| *edited-file*)))
|
||||||
|
(setf secs sum)))
|
||||||
|
(qsingle-shot 250 'watch-files)))
|
||||||
|
|
||||||
|
(progn
|
||||||
|
(load "qml/.create-qml-loaders")
|
||||||
|
(watch-files))
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
;;; this file will be loaded every time QML has been reloaded
|
||||||
|
|
||||||
|
(in-package :qml-user)
|
||||||
|
|
||||||
|
;; delay needed here
|
||||||
|
(qsingle-shot 500 (lambda () (eval:eval-in-thread "(qml::help)"))) ; show help in REPL
|
||||||
190
examples/advanced-qml-auto-reload/lisp/swank-quicklisp.lisp
Normal file
190
examples/advanced-qml-auto-reload/lisp/swank-quicklisp.lisp
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
;;; enable Swank and Quicklisp on mobile
|
||||||
|
|
||||||
|
(in-package :qml)
|
||||||
|
|
||||||
|
;; for mobile app updates:
|
||||||
|
;; to be incremented on every ECL upgrade in order to replace all asset files
|
||||||
|
#+(or android ios)
|
||||||
|
(defconstant +app-version+ 1)
|
||||||
|
|
||||||
|
#+(and ios (not interpreter))
|
||||||
|
(ffi:clines "extern void init_lib_ASDF(cl_object);")
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(defvar *assets* #+android "assets:/lib/"
|
||||||
|
#+ios "assets/")
|
||||||
|
|
||||||
|
#+ios
|
||||||
|
(defvar *bundle-root* (namestring *default-pathname-defaults*))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(defun copy-asset-files (&optional (dir-name *assets*) origin)
|
||||||
|
"Copy asset files to home directory."
|
||||||
|
(flet ((directory-p (path)
|
||||||
|
(x:ends-with "/" path))
|
||||||
|
(translate (name)
|
||||||
|
#+android
|
||||||
|
(if (x:starts-with *assets* name)
|
||||||
|
(subseq name (length *assets*))
|
||||||
|
name)
|
||||||
|
#+ios
|
||||||
|
(namestring
|
||||||
|
(merge-pathnames (x:cc "../" (subseq name (length origin)))))))
|
||||||
|
(ensure-directories-exist (translate dir-name))
|
||||||
|
;; note: both QDIRECTORY and QCOPY-FILE are prepared for accessing
|
||||||
|
;; APK asset files, which can't be accessed directly from Lisp
|
||||||
|
(dolist (from (qdirectory dir-name))
|
||||||
|
(if (directory-p from)
|
||||||
|
(copy-asset-files from origin)
|
||||||
|
(let ((to (translate from)))
|
||||||
|
(when (probe-file to)
|
||||||
|
(delete-file to))
|
||||||
|
(unless (qcopy-file from to)
|
||||||
|
(qlog "Error copying asset file: ~S" from)
|
||||||
|
(return-from copy-asset-files))))))
|
||||||
|
t)
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(let ((file ".app-version"))
|
||||||
|
(defun app-version ()
|
||||||
|
(if (probe-file file)
|
||||||
|
(with-open-file (s file)
|
||||||
|
(let ((str (make-string (file-length s))))
|
||||||
|
(read-sequence str s)
|
||||||
|
(values (parse-integer str))))
|
||||||
|
0))
|
||||||
|
(defun save-app-version ()
|
||||||
|
(with-open-file (s file :direction :output :if-exists :supersede)
|
||||||
|
(princ +app-version+ s))
|
||||||
|
(values)))
|
||||||
|
|
||||||
|
(defun %sym (symbol package)
|
||||||
|
(intern (symbol-name symbol) package))
|
||||||
|
|
||||||
|
;;; Quicklisp setup
|
||||||
|
|
||||||
|
#+ios
|
||||||
|
(defun load-asdf ()
|
||||||
|
(unless (find-package :asdf)
|
||||||
|
;; needed for ASDF and Quicklisp
|
||||||
|
(setf (logical-pathname-translations "SYS")
|
||||||
|
(list (list "sys:**;*.*"
|
||||||
|
(merge-pathnames "**/*.*" (user-homedir-pathname)))
|
||||||
|
(list "home:**;*.*"
|
||||||
|
(merge-pathnames "**/*.*" (user-homedir-pathname)))))
|
||||||
|
(ffi:c-inline nil nil :void "ecl_init_module(NULL, init_lib_ASDF)" :one-liner t)
|
||||||
|
(in-package :qml-user))
|
||||||
|
:asdf)
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(defun ensure-asdf ()
|
||||||
|
(unless (find-package :asdf)
|
||||||
|
#+android
|
||||||
|
(require :asdf)
|
||||||
|
#+ios
|
||||||
|
(load-asdf)))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(defun quicklisp ()
|
||||||
|
(ensure-asdf)
|
||||||
|
(unless (find-package :quicklisp)
|
||||||
|
#+android
|
||||||
|
(progn
|
||||||
|
(require :ecl-quicklisp)
|
||||||
|
(require :deflate)
|
||||||
|
(require :ql-minitar))
|
||||||
|
#+ios
|
||||||
|
(load "quicklisp/setup")
|
||||||
|
;; replace interpreted function with precompiled one from DEFLATE
|
||||||
|
(setf (symbol-function (%sym 'gunzip :ql-gunzipper))
|
||||||
|
(symbol-function (%sym 'gunzip :deflate)))
|
||||||
|
(in-package :qml-user))
|
||||||
|
:quicklisp)
|
||||||
|
|
||||||
|
;;; Swank setup
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(defun swank/create-server (interface port dont-close style)
|
||||||
|
(funcall (%sym 'create-server :swank)
|
||||||
|
:interface interface
|
||||||
|
:port port
|
||||||
|
:dont-close dont-close
|
||||||
|
:style style))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(defun start-swank (&key (port 4005) (interface "0.0.0.0") (style :spawn)
|
||||||
|
(load-contribs t) (setup t) (delete t) (quiet t)
|
||||||
|
(dont-close t) log-events)
|
||||||
|
(unless (find-package :swank)
|
||||||
|
(ensure-asdf)
|
||||||
|
(funcall (%sym 'load-system :asdf) :swank))
|
||||||
|
(funcall (%sym 'init :swank-loader)
|
||||||
|
:load-contribs load-contribs
|
||||||
|
:setup setup
|
||||||
|
:delete delete
|
||||||
|
:quiet quiet)
|
||||||
|
(setf (symbol-value (%sym '*log-events* :swank)) log-events)
|
||||||
|
(eval (read-from-string "(swank/backend:defimplementation swank/backend:lisp-implementation-program () \"org.lisp.ecl\")"))
|
||||||
|
(if (eql :spawn style)
|
||||||
|
(swank/create-server interface port dont-close style)
|
||||||
|
(mp:process-run-function
|
||||||
|
"SLIME-listener"
|
||||||
|
(lambda () (swank/create-server interface port dont-close style)))))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(defun stop-swank (&optional (port 4005))
|
||||||
|
(when (find-package :swank)
|
||||||
|
(funcall (%sym 'stop-server :swank) port)
|
||||||
|
:stopped))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(progn
|
||||||
|
;; be careful not to use :s, :q in your mobile app code
|
||||||
|
;; ios simulator note: wrap :s and :q in qrun* (would crash otherwise)
|
||||||
|
(define-symbol-macro :s (start-swank))
|
||||||
|
(define-symbol-macro :q (quicklisp)))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(export (list #+ios
|
||||||
|
'load-asdf
|
||||||
|
'start-swank
|
||||||
|
'stop-swank
|
||||||
|
'quicklisp))
|
||||||
|
|
||||||
|
#+ios
|
||||||
|
(progn
|
||||||
|
;; adapt paths to iOS specific values
|
||||||
|
(defvar *user-homedir-pathname-orig* (symbol-function 'user-homedir-pathname))
|
||||||
|
|
||||||
|
(ext:package-lock :common-lisp nil)
|
||||||
|
|
||||||
|
(defun cl:user-homedir-pathname (&optional host)
|
||||||
|
(merge-pathnames "Library/" (funcall *user-homedir-pathname-orig* host)))
|
||||||
|
|
||||||
|
(ext:package-lock :common-lisp t)
|
||||||
|
|
||||||
|
(dolist (el '(("XDG_DATA_HOME" . "")
|
||||||
|
("XDG_CONFIG_HOME" . "")
|
||||||
|
("XDG_DATA_DIRS" . "")
|
||||||
|
("XDG_CONFIG_DIRS" . "")
|
||||||
|
("XDG_CACHE_HOME" . ".cache")))
|
||||||
|
(ext:setenv (car el) (namestring (merge-pathnames (cdr el)
|
||||||
|
(user-homedir-pathname))))))
|
||||||
|
|
||||||
|
;;; ini
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(defun startup-ini ()
|
||||||
|
#+ios
|
||||||
|
(setf *default-pathname-defaults* (user-homedir-pathname))
|
||||||
|
(ext:install-bytecodes-compiler)
|
||||||
|
(and (/= +app-version+ (app-version))
|
||||||
|
#+ios
|
||||||
|
(let ((dir (namestring (merge-pathnames *assets* *bundle-root*))))
|
||||||
|
(copy-asset-files dir dir))
|
||||||
|
#+android
|
||||||
|
(copy-asset-files)
|
||||||
|
(save-app-version)))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(qlater 'startup-ini)
|
||||||
20
examples/advanced-qml-auto-reload/lisp/ui-vars.lisp
Normal file
20
examples/advanced-qml-auto-reload/lisp/ui-vars.lisp
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
(defpackage ui
|
||||||
|
(:use :cl :qml)
|
||||||
|
(:export
|
||||||
|
#:*flick-output*
|
||||||
|
#:*history-back*
|
||||||
|
#:*history-forward*
|
||||||
|
#:*progress*
|
||||||
|
#:*repl-input*
|
||||||
|
#:*repl-output*
|
||||||
|
#:*repl-model*))
|
||||||
|
|
||||||
|
(in-package :ui)
|
||||||
|
|
||||||
|
(defparameter *flick-output* "flick_output")
|
||||||
|
(defparameter *history-back* "history_back")
|
||||||
|
(defparameter *history-forward* "history_forward")
|
||||||
|
(defparameter *progress* "progress")
|
||||||
|
(defparameter *repl-input* "repl_input")
|
||||||
|
(defparameter *repl-output* "repl_output")
|
||||||
|
(defparameter *repl-model* "repl_model")
|
||||||
104
examples/advanced-qml-auto-reload/make.lisp
Normal file
104
examples/advanced-qml-auto-reload/make.lisp
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
;;; check target
|
||||||
|
|
||||||
|
(let ((arg (first (ext:command-args))))
|
||||||
|
(mapc (lambda (name feature)
|
||||||
|
(when (search name arg)
|
||||||
|
(pushnew feature *features*)))
|
||||||
|
(list "/ecl-android/" "/ecl-ios/")
|
||||||
|
(list :android :ios)))
|
||||||
|
|
||||||
|
;;; copy Swank and ECL contrib files (mobile only)
|
||||||
|
|
||||||
|
(defun cc (&rest args)
|
||||||
|
(apply 'concatenate 'string args))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(defvar *assets* #+android "../platforms/android/assets/lib/"
|
||||||
|
#+ios "../platforms/ios/assets/Library/")
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(defun find-swank ()
|
||||||
|
(probe-file (cc *assets* "quicklisp/local-projects/slime/swank.lisp")))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(defun shell (command)
|
||||||
|
(ext:run-program "sh" (list "-c" command)))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(progn
|
||||||
|
(unless (find-swank)
|
||||||
|
(let ((to (cc *assets* "quicklisp/local-projects/slime/")))
|
||||||
|
(ensure-directories-exist to)
|
||||||
|
(shell (cc "cp -r ../../../slime/src/* " to))))
|
||||||
|
(unless (probe-file (cc *assets* "encodings"))
|
||||||
|
#+android
|
||||||
|
(let ((lib (cc (ext:getenv "ECL_ANDROID") "/lib/ecl-*/")))
|
||||||
|
(shell (cc "cp " lib "*.asd " *assets*))
|
||||||
|
(shell (cc "cp " lib "*.fas " *assets*))
|
||||||
|
(shell (cc "cp " lib "*.doc " *assets*))
|
||||||
|
(shell (cc "cp -r " lib "encodings " *assets*)))
|
||||||
|
#+ios
|
||||||
|
(let ((lib (cc (ext:getenv "ECL_IOS") "/lib/ecl-*/")))
|
||||||
|
(shell (cc "cp " lib "*.doc " *assets*))
|
||||||
|
(shell (cc "cp -r " lib "encodings " *assets*)))))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(unless (find-swank)
|
||||||
|
(error "Swank files missing, please see <LQML root>/slime/src/readme-sources.md"))
|
||||||
|
|
||||||
|
;;; compile ASDF system
|
||||||
|
|
||||||
|
(require :asdf)
|
||||||
|
|
||||||
|
(push (merge-pathnames "../")
|
||||||
|
asdf:*central-registry*)
|
||||||
|
|
||||||
|
(setf *default-pathname-defaults*
|
||||||
|
(truename (merge-pathnames "../../../"))) ; LQML root
|
||||||
|
|
||||||
|
(defvar *current*
|
||||||
|
(let ((name (namestring *load-truename*)))
|
||||||
|
(subseq name
|
||||||
|
(length (namestring *default-pathname-defaults*))
|
||||||
|
(1+ (position #\/ name :from-end t)))))
|
||||||
|
|
||||||
|
;; load all LQML symbols
|
||||||
|
(dolist (file (list "package" "x" "ecl-ext" "ini" "qml"))
|
||||||
|
(load (merge-pathnames file "src/lisp/")))
|
||||||
|
|
||||||
|
#-(or android ios)
|
||||||
|
(progn
|
||||||
|
(asdf:make-build "app"
|
||||||
|
:monolithic t
|
||||||
|
:type :static-library
|
||||||
|
:move-here (cc *current* "build/tmp/")
|
||||||
|
:init-name "ini_app")
|
||||||
|
(let* ((from (cc *current* "build/tmp/app--all-systems.a"))
|
||||||
|
(to "libapp.a")
|
||||||
|
(to* (cc *current* "build/tmp/" to)))
|
||||||
|
(when (probe-file to*)
|
||||||
|
(delete-file to*))
|
||||||
|
(rename-file from to)))
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(progn
|
||||||
|
(pushnew :interpreter *features*)
|
||||||
|
(defvar *asdf-system* "app")
|
||||||
|
(defvar *ql-libs* (cc *current* "ql-libs.lisp"))
|
||||||
|
(defvar *init-name* "ini_app")
|
||||||
|
(defvar *library-name* (format nil "~Abuild-~A/tmp/app"
|
||||||
|
*current*
|
||||||
|
#+android "android"
|
||||||
|
#+ios "ios"))
|
||||||
|
(defvar *epilogue-code* nil)
|
||||||
|
(load "platforms/shared/make"))
|
||||||
|
|
||||||
|
;;; byte compile curl (delayed load)
|
||||||
|
|
||||||
|
#+(or android ios)
|
||||||
|
(progn
|
||||||
|
(require :ecl-curl)
|
||||||
|
(ext:install-bytecodes-compiler)
|
||||||
|
(compile-file (cc *current* "lisp/curl.lisp")
|
||||||
|
:output-file (cc *current* "lisp/" *assets* "curl.fasc")))
|
||||||
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
(in-package :cl-user)
|
||||||
|
|
||||||
|
(defparameter *dir* *load-truename*)
|
||||||
|
|
||||||
|
(defvar *template* (with-open-file (s (merge-pathnames ".template.qml" *dir*))
|
||||||
|
(let ((str (make-string (file-length s))))
|
||||||
|
(read-sequence str s)
|
||||||
|
str)))
|
||||||
|
|
||||||
|
(defun create-qml-loaders ()
|
||||||
|
(dolist (file (directory (merge-pathnames "ext/**/*.qml" *dir*)))
|
||||||
|
(let* ((name (namestring file))
|
||||||
|
(p (1+ (search "/ext/" name)))
|
||||||
|
(loader (concatenate 'string (subseq name 0 p) "." (subseq name p))))
|
||||||
|
(unless (probe-file loader)
|
||||||
|
(ensure-directories-exist loader)
|
||||||
|
(with-open-file (s loader :direction :output)
|
||||||
|
(let ((new (subseq name p)))
|
||||||
|
(format t "~&creating .~A~%" new)
|
||||||
|
(format s *template* (subseq name p))))))))
|
||||||
|
|
||||||
|
(create-qml-loaders)
|
||||||
15
examples/advanced-qml-auto-reload/qml/.template.qml
Normal file
15
examples/advanced-qml-auto-reload/qml/.template.qml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
objectName: "~A"
|
||||||
|
source: "../" + objectName // TODO: set 'baseUrl' to Engine, so we don't need to fiddle here
|
||||||
|
|
||||||
|
Component.onCompleted: if (width === 0) { anchors.fill = parent }
|
||||||
|
|
||||||
|
function reload() {
|
||||||
|
var src = source
|
||||||
|
source = ""
|
||||||
|
Engine.clearCache()
|
||||||
|
source = src
|
||||||
|
}
|
||||||
|
}
|
||||||
13
examples/advanced-qml-auto-reload/qml/ext/Page1.qml
Normal file
13
examples/advanced-qml-auto-reload/qml/ext/Page1.qml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
radius: 50
|
||||||
|
color: Qt.lighter("red", 1.5)
|
||||||
|
border.width: 10
|
||||||
|
border.color: "red"
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "<h2>page 1</h2>"
|
||||||
|
}
|
||||||
|
}
|
||||||
13
examples/advanced-qml-auto-reload/qml/ext/Page2.qml
Normal file
13
examples/advanced-qml-auto-reload/qml/ext/Page2.qml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
radius: 50
|
||||||
|
color: Qt.lighter("green", 3.0)
|
||||||
|
border.width: 10
|
||||||
|
border.color: "green"
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "<h2>page 2</h2>"
|
||||||
|
}
|
||||||
|
}
|
||||||
13
examples/advanced-qml-auto-reload/qml/ext/Page3.qml
Normal file
13
examples/advanced-qml-auto-reload/qml/ext/Page3.qml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
radius: 50
|
||||||
|
color: Qt.lighter("blue", 1.7)
|
||||||
|
border.width: 10
|
||||||
|
border.color: "blue"
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "<h2>page 3</h2>"
|
||||||
|
}
|
||||||
|
}
|
||||||
162
examples/advanced-qml-auto-reload/qml/ext/Repl.qml
Normal file
162
examples/advanced-qml-auto-reload/qml/ext/Repl.qml
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: repl
|
||||||
|
z: 2
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Row {
|
||||||
|
z: 1
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "REPL"
|
||||||
|
anchors.verticalCenter: show.verticalCenter
|
||||||
|
visible: !show.checked
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch {
|
||||||
|
id: show
|
||||||
|
|
||||||
|
onCheckedChanged: container.enabled = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: container
|
||||||
|
opacity: 0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: repl.parent.width
|
||||||
|
height: repl.parent.height / 4
|
||||||
|
color: "#101010"
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: replOutput
|
||||||
|
objectName: "repl_output"
|
||||||
|
anchors.fill: parent
|
||||||
|
contentWidth: parent.width * 4
|
||||||
|
clip: true
|
||||||
|
model: replModel
|
||||||
|
flickableDirection: Flickable.HorizontalAndVerticalFlick
|
||||||
|
|
||||||
|
delegate: Column {
|
||||||
|
Rectangle {
|
||||||
|
width: replOutput.contentWidth
|
||||||
|
height: 1
|
||||||
|
color: "#707070"
|
||||||
|
visible: mLine
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
x: 2
|
||||||
|
padding: 2
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
font.family: fontHack.name
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.bold: mBold
|
||||||
|
text: mText
|
||||||
|
color: mColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListModel {
|
||||||
|
id: replModel
|
||||||
|
objectName: "repl_model"
|
||||||
|
|
||||||
|
function appendText(data) {
|
||||||
|
append(data)
|
||||||
|
replOutput.contentX = 0
|
||||||
|
replOutput.positionViewAtEnd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: repl.parent.width
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: input
|
||||||
|
objectName: "repl_input"
|
||||||
|
width: repl.parent.width - 2 * back.width
|
||||||
|
font.family: fontHack.name
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.bold: true
|
||||||
|
color: "#c0c0c0"
|
||||||
|
inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
|
||||||
|
focus: show.checked
|
||||||
|
palette {
|
||||||
|
highlight: "#e0e0e0"
|
||||||
|
highlightedText: "#101010"
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: "#101010"
|
||||||
|
border.width: 2
|
||||||
|
border.color: "gray"
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccepted: Lisp.call("eval:eval-in-thread", text)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: back
|
||||||
|
objectName: "history_back"
|
||||||
|
width: 40
|
||||||
|
height: input.height
|
||||||
|
focusPolicy: Qt.NoFocus
|
||||||
|
font.family: fontIcons.name
|
||||||
|
font.pixelSize: 26
|
||||||
|
text: "\uf100"
|
||||||
|
|
||||||
|
onClicked: Lisp.call("eval:history-move", "back")
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 1
|
||||||
|
height: input.height
|
||||||
|
color: "#101010"
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: forward
|
||||||
|
objectName: "history_forward"
|
||||||
|
width: back.width
|
||||||
|
height: input.height
|
||||||
|
focusPolicy: Qt.NoFocus
|
||||||
|
font.family: fontIcons.name
|
||||||
|
font.pixelSize: 26
|
||||||
|
text: "\uf101"
|
||||||
|
|
||||||
|
onClicked: Lisp.call("eval:history-move", "forward")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: repl.parent.width
|
||||||
|
height: 1
|
||||||
|
color: "#101010"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressBar {
|
||||||
|
objectName: "progress"
|
||||||
|
anchors.top: container.bottom
|
||||||
|
width: repl.width
|
||||||
|
z: 1
|
||||||
|
indeterminate: true
|
||||||
|
enabled: visible
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State { when: show.checked; PropertyChanges { target: container; opacity: 0.9; y: 0 }},
|
||||||
|
State { when: !show.checked; PropertyChanges { target: container; opacity: 0.0; y: -height }}
|
||||||
|
]
|
||||||
|
|
||||||
|
transitions: [
|
||||||
|
Transition { NumberAnimation { properties: "opacity,y"; duration: 250; easing.type: Easing.InCubic }}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
examples/advanced-qml-auto-reload/qml/fonts/Hack-Bold.ttf
Normal file
BIN
examples/advanced-qml-auto-reload/qml/fonts/Hack-Bold.ttf
Normal file
Binary file not shown.
BIN
examples/advanced-qml-auto-reload/qml/fonts/Hack-Regular.ttf
Normal file
BIN
examples/advanced-qml-auto-reload/qml/fonts/Hack-Regular.ttf
Normal file
Binary file not shown.
Binary file not shown.
46
examples/advanced-qml-auto-reload/qml/main.qml
Normal file
46
examples/advanced-qml-auto-reload/qml/main.qml
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import ".ext/" as Ext // for single file auto reload (development)
|
||||||
|
//import "ext/" as Ext // release version
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 300
|
||||||
|
height: 500
|
||||||
|
color: "black"
|
||||||
|
|
||||||
|
SwipeView {
|
||||||
|
id: view
|
||||||
|
objectName: "view"
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
color: "white"
|
||||||
|
|
||||||
|
Ext.Repl {}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "swipe for next page"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// N.B. don't use Loader inside a Repeater here, won't work with single
|
||||||
|
// file auto reload (which already uses a Loader)
|
||||||
|
|
||||||
|
Ext.Page1 {}
|
||||||
|
Ext.Page2 {}
|
||||||
|
Ext.Page3 {}
|
||||||
|
}
|
||||||
|
|
||||||
|
PageIndicator {
|
||||||
|
anchors.bottom: view.bottom
|
||||||
|
anchors.bottomMargin: 10
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
count: view.count
|
||||||
|
currentIndex: view.currentIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
FontLoader { id: fontIcons; source: "fonts/fontawesome-webfont.ttf" }
|
||||||
|
FontLoader { id: fontHack; source: "fonts/Hack-Regular.ttf" }
|
||||||
|
FontLoader { id: fontHackBold; source: "fonts/Hack-Bold.ttf" }
|
||||||
|
}
|
||||||
80
examples/advanced-qml-auto-reload/readme.md
Normal file
80
examples/advanced-qml-auto-reload/readme.md
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
|
||||||
|
Prepare
|
||||||
|
-------
|
||||||
|
|
||||||
|
Please copy the app template files first:
|
||||||
|
```
|
||||||
|
$ cd ..
|
||||||
|
$ ./copy.sh advanced-qml-auto-reload
|
||||||
|
```
|
||||||
|
|
||||||
|
See also [../../slime/src/readme-sources](../../slime/src/readme-sources.md) for
|
||||||
|
installing the Slime sources where this example can find them.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Info
|
||||||
|
----
|
||||||
|
|
||||||
|
This is simply an extended version of example **swank-sevrer** of the parent
|
||||||
|
directory.
|
||||||
|
|
||||||
|
It adds a QML `SwipeView` with 3 pages, to demonstrate how to reload single
|
||||||
|
QML pages, without reloading the whole UI. This is important for nested UI
|
||||||
|
pages, in order to not lose your current view in the UI.
|
||||||
|
|
||||||
|
Without the aboce feature, you would always land on the main view after
|
||||||
|
reloading QML.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
QML single file auto reload on mobile
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
If you compile for mobile, it will ask for the **Wifi IP** of your desktop
|
||||||
|
computer (currently hard coded into the app). If you just hit RET, auto
|
||||||
|
reloading will be disabled.
|
||||||
|
|
||||||
|
N.B: **Before** installing and launching the app, just run this script from
|
||||||
|
your example directory:
|
||||||
|
```
|
||||||
|
./web-server.sh
|
||||||
|
```
|
||||||
|
It requires Python 3 and the cgi module, which are probably already installed
|
||||||
|
on your computer.
|
||||||
|
|
||||||
|
You may now edit any QML file on the desktop computer, and upon saving, only
|
||||||
|
the saved file will be reloaded.
|
||||||
|
|
||||||
|
Only if you edit and save `qml/main.qml` the following file will be loaded for
|
||||||
|
eventual re-initialization on Lisp side:
|
||||||
|
```
|
||||||
|
lisp/qml-reload/on-reloaded.lisp
|
||||||
|
```
|
||||||
|
For **android**, in order to see the debug output of eventual QML errors, you
|
||||||
|
need to run `./log.sh` in your `build-android/` directory.
|
||||||
|
|
||||||
|
If you don't want auto reload enabled (would block the app if the web server
|
||||||
|
isn't reachable), you need to rebuild the app and skip entering an IP during
|
||||||
|
the build process, see above.
|
||||||
|
|
||||||
|
Both desktop and mobile auto reload can also be run **simultaneously**, since
|
||||||
|
they share the QML source files. Any number of mobile devices may be connected,
|
||||||
|
if they are in the same WiFi and point to the same desktop IP.
|
||||||
|
|
||||||
|
|
||||||
|
Important notes for mobile
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Please remember that installing a new version of your app on mobile will
|
||||||
|
**keep all app data** present on the device.
|
||||||
|
|
||||||
|
So, if you changed e.g. `lisp/curl.lisp`, a simple update will not replace
|
||||||
|
that file, because it has been copied from the assets directory, and is only
|
||||||
|
replaced if you increment `+app-version+` in `lisp/swank-quicklisp.lisp`.
|
||||||
|
|
||||||
|
A simple way to guarantee a clean install is simply uninstalling the app first,
|
||||||
|
both on the device and on the emulator (android) or simulator (iOS).
|
||||||
|
|
||||||
|
If your local web server doesn't seem to work, please check your firewall
|
||||||
|
settings.
|
||||||
3
examples/advanced-qml-auto-reload/web-server.sh
Executable file
3
examples/advanced-qml-auto-reload/web-server.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
ecl -shell qml/.create-qml-loaders.lisp # for single file reload
|
||||||
|
|
||||||
|
python3 -m http.server 8080 --cgi
|
||||||
|
|
@ -55,7 +55,8 @@ QML auto reload on mobile
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
If you compile for mobile, it will ask for the **Wifi IP** of your desktop
|
If you compile for mobile, it will ask for the **Wifi IP** of your desktop
|
||||||
computer (currently hard coded into the app).
|
computer (currently hard coded into the app). If you just hit RET, auto
|
||||||
|
reloading will be disabled.
|
||||||
|
|
||||||
After installing and launching the app, just run this script from your example
|
After installing and launching the app, just run this script from your example
|
||||||
directory:
|
directory:
|
||||||
|
|
@ -78,7 +79,9 @@ If you don't want auto reload enabled (would block the app if the web server
|
||||||
isn't reachable), comment out `auto-reload-qml` in `lisp/main.lisp`.
|
isn't reachable), comment out `auto-reload-qml` in `lisp/main.lisp`.
|
||||||
|
|
||||||
Both desktop and mobile auto reload can also be run **simultaneously**, since
|
Both desktop and mobile auto reload can also be run **simultaneously**, since
|
||||||
they share the QML source files.
|
they share the QML source files. Any number of mobile devices may be connected,
|
||||||
|
if they are in the same WiFi and point to the same desktop IP.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Important notes for mobile
|
Important notes for mobile
|
||||||
|
|
|
||||||
18
readme.md
18
readme.md
|
|
@ -9,6 +9,24 @@ cross-platform apps. The same sources can be used to build executables for both
|
||||||
desktop (Linux/macOS) and mobile (android/iOS).
|
desktop (Linux/macOS) and mobile (android/iOS).
|
||||||
|
|
||||||
|
|
||||||
|
QML auto reload
|
||||||
|
---------------
|
||||||
|
|
||||||
|
A new feature is auto reloading of QML files after saving any changes. This
|
||||||
|
works both on the desktop and on mobile.
|
||||||
|
|
||||||
|
As a concrete example, you may have running your app on the desktop, and have
|
||||||
|
both an android mobile device plus an iOS mobile device pointing to the IP of
|
||||||
|
the desktop. Now you will see any change to QML on all 3 screens
|
||||||
|
simultaneoulsy.
|
||||||
|
|
||||||
|
This even works (with some limitations, and only in this
|
||||||
|
[advanced example](examples/advanced-qml-auto-reload/)) at QML file level,
|
||||||
|
which means: only the QML file currently edited is reloaded, preserving the
|
||||||
|
state of all other QML files, and more importantly, the current view in case
|
||||||
|
of nested page structures.
|
||||||
|
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue