mirror of
https://gitlab.com/eql/lqml.git
synced 2025-12-06 02:30:38 -08:00
make example work on android; revisions
This commit is contained in:
parent
113386fdae
commit
99ea5a081d
49 changed files with 637 additions and 133 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,6 +1,5 @@
|
|||
TODO
|
||||
lqml
|
||||
build
|
||||
_*
|
||||
*.a
|
||||
*.fas*
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ win32 {
|
|||
HEADERS += \
|
||||
lib.h \
|
||||
../../src/cpp/marshal.h \
|
||||
../../src/cpp/qt_ech.h
|
||||
../../src/cpp/qt_ecl.h
|
||||
|
||||
SOURCES += \
|
||||
lib.cpp \
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ QObject* ini() {
|
|||
return cpp;
|
||||
}
|
||||
|
||||
// functiones defined Q_INVOKABLE
|
||||
// functions defined Q_INVOKABLE
|
||||
|
||||
QVariant CPP::hello(const QVariant& arg) {
|
||||
|
||||
|
|
|
|||
|
|
@ -23,9 +23,18 @@ the argument and return type simply being defined as `QVariant`, you may also
|
|||
pass lists, because a `QVariant` can also be of type `QVariantList`, so this
|
||||
is a perfect fit for (nested) Lisp lists.
|
||||
|
||||
So, we pass a nested Lisp list, and it gets shown on Qt side with the
|
||||
respective types. Then the `QVariantList` is returned to Lisp, where it is
|
||||
So, we pass a nested Lisp list, and it gets converted and shown on Qt side with
|
||||
the respective types. Then the `QVariantList` is returned to Lisp, where it is
|
||||
automatically converted back to a nested Lisp list.
|
||||
|
||||
Really convenient!
|
||||
|
||||
From the second function -- which calls back to Lisp -- we can see that it
|
||||
suffices to simply pass some intuitive, primitive C++ values to `ecl_fun`,
|
||||
which will be converted automatically (using `QVariant`) to the appropriate
|
||||
Lisp values.
|
||||
|
||||
**Conclusion**: by only allowing `QVariant` arguments for calls between Lisp
|
||||
and C++/Qt, we simplify things to a point where it becomes trivial, especially
|
||||
considering nested lists on both sides.
|
||||
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@
|
|||
(qget *quick-view* |width|)
|
||||
|
||||
|
||||
<b>qjs (method-name item/name &rest arguments</b>
|
||||
<b>qjs (method-name item/name &rest arguments)</b>
|
||||
|
||||
Fast and convenient way to call JS functions defined in QML. You may pass
|
||||
up to 10 arguments of the following types:
|
||||
|
|
@ -123,6 +123,8 @@
|
|||
mentioned arguments.
|
||||
N.B: Does not work with JS default arguments.
|
||||
|
||||
(qjs |drawLine| *canvas* x1 y1 x2 y2))
|
||||
|
||||
|
||||
<b>qlater (function)</b>
|
||||
|
||||
|
|
@ -137,7 +139,7 @@
|
|||
The plugin will be reloaded (if supported by the OS) every time you call
|
||||
this function. If the UNLOAD argument is not NIL, the plugin will be
|
||||
unloaded (if supported by the OS).
|
||||
N.B: This works only for Qt6 functions with the following signature:
|
||||
N.B: This works only for Qt functions with the following signature:
|
||||
"QVariant foo(QVariant, ...)" ; max 10 QVariant arguments
|
||||
Since a QVariant can also be of type QVariantList, this is a perfect fit
|
||||
for (nested) Lisp lists.
|
||||
|
|
@ -147,7 +149,7 @@
|
|||
(define-qt-wrappers *c++*) ; Lisp wrapper functions
|
||||
|
||||
|
||||
<b>qlog (arg1 &optional arg2 arg3...)</b>
|
||||
<b>qlog (arg1 &rest args)</b>
|
||||
|
||||
For log messages on android.
|
||||
|
||||
|
|
|
|||
6
examples/9999/app.asd
Normal file
6
examples/9999/app.asd
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
(defsystem :app
|
||||
:serial t
|
||||
:depends-on ()
|
||||
:components ((:file "lisp/package")
|
||||
(:file "lisp/main")))
|
||||
|
||||
53
examples/9999/app.pro
Normal file
53
examples/9999/app.pro
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# changes to these files will re-compile the Lisp library when running 'make'
|
||||
LISP_FILES = \
|
||||
lisp/package.lisp \
|
||||
lisp/main.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 = ecl -shell $$PWD/make.lisp
|
||||
}
|
||||
|
||||
lisp.input = LISP_FILES
|
||||
lisp.output = tmp/libapp.a
|
||||
|
||||
QMAKE_EXTRA_COMPILERS += lisp
|
||||
|
||||
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 -llqml -llisp -Ltmp -lapp
|
||||
macx: LIBS += -L../../../platforms/macos/lib -llqml -llisp -Ltmp -lapp
|
||||
|
||||
android {
|
||||
QT += androidextras
|
||||
INCLUDEPATH = $$(ECL_ANDROID)/include
|
||||
LIBS = -L$$(ECL_ANDROID)/lib -lecl
|
||||
LIBS += -L../../../platforms/android/lib -llqml -llisp -Ltmp -lapp
|
||||
|
||||
ANDROID_ABIS = "arm64-v8a"
|
||||
ANDROID_EXTRA_LIBS += $$(ECL_ANDROID)/lib/libecl.so
|
||||
#ANDROID_PACKAGE_SOURCE_DIR = ../platforms/android/sources
|
||||
}
|
||||
|
||||
SOURCES += ../../src/cpp/main.cpp
|
||||
|
||||
RESOURCES = app.qrc
|
||||
|
||||
QMAKE_CXXFLAGS += -std=c++17
|
||||
|
||||
5
examples/9999/app.qrc
Normal file
5
examples/9999/app.qrc
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource>
|
||||
<file>qml/main.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
2
examples/9999/build-android/.gitignore
vendored
Normal file
2
examples/9999/build-android/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
2
examples/9999/build-ios/.gitignore
vendored
Normal file
2
examples/9999/build-ios/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
2
examples/9999/build/.gitignore
vendored
Normal file
2
examples/9999/build/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(in-package :qml-user)
|
||||
(in-package :app)
|
||||
|
||||
(defvar *number* 0)
|
||||
|
||||
|
|
|
|||
6
examples/9999/lisp/package.lisp
Normal file
6
examples/9999/lisp/package.lisp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
(defpackage :app
|
||||
(:use :cl :qml)
|
||||
(:export
|
||||
#:draw-number
|
||||
#:paint))
|
||||
|
||||
47
examples/9999/make.lisp
Normal file
47
examples/9999/make.lisp
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
(when (search "/ecl-android/" (first (ext:command-args)))
|
||||
(pushnew :android *features*))
|
||||
|
||||
(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*))
|
||||
(position #\/ name :from-end t))))
|
||||
|
||||
;; load all LQML symbols
|
||||
(dolist (file (list "package" "x" "ecl-ext" "ini" "qml"))
|
||||
(load (merge-pathnames file "src/lisp/")))
|
||||
|
||||
(defun cc (&rest args)
|
||||
(apply 'concatenate 'string args))
|
||||
|
||||
#-android
|
||||
(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)))
|
||||
|
||||
#+android
|
||||
(progn
|
||||
(defvar *asdf-system* "app")
|
||||
(defvar *ql-libs* (cc *current* "/ql-libs.lisp"))
|
||||
(defvar *library-name* (cc *current* "/build-android/tmp/app"))
|
||||
(defvar *init-name* "ini_app")
|
||||
(defvar *epilogue-code* nil)
|
||||
(load "platforms/shared/make"))
|
||||
|
||||
3
examples/9999/mkdirs.sh
Executable file
3
examples/9999/mkdirs.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
mkdir build
|
||||
mkdir build-android
|
||||
mkdir build-ios
|
||||
3
examples/9999/ql-libs.lisp
Normal file
3
examples/9999/ql-libs.lisp
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
;;; define here eventual Quicklisp dependencies
|
||||
;;; e.g. (ql:quickload :alessandria)
|
||||
|
||||
|
|
@ -1,17 +1,37 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import Lisp 1.0
|
||||
|
||||
Rectangle {
|
||||
width: 220
|
||||
height: 320 + input.height
|
||||
id: main
|
||||
width: 200
|
||||
height: 300 + input.height
|
||||
color: "lavender"
|
||||
|
||||
TextField {
|
||||
id: input
|
||||
objectName: "input"
|
||||
width: parent.width
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
text: "0000"
|
||||
inputMask: "9999"
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
focus: true
|
||||
|
||||
onTextChanged: Lisp.call("app:draw-number", Number(text))
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
objectName: "canvas"
|
||||
width: 220
|
||||
height: 320
|
||||
y: input.height
|
||||
width: parent.width
|
||||
height: {
|
||||
var h = Qt.inputMethod.keyboardRectangle.y
|
||||
h = (h === 0) ? main.height : h / Screen.devicePixelRatio
|
||||
return (h - input.height)
|
||||
}
|
||||
|
||||
property var ctx
|
||||
|
||||
|
|
@ -36,23 +56,13 @@ Rectangle {
|
|||
onPaint: {
|
||||
ctx = getContext("2d")
|
||||
ctx.reset()
|
||||
ctx.translate(110, 160)
|
||||
ctx.translate(canvas.width / 2, canvas.height / 2)
|
||||
var s = height / 340
|
||||
ctx.scale(s, s)
|
||||
|
||||
Lisp.call("qml-user:paint")
|
||||
Lisp.call("app:paint")
|
||||
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: input
|
||||
objectName: "input"
|
||||
width: parent.width
|
||||
anchors.bottom: parent.bottom
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
text: "0000"
|
||||
inputMask: "9999"
|
||||
|
||||
onTextChanged: Lisp.call("qml-user:draw-number", Number(text))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
32
examples/9999/readme-build.md
Normal file
32
examples/9999/readme-build.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
Run desktop
|
||||
-----------
|
||||
|
||||
```
|
||||
$ lqml run.lisp
|
||||
```
|
||||
|
||||
|
||||
Build desktop app
|
||||
-----------------
|
||||
|
||||
```
|
||||
$ cd build
|
||||
|
||||
$ qmake ../app.pro
|
||||
$ make
|
||||
```
|
||||
|
||||
|
||||
Build android APK
|
||||
-----------------
|
||||
|
||||
```
|
||||
$ cd build-android
|
||||
|
||||
$ qmake-android ../app.pro
|
||||
$ make apk
|
||||
|
||||
$ adb install -r android-build/*.apk
|
||||
```
|
||||
|
||||
|
|
@ -1,12 +1,17 @@
|
|||
(in-package :qml-user)
|
||||
|
||||
(load "lisp/main")
|
||||
(require :asdf)
|
||||
|
||||
(push (merge-pathnames "./")
|
||||
asdf:*central-registry*)
|
||||
|
||||
(asdf:operate 'asdf:load-source-op :app)
|
||||
|
||||
(qset *quick-view*
|
||||
|x| 75
|
||||
|y| 75)
|
||||
|
||||
;;; for Slime after copying 'qml-start-swank.lisp' from LQML sources
|
||||
;;; for Slime after copying 'lqml-start-swank.lisp' from LQML sources
|
||||
;;; to your Slime directory, which is assumed to be '~/slime/'
|
||||
|
||||
(when (find "-slime" (ext:command-args) :test 'string=)
|
||||
|
|
|
|||
11
platforms/android/build-ecl/1-make-ecl-host.sh
Executable file
11
platforms/android/build-ecl/1-make-ecl-host.sh
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
# build the host ECL, which will then be used
|
||||
# to build the cross-compiled Android version
|
||||
# (assumes a 64bit platform)
|
||||
|
||||
./configure CFLAGS="-g -O2" LDFLAGS="-g -O2" CC=clang \
|
||||
--prefix=`pwd`/ecl-android-host \
|
||||
--disable-c99complex \
|
||||
--enable-manual=no
|
||||
make
|
||||
make install
|
||||
rm -r build
|
||||
20
platforms/android/build-ecl/2-make-ecl-android.sh
Executable file
20
platforms/android/build-ecl/2-make-ecl-android.sh
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
# use the previously built host ECL to build the android version
|
||||
# requires NDK >= 19
|
||||
# you need to define ANDROID_NDK_TOOLCHAIN
|
||||
|
||||
export AR=$ANDROID_NDK_TOOLCHAIN/bin/aarch64-linux-android-ar
|
||||
export AS=$ANDROID_NDK_TOOLCHAIN/bin/aarch64-linux-android-as
|
||||
export CC=$ANDROID_NDK_TOOLCHAIN/bin/aarch64-linux-android21-clang
|
||||
export LD=$ANDROID_NDK_TOOLCHAIN/bin/aarch64-linux-android-ld
|
||||
export RANLIB=$ANDROID_NDK_TOOLCHAIN/bin/aarch64-linux-android-ranlib
|
||||
export STRIP=$ANDROID_NDK_TOOLCHAIN/bin/aarch64-linux-android-strip
|
||||
|
||||
export ECL_TO_RUN=`pwd`/ecl-android-host/bin/ecl
|
||||
|
||||
./configure --host=aarch64-linux-android \
|
||||
--prefix=`pwd`/ecl-android \
|
||||
--disable-c99complex \
|
||||
--enable-manual=no \
|
||||
--with-cross-config=`pwd`/src/util/android-arm64.cross_config
|
||||
make
|
||||
make install
|
||||
39
platforms/android/cross-compile.lisp
Normal file
39
platforms/android/cross-compile.lisp
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
;;; please use NDK versions >= 19 (with prebuilt standalone toolchain)
|
||||
|
||||
(in-package :cl-user)
|
||||
|
||||
(pushnew :android *features*)
|
||||
(pushnew :aarch64 *features*)
|
||||
|
||||
(require :cmp)
|
||||
|
||||
(defvar *ndk-toolchain* (ext:getenv "ANDROID_NDK_TOOLCHAIN"))
|
||||
(defvar *ecl-android* (ext:getenv "ECL_ANDROID"))
|
||||
(defvar *architecture* "aarch64-linux-android")
|
||||
|
||||
(defun cc (&rest arguments)
|
||||
(apply 'concatenate 'string arguments))
|
||||
|
||||
(setf c::*ecl-include-directory* (cc *ecl-android* "/include/")
|
||||
c::*ecl-library-directory* (cc *ecl-android* "/lib/"))
|
||||
|
||||
(defun ecl-config (flags)
|
||||
(read-line (ext:run-program (cc *ecl-android* "/bin/ecl-config")
|
||||
(list flags))))
|
||||
|
||||
(setf c::*cc* (let ((path (or (probe-file (cc *ndk-toolchain* "/bin/aarch64-linux-android21-clang"))
|
||||
(error "clang compiler not found"))))
|
||||
(namestring path))
|
||||
c::*ld* (cc *ndk-toolchain* "/bin/aarch64-linux-android-ld")
|
||||
c::*ar* (cc *ndk-toolchain* "/bin/aarch64-linux-android-ar")
|
||||
c::*ranlib* (cc *ndk-toolchain* "/bin/aarch64-linux-android-ranlib")
|
||||
c::*cc-flags* (cc (ecl-config "--cflags")
|
||||
" -DANDROID -DPLATFORM_ANDROID -O2 -fPIC -fno-common -D_THREAD_SAFE -I"
|
||||
*ecl-android* "/build/gmp")
|
||||
c::*ld-flags* (cc "-L" *ecl-android* "/lib -lecl -ldl -lm "
|
||||
"-L" *ndk-toolchain* "/sysroot/usr/lib/aarch64-linux-android/")
|
||||
c::*ld-rpath* nil
|
||||
c::*ld-shared-flags* (cc "-shared " c::*ld-flags*)
|
||||
c::*ld-bundle-flags* c::*ld-shared-flags*)
|
||||
|
||||
(format t "~%*** cross compiling for 'aarch64' ***~%")
|
||||
75
platforms/shared/make.lisp
Normal file
75
platforms/shared/make.lisp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
;;; cross-compile ASDF system, using the byte-codes compiler
|
||||
;;; as an intermediate step
|
||||
|
||||
;; optional vars, to be set in 'make.lisp' in your app dir
|
||||
(defvar *epilogue-code* nil)
|
||||
(defvar *ql-libs* nil)
|
||||
|
||||
;;; *** (1) byte-compile ASDF system ***
|
||||
|
||||
(si:install-bytecodes-compiler)
|
||||
|
||||
(when *ql-libs*
|
||||
(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp"
|
||||
(user-homedir-pathname))))
|
||||
(when (probe-file quicklisp-init)
|
||||
(load quicklisp-init)))
|
||||
(load *ql-libs*))
|
||||
|
||||
;;; load ASDF system and collect file names
|
||||
|
||||
(defvar *source-files* nil)
|
||||
|
||||
(defmethod asdf:perform ((o asdf:load-op) (c asdf:cl-source-file))
|
||||
(let ((source (namestring (asdf:component-pathname c))))
|
||||
(push (subseq source 0 (position #\. source :from-end t))
|
||||
*source-files*))
|
||||
(asdf::perform-lisp-load-fasl o c))
|
||||
|
||||
(asdf:load-system *asdf-system*)
|
||||
|
||||
(setf *source-files* (nreverse *source-files*))
|
||||
|
||||
;;; *** (2) cross-compile ***
|
||||
|
||||
;;; load and prepare cross-compiler
|
||||
|
||||
(si:install-c-compiler)
|
||||
|
||||
(load #+android (merge-pathnames "platforms/android/cross-compile"))
|
||||
|
||||
(setf *load-verbose* nil
|
||||
*compile-verbose* t)
|
||||
|
||||
(setf c::*suppress-compiler-warnings* nil
|
||||
c::*suppress-compiler-notes* nil
|
||||
c::*compile-in-constants* t)
|
||||
|
||||
(load (merge-pathnames "src/lisp/tr.lisp")) ; i18n
|
||||
|
||||
(setf *break-on-signals* 'error)
|
||||
|
||||
;;; compile/link manually (byte-compiled version is already loaded)
|
||||
|
||||
(defvar *object-files* nil)
|
||||
|
||||
(defun cc (&rest args)
|
||||
(apply 'concatenate 'string args))
|
||||
|
||||
(dolist (file *source-files*)
|
||||
(let ((src (cc file ".lisp"))
|
||||
(obj (merge-pathnames (cc "src/.cache/" *architecture* file ".o"))))
|
||||
(when (or (not (probe-file obj))
|
||||
(> (file-write-date src)
|
||||
(file-write-date obj)))
|
||||
(ensure-directories-exist obj)
|
||||
(compile-file src :output-file obj :system-p t))
|
||||
(push obj *object-files*)))
|
||||
|
||||
(setf *object-files* (nreverse *object-files*))
|
||||
|
||||
(c:build-static-library *library-name*
|
||||
:lisp-files *object-files*
|
||||
:init-name *init-name*
|
||||
:epilogue-code *epilogue-code*)
|
||||
|
||||
|
|
@ -1,21 +1,46 @@
|
|||
|
||||
Build
|
||||
-----
|
||||
Build executable
|
||||
----------------
|
||||
|
||||
Currently still using qmake, will be ported to CMake.
|
||||
Currently still using qmake, will eventually be ported to CMake.
|
||||
|
||||
* make sure you have both **ECL** and **Qt6** installed
|
||||
* make sure to use `qmake` from Qt6
|
||||
|
||||
**macOS**: please use ECL from development branch.
|
||||
Please make sure you have both latest **ECL** (from development branch) and
|
||||
either **Qt5.15** or **Qt6** installed (see [readme-qt](readme-qt.md)).
|
||||
|
||||
```
|
||||
$ cd src
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cd src/build
|
||||
|
||||
$ qmake ../lqml.pro
|
||||
$ make -j4
|
||||
$ sudo make install
|
||||
```
|
||||
|
||||
|
||||
Build library
|
||||
-------------
|
||||
|
||||
To build the static library needed for building your own apps, do:
|
||||
|
||||
* desktop
|
||||
```
|
||||
$ cd src/build
|
||||
|
||||
$ qmake ../lqml-lib.pro
|
||||
$ make
|
||||
```
|
||||
* android (note separate build directory)
|
||||
```
|
||||
$ cd src/build-android
|
||||
|
||||
$ qmake-android ../lqml-lib.pro
|
||||
$ make
|
||||
```
|
||||
* ios (note separate build directory)
|
||||
```
|
||||
$ cd src/build-ios
|
||||
|
||||
$ qmake-ios ../lqml-lib.pro
|
||||
$ make
|
||||
```
|
||||
The library files can be found under `platforms/<os>/lib/`
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ Description
|
|||
|
||||
A lightweight ECL based QML-only binding to Qt5/Qt6.
|
||||
|
||||
This small project aims to simplify all the steps needed for building
|
||||
cross-platform apps. The same sources can be used to build executables for both
|
||||
desktop (Linux/macOS) and mobile (android/iOS).
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
|
@ -33,7 +37,6 @@ port still lacks significant parts of mobile (as of Qt6.2).
|
|||
TODO
|
||||
----
|
||||
|
||||
* make example work on android
|
||||
* make example work on iOS
|
||||
* add item model example
|
||||
* add sokoban example
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
Swank/Slime
|
||||
-----------
|
||||
|
||||
Please put `qml-start-swank.lisp` in your `slime/` directory, and `.swank.lisp`
|
||||
in your home directory.
|
||||
Please put `lqml-start-swank.lisp` in your `slime/` directory, and
|
||||
`.swank.lisp` in your home directory.
|
||||
|
||||
Remember to always use `(qquit)` or `(qq)` to gracefully exit the Swank server.
|
||||
|
|
|
|||
2
src/build-android/.gitignore
vendored
Normal file
2
src/build-android/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
2
src/build-ios/.gitignore
vendored
Normal file
2
src/build-ios/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
2
src/build/.gitignore
vendored
Normal file
2
src/build/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
|
|
@ -214,7 +214,7 @@ cl_object qload_cpp(cl_object l_lib_name, cl_object l_unload) { /// qload-c++
|
|||
/// The plugin will be reloaded (if supported by the OS) every time you call
|
||||
/// this function. If the UNLOAD argument is not NIL, the plugin will be
|
||||
/// unloaded (if supported by the OS).
|
||||
/// N.B: This works only for Qt6 functions with the following signature:
|
||||
/// N.B: This works only for Qt functions with the following signature:
|
||||
/// "QVariant foo(QVariant, ...)" ; max 10 QVariant arguments
|
||||
/// Since a QVariant can also be of type QVariantList, this is a perfect fit
|
||||
/// for (nested) Lisp lists.
|
||||
|
|
@ -348,7 +348,7 @@ cl_object qsingle_shot2(cl_object l_msec, cl_object l_fun) {
|
|||
/// (qsingle-shot 1000 'one-second-later)
|
||||
ecl_process_env()->nvalues = 1;
|
||||
if (l_fun != ECL_NIL) {
|
||||
new SingleShot(toInt(l_msec), l_fun);
|
||||
new SingleShot(toInt(l_msec), l_fun); // see 'deleteLater()' in sources
|
||||
return l_msec;
|
||||
}
|
||||
error_msg("QSINGLE-SHOT", LIST2(l_msec, l_fun));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef ECL_EXT_H
|
||||
#define ECL_EXT_H
|
||||
|
||||
#undef SLOT
|
||||
|
||||
#include <ecl/ecl.h>
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
#include <QStringList>
|
||||
#include <QDebug>
|
||||
|
||||
const char LQML::version[] = "22.1.1"; // Jan 2022
|
||||
const char LQML::version[] = "22.2.1"; // Feb 2022
|
||||
|
||||
extern "C" void ini_LQML(cl_object);
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ static void logMessageHandler(QtMsgType, const QMessageLogContext& context, cons
|
|||
report += " function ";
|
||||
report += QString(context.function);
|
||||
}
|
||||
__android_log_write(ANDROID_LOG_DEBUG, "[EQL5]", report.toLocal8Bit().constData());
|
||||
__android_log_write(ANDROID_LOG_DEBUG, "[LQML]", report.toLocal8Bit().constData());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -44,11 +44,12 @@ LQML::LQML(int argc, char* argv[], QQuickView* view) : QObject() {
|
|||
qInstallMessageHandler(logMessageHandler); // see above
|
||||
#endif
|
||||
if (!cl_booted_p) {
|
||||
cl_boot(argc, argv); }
|
||||
cl_boot(argc, argv);
|
||||
}
|
||||
iniCLFunctions();
|
||||
ecl_init_module(NULL, ini_LQML);
|
||||
eval("(in-package :qml-user)");
|
||||
eval(QString("(setf *quick-view* (qt-object %1))")
|
||||
eval(QString("(setf qml:*quick-view* (qml:qt-object %1))")
|
||||
.arg(reinterpret_cast<quintptr>(view)));
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +88,7 @@ void LQML::eval(const QString& lisp_code, bool slime) {
|
|||
safe_eval_debug(lisp_code.toLatin1().constData());
|
||||
} else {
|
||||
cl_object ret = safe_eval(lisp_code.toLatin1().constData());
|
||||
if (ecl_t_of(ret) == t_fixnum && (fix(ret) == EVAL_ERROR_VALUE)) {
|
||||
if ((ecl_t_of(ret) == t_fixnum) && (fix(ret) == EVAL_ERROR_VALUE)) {
|
||||
qDebug() << "Error evaluating " << lisp_code;
|
||||
exit(-1);
|
||||
}
|
||||
|
|
@ -96,7 +97,7 @@ void LQML::eval(const QString& lisp_code, bool slime) {
|
|||
|
||||
void LQML::ignoreIOStreams() {
|
||||
// [Windows] print output would cause a gui exe to crash (without console)
|
||||
eval("(eql::ignore-io-streams)");
|
||||
eval("(qml::ignore-io-streams)");
|
||||
}
|
||||
|
||||
void LQML::exec(lisp_ini ini, const QByteArray& expression, const QByteArray& package) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef LQML_H
|
||||
#define LQML_H
|
||||
|
||||
#undef SLOT
|
||||
|
||||
#include <ecl/ecl.h>
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@
|
|||
#define ADD_MACOS_BUNDLE_IMPORT_PATH
|
||||
#endif
|
||||
|
||||
#ifdef INI_LISP
|
||||
extern "C" void ini_app(cl_object);
|
||||
#endif
|
||||
|
||||
int catch_all_qexec() {
|
||||
int ret = 0;
|
||||
CL_CATCH_ALL_BEGIN(ecl_process_env()) {
|
||||
|
|
@ -25,6 +29,7 @@ int catch_all_qexec() {
|
|||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QGuiApplication app(argc, argv);
|
||||
//app.setOrganizationName("MyProject");
|
||||
//app.setOrganizationDomain("my.org");
|
||||
|
|
@ -49,25 +54,26 @@ int main(int argc, char* argv[]) {
|
|||
exit(0);
|
||||
}
|
||||
|
||||
#ifdef INI_LISP
|
||||
ecl_init_module(NULL, ini_app);
|
||||
#endif
|
||||
|
||||
new QQmlFileSelector(view.engine(), &view);
|
||||
QString qml("qml/main.qml");
|
||||
QUrl url("qrc:///" + qml); // (1) try resources first (final app)
|
||||
bool set = false;
|
||||
if (QFile::exists(url.fileName())) {
|
||||
set = true;
|
||||
QUrl url;
|
||||
if (QFile::exists(qml)) { // (1) try local file (development)
|
||||
url = QUrl::fromLocalFile(qml);
|
||||
} else {
|
||||
url = QUrl::fromLocalFile(qml); // (2) use local file (development)
|
||||
if (QFile::exists(QDir::currentPath() + "/" + qml)) {
|
||||
set = true;
|
||||
url = QUrl("qrc:///" + qml); // (2) use resource file (final app)
|
||||
}
|
||||
}
|
||||
if (set) {
|
||||
view.setSource(url);
|
||||
if (view.status() == QQuickView::Error) {
|
||||
return -1;
|
||||
}
|
||||
if (view.status() != QQuickView::Error) {
|
||||
view.setResizeMode(QQuickView::SizeRootObjectToView);
|
||||
#if (defined Q_OS_ANDROID) || (defined Q_OS_IOS)
|
||||
view.show();
|
||||
#else
|
||||
QTimer::singleShot(0, &view, &QQuickView::show);
|
||||
#endif
|
||||
}
|
||||
|
||||
// load .eclrc
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
#include "marshal.h"
|
||||
#include <ecl/ecl.h>
|
||||
#include <QVariant>
|
||||
#include <QObject>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef MARSHAL_H
|
||||
#define MARSHAL_H
|
||||
|
||||
#undef SLOT
|
||||
|
||||
#include <ecl/ecl.h>
|
||||
#include <QRectF>
|
||||
#include <QVariant>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#include "qt_ecl.h"
|
||||
#include "marshal.h"
|
||||
#include "ecl_ext.h"
|
||||
#include <ecl/ecl.h>
|
||||
#include <QVariant>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
|
|
|||
25
src/cpp/single_shot.cpp
Normal file
25
src/cpp/single_shot.cpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#include "single_shot.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
SingleShot::SingleShot(int msec, void* fun)
|
||||
: function(fun) {
|
||||
id = startTimer(msec);
|
||||
}
|
||||
|
||||
void SingleShot::timerEvent(QTimerEvent*) {
|
||||
killTimer(id);
|
||||
const cl_env_ptr l_env = ecl_process_env();
|
||||
CL_CATCH_ALL_BEGIN(l_env) {
|
||||
CL_UNWIND_PROTECT_BEGIN(l_env) {
|
||||
cl_funcall(1, (cl_object)function);
|
||||
}
|
||||
CL_UNWIND_PROTECT_EXIT {}
|
||||
CL_UNWIND_PROTECT_END;
|
||||
}
|
||||
CL_CATCH_ALL_END;
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
|
|
@ -1,30 +1,24 @@
|
|||
#ifndef SINGLE_SHOT_H
|
||||
#define SINGLE_SHOT_H
|
||||
|
||||
#undef SLOT
|
||||
|
||||
#include <ecl/ecl.h>
|
||||
#include <QObject>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
struct SingleShot : public QObject {
|
||||
class SingleShot : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
int id;
|
||||
void* function;
|
||||
|
||||
SingleShot(int msec, void* fun) : id(startTimer(msec)), function(fun) {}
|
||||
SingleShot(int, void*);
|
||||
|
||||
void timerEvent(QTimerEvent*) {
|
||||
killTimer(id);
|
||||
const cl_env_ptr l_env = ecl_process_env();
|
||||
CL_CATCH_ALL_BEGIN(l_env) {
|
||||
CL_UNWIND_PROTECT_BEGIN(l_env) {
|
||||
cl_funcall(1, (cl_object)function);
|
||||
}
|
||||
CL_UNWIND_PROTECT_EXIT {}
|
||||
CL_UNWIND_PROTECT_END;
|
||||
}
|
||||
CL_CATCH_ALL_END;
|
||||
delete this;
|
||||
}
|
||||
protected:
|
||||
void timerEvent(QTimerEvent*) override;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
|||
32
src/lisp/ecl-ext.lisp
Normal file
32
src/lisp/ecl-ext.lisp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
;;; needed for cross-compiling
|
||||
|
||||
(in-package :qml)
|
||||
|
||||
(defun %js (a b))
|
||||
(defun pixel-ratio ())
|
||||
(defun %qapropos (a b c))
|
||||
(defun qchildren (a))
|
||||
(defun qescape (a))
|
||||
(defun %qexec (a))
|
||||
(defun qexit ())
|
||||
(defun qfind-child (a b))
|
||||
(defun %qfind-children (a b c))
|
||||
(defun qfrom-utf8 (a))
|
||||
(defun %qinvoke-method (a b c))
|
||||
(defun %qload-c++ (a b))
|
||||
(defun %qlog (a))
|
||||
(defun %qml-get (a b))
|
||||
(defun %qml-set (a b c))
|
||||
(defun qobject-name (a))
|
||||
(defun qprocess-events ())
|
||||
(defun %qquit (a))
|
||||
(defun %qrun-on-ui-thread (a b))
|
||||
(defun %qget (a b))
|
||||
(defun %qset (a b))
|
||||
(defun %qsingle-shot (a b))
|
||||
(defun qtranslate (a b c))
|
||||
(defun qversion ())
|
||||
(defun qt-object-info (a))
|
||||
(defun %reload ())
|
||||
(defun root-item ())
|
||||
(defun %set-shutdown-p (a))
|
||||
|
|
@ -58,11 +58,9 @@
|
|||
;; check for LAMBDA, #'LAMBDA
|
||||
(if (find (first function) '(lambda function))
|
||||
;; hold a reference (will be called later from Qt event loop)
|
||||
`(qrun (lambda ()
|
||||
(%qsingle-shot ,milliseconds (setf (symbol-function (intern ,(%reference-name))) ; lambda
|
||||
,function))))
|
||||
`(qrun (lambda ()
|
||||
(%qsingle-shot ,milliseconds ,function))))) ; 'foo
|
||||
`(qrun* (%qsingle-shot ,milliseconds (setf (symbol-function (intern ,(%reference-name))) ; lambda
|
||||
,function)))
|
||||
`(qrun* (%qsingle-shot ,milliseconds ,function)))) ; 'foo
|
||||
|
||||
(defmacro qlater (function)
|
||||
"args: (function)
|
||||
|
|
@ -217,7 +215,7 @@
|
|||
;;; for android logging
|
||||
|
||||
(defun qlog (arg1 &rest args)
|
||||
"args: (arg1 &optional arg2 arg3...)
|
||||
"args: (arg1 &rest args)
|
||||
For log messages on android.
|
||||
(qlog 12)
|
||||
(qlog \"width\" 10 \"height\" 20)
|
||||
|
|
@ -235,3 +233,4 @@
|
|||
(alias qfun qinvoke-method)
|
||||
(alias qrun qrun-on-ui-thread)
|
||||
(alias qq qquit)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
(defpackage :qml
|
||||
(:use :common-lisp)
|
||||
(:use :cl)
|
||||
(:export
|
||||
#:*break-on-errors*
|
||||
#:*quick-view*
|
||||
|
|
@ -51,6 +51,6 @@
|
|||
#:!))
|
||||
|
||||
(defpackage :qml-user
|
||||
(:use :common-lisp :qml))
|
||||
(:use :cl :qml))
|
||||
|
||||
(pushnew :qml *features*)
|
||||
|
|
|
|||
|
|
@ -166,12 +166,13 @@
|
|||
;;; JS calls
|
||||
|
||||
(defmacro qjs (method-name item/name &rest arguments)
|
||||
"args: (method-name item/name &rest arguments
|
||||
"args: (method-name item/name &rest arguments)
|
||||
Fast and convenient way to call JS functions defined in QML. You may pass
|
||||
up to 10 arguments of the following types:
|
||||
T, NIL, INTEGER, FLOAT, STRING, VECTOR of octets, and (nested) lists of
|
||||
mentioned arguments.
|
||||
N.B: Does not work with JS default arguments."
|
||||
N.B: Does not work with JS default arguments.
|
||||
(qjs |drawLine| *canvas* x1 y1 x2 y2))"
|
||||
`(qrun* (qfun (quick-item ,item/name)
|
||||
,(if (symbolp method-name)
|
||||
(symbol-name method-name)
|
||||
|
|
|
|||
61
src/lqml-lib.pro
Normal file
61
src/lqml-lib.pro
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
QT += quick qml
|
||||
TEMPLATE = lib
|
||||
CONFIG += staticlib no_keywords release
|
||||
LIBS = -L/usr/local/lib -lecl
|
||||
TARGET = lqml
|
||||
OBJECTS_DIR = ./tmp
|
||||
MOC_DIR = ./tmp
|
||||
|
||||
linux {
|
||||
INCLUDEPATH = /usr/local/include
|
||||
DESTDIR = ../../platforms/linux/lib
|
||||
}
|
||||
|
||||
macx {
|
||||
INCLUDEPATH = /usr/local/include
|
||||
DESTDIR = ../../platforms/macos/lib
|
||||
}
|
||||
|
||||
android {
|
||||
INCLUDEPATH = $$(ECL_ANDROID)/include
|
||||
LIBS = -L$$(ECL_ANDROID)/lib -lecl
|
||||
DESTDIR = ../../platforms/android/lib
|
||||
ANDROID_ABIS = "arm64-v8a"
|
||||
}
|
||||
|
||||
ios {
|
||||
INCLUDEPATH = $$(ECL_IOS)/include
|
||||
LIBS = -L$$(ECL_IOS)/lib -lecl
|
||||
DESTDIR = ../../platforms/ios/lib
|
||||
}
|
||||
|
||||
HEADERS += \
|
||||
cpp/marshal.h \
|
||||
cpp/ecl_ext.h \
|
||||
cpp/lqml.h \
|
||||
cpp/qml.h \
|
||||
cpp/qt_ecl.h \
|
||||
cpp/single_shot.h
|
||||
|
||||
SOURCES += \
|
||||
cpp/marshal.cpp \
|
||||
cpp/ecl_ext.cpp \
|
||||
cpp/lqml.cpp \
|
||||
cpp/qml.cpp \
|
||||
cpp/qt_ecl.cpp \
|
||||
cpp/single_shot.cpp
|
||||
|
||||
QMAKE_CXXFLAGS += -std=c++17
|
||||
|
||||
# compile Lisp code
|
||||
|
||||
android {
|
||||
QMAKE_POST_LINK = $$(ECL_ANDROID)/../ecl-android-host/bin/ecl \
|
||||
-norc -shell $$PWD/make.lisp
|
||||
} else:ios {
|
||||
QMAKE_POST_LINK = $$(ECL_IOS)/../ecl-ios-host/bin/ecl \
|
||||
-norc -shell $$PWD/make.lisp
|
||||
} else:unix {
|
||||
QMAKE_POST_LINK = ecl -shell $$PWD/make.lisp
|
||||
}
|
||||
|
||||
31
src/lqml.pro
31
src/lqml.pro
|
|
@ -1,40 +1,26 @@
|
|||
LISP_FILES = \
|
||||
make.lisp \
|
||||
lisp/x.lisp \
|
||||
lisp/package.lisp \
|
||||
lisp/ini.lisp \
|
||||
lisp/qml.lisp \
|
||||
lqml.asd
|
||||
|
||||
lisp.output = liblqml.a
|
||||
lisp.commands = ecl -shell $$PWD/make.lisp
|
||||
lisp.input = LISP_FILES
|
||||
|
||||
QMAKE_EXTRA_COMPILERS += lisp
|
||||
|
||||
QT += quick qml
|
||||
TEMPLATE = app
|
||||
CONFIG += no_keywords release
|
||||
INCLUDEPATH += /usr/local/include
|
||||
LIBS += -L/usr/local/lib -lecl -L. -llqml
|
||||
INCLUDEPATH = /usr/local/include
|
||||
LIBS = -L/usr/local/lib -lecl -llisp
|
||||
TARGET = lqml
|
||||
DESTDIR = .
|
||||
OBJECTS_DIR = ./tmp
|
||||
MOC_DIR = ./tmp
|
||||
|
||||
linux {
|
||||
LIBS += -L../../platforms/linux/lib
|
||||
target.path = /usr/bin
|
||||
}
|
||||
|
||||
osx {
|
||||
macx {
|
||||
CONFIG -= app_bundle
|
||||
LIBS += -L../../platforms/macos/lib
|
||||
target.path = /usr/local/bin
|
||||
}
|
||||
|
||||
INSTALLS = target
|
||||
|
||||
win32 {
|
||||
include(windows.pri)
|
||||
}
|
||||
|
||||
HEADERS += \
|
||||
cpp/marshal.h \
|
||||
cpp/ecl_ext.h \
|
||||
|
|
@ -49,7 +35,10 @@ SOURCES += \
|
|||
cpp/lqml.cpp \
|
||||
cpp/qml.cpp \
|
||||
cpp/qt_ecl.cpp \
|
||||
cpp/single_shot.cpp \
|
||||
cpp/main.cpp
|
||||
|
||||
QMAKE_CXXFLAGS += -std=c++17
|
||||
|
||||
QMAKE_PRE_LINK = ecl -shell $$PWD/make.lisp
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,41 @@
|
|||
(when (search "/ecl-android/" (first (ext:command-args)))
|
||||
(pushnew :android *features*))
|
||||
|
||||
(require :asdf)
|
||||
|
||||
(push (merge-pathnames "../")
|
||||
asdf:*central-registry*)
|
||||
|
||||
(asdf:make-build "lqml"
|
||||
(setf *default-pathname-defaults*
|
||||
(merge-pathnames "../../")) ; LQML root
|
||||
|
||||
#-android
|
||||
(progn
|
||||
(asdf:make-build "lqml"
|
||||
:monolithic t
|
||||
:type :static-library
|
||||
:move-here "./"
|
||||
:move-here (format nil "platforms/~A/lib/"
|
||||
#+linux "linux"
|
||||
#+darwin "macos")
|
||||
:init-name "ini_LQML")
|
||||
(let* ((from (format nil "platforms/~A/lib/lqml--all-systems.a"
|
||||
#+linux "linux"
|
||||
#+darwin "macos"))
|
||||
(to "liblisp.a")
|
||||
(to* (format nil "platforms/~A/lib/~A"
|
||||
#+linux "linux"
|
||||
#+darwin "macos"
|
||||
to)))
|
||||
(when (probe-file to*)
|
||||
(delete-file to*))
|
||||
(rename-file from to)))
|
||||
|
||||
(let ((from "lqml--all-systems.a")
|
||||
(to "liblqml.a"))
|
||||
(when (probe-file to)
|
||||
(delete-file to))
|
||||
(rename-file from to))
|
||||
#+android
|
||||
(progn
|
||||
(defvar *asdf-system* :lqml)
|
||||
(defvar *library-name* "platforms/android/lib/lisp")
|
||||
(defvar *init-name* "ini_LQML")
|
||||
(load "platforms/shared/make"))
|
||||
|
||||
(terpri)
|
||||
|
||||
|
|
|
|||
3
src/mkdirs.sh
Executable file
3
src/mkdirs.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
mkdir build
|
||||
mkdir build-android
|
||||
mkdir build-ios
|
||||
Loading…
Add table
Add a link
Reference in a new issue