mirror of
https://gitlab.com/eql/lqml.git
synced 2025-12-05 18:20:33 -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
|
||||
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
|
||||
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`.
|
||||
|
||||
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
|
||||
|
|
|
|||
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).
|
||||
|
||||
|
||||
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
|
||||
-------
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue