make example work on android; revisions

This commit is contained in:
pls.153 2022-02-11 13:05:59 +01:00
parent 113386fdae
commit 99ea5a081d
49 changed files with 637 additions and 133 deletions

1
.gitignore vendored
View file

@ -1,6 +1,5 @@
TODO
lqml
build
_*
*.a
*.fas*

View file

@ -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 \

View file

@ -20,7 +20,7 @@ QObject* ini() {
return cpp;
}
// functiones defined Q_INVOKABLE
// functions defined Q_INVOKABLE
QVariant CPP::hello(const QVariant& arg) {

View file

@ -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.

View file

@ -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
View 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
View 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
View file

@ -0,0 +1,5 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>qml/main.qml</file>
</qresource>
</RCC>

View file

@ -0,0 +1,2 @@
*
!.gitignore

2
examples/9999/build-ios/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

2
examples/9999/build/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

View file

@ -1,4 +1,4 @@
(in-package :qml-user)
(in-package :app)
(defvar *number* 0)

View file

@ -0,0 +1,6 @@
(defpackage :app
(:use :cl :qml)
(:export
#:draw-number
#:paint))

47
examples/9999/make.lisp Normal file
View 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
View file

@ -0,0 +1,3 @@
mkdir build
mkdir build-android
mkdir build-ios

View file

@ -0,0 +1,3 @@
;;; define here eventual Quicklisp dependencies
;;; e.g. (ql:quickload :alessandria)

View file

@ -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))
}
}

View 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
```

View file

@ -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=)

View 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

View 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

View 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' ***~%")

View 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*)

View file

@ -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/`

View file

@ -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

View file

@ -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
View file

@ -0,0 +1,2 @@
*
!.gitignore

2
src/build-ios/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

2
src/build/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

View file

@ -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));

View file

@ -1,6 +1,8 @@
#ifndef ECL_EXT_H
#define ECL_EXT_H
#undef SLOT
#include <ecl/ecl.h>
#include <QList>
#include <QVariant>
@ -8,7 +10,7 @@
QT_BEGIN_NAMESPACE
#define DEFUN(name, c_name, num_args) \
ecl_def_c_function(ecl_read_from_cstring(name), (cl_objectfn_fixed)c_name, num_args);
ecl_def_c_function(ecl_read_from_cstring(name), (cl_objectfn_fixed)c_name, num_args);
#define STRING(s) ecl_make_constant_base_string(s, -1)
@ -19,12 +21,12 @@ QT_BEGIN_NAMESPACE
#define TERPRI() cl_terpri(0)
#define STATIC_SYMBOL(var, name) \
static cl_object var = cl_intern(1, ecl_make_constant_base_string(name, -1));
static cl_object var = cl_intern(1, ecl_make_constant_base_string(name, -1));
#define STATIC_SYMBOL_PKG(var, name, pkg) \
static cl_object var = cl_intern(2, \
ecl_make_constant_base_string(name, -1), \
cl_find_package(ecl_make_constant_base_string(pkg, -1)));
static cl_object var = cl_intern(2, \
ecl_make_constant_base_string(name, -1), \
cl_find_package(ecl_make_constant_base_string(pkg, -1)));
#define LEN(x) fixint(cl_length(x))

View file

@ -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) {

View file

@ -1,6 +1,8 @@
#ifndef LQML_H
#define LQML_H
#undef SLOT
#include <ecl/ecl.h>
#include <QObject>
#include <QByteArray>

View file

@ -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;
}
view.setSource(url);
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

View file

@ -1,5 +1,4 @@
#include "marshal.h"
#include <ecl/ecl.h>
#include <QVariant>
#include <QObject>

View file

@ -1,6 +1,8 @@
#ifndef MARSHAL_H
#define MARSHAL_H
#undef SLOT
#include <ecl/ecl.h>
#include <QRectF>
#include <QVariant>
@ -12,12 +14,12 @@ QT_BEGIN_NAMESPACE
#define STRING_COPY(s) (s ? ecl_make_simple_base_string(s, -1) : ECL_NIL)
#define STATIC_SYMBOL(var, name) \
static cl_object var = cl_intern(1, ecl_make_constant_base_string(name, -1));
static cl_object var = cl_intern(1, ecl_make_constant_base_string(name, -1));
#define STATIC_SYMBOL_PKG(var, name, pkg) \
static cl_object var = cl_intern(2, \
ecl_make_constant_base_string(name, -1), \
cl_find_package(ecl_make_constant_base_string(pkg, -1)));
static cl_object var = cl_intern(2, \
ecl_make_constant_base_string(name, -1), \
cl_find_package(ecl_make_constant_base_string(pkg, -1)));
#define LEN(x) fixint(cl_length(x))

View file

@ -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
View 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

View file

@ -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
View 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))

View file

@ -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)

View file

@ -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*)

View file

@ -19,8 +19,8 @@
;: function. The variable *CALLER* will be bound to the calling QQuickItem,
;; if passed with 'this' as first argument to 'Lisp.call()' / 'Lisp.apply()'.
(let ((*caller* (if (zerop caller)
*caller*
(qt-object caller))))
*caller*
(qt-object caller))))
(apply (string-to-symbol function)
arguments)))
@ -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
View 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
}

View file

@ -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 {
CONFIG -= app_bundle
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

View file

@ -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"
:monolithic t
:type :static-library
:move-here "./"
:init-name "ini_LQML")
(setf *default-pathname-defaults*
(merge-pathnames "../../")) ; LQML root
(let ((from "lqml--all-systems.a")
(to "liblqml.a"))
(when (probe-file to)
(delete-file to))
(rename-file from to))
#-android
(progn
(asdf:make-build "lqml"
:monolithic t
:type :static-library
: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)))
#+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
View file

@ -0,0 +1,3 @@
mkdir build
mkdir build-android
mkdir build-ios