diff --git a/docker/Dockerfile b/docker/Dockerfile index 05a7fc0..7f9fcb5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,36 +1,36 @@ # Dockerfile for a development environment for rust_qt_binding_generator FROM ubuntu:17.10 USER root RUN apt-get update && apt-get dist-upgrade -y # build dependencies and many extra qml modules RUN apt-get update && apt-get dist-upgrade -y && \ apt-get install -y --no-install-recommends \ - qtcreator rustc cargo ninja-build cmake g++ extra-cmake-modules \ + qtcreator rustc cargo ninja-build make cmake g++ extra-cmake-modules \ kdesdk-scripts \ gdb valgrind \ qtdeclarative5-dev-tools qtdeclarative5-dev qtquickcontrols2-5-dev \ libqt5charts5-dev libqt5svg5-dev qml-module-qtcharts \ qt5-doc qtbase5-doc qtcharts5-doc qtcreator-doc qtquickcontrols2-5-doc \ qtquickcontrols5-doc qtsvg5-doc qttools5-doc RUN apt-get update && apt-get dist-upgrade -y && \ apt-get install -y --no-install-recommends \ qtquickcontrols2-5-examples kirigami2-dev # qml-module-qt-labs-platform RUN apt-get update && apt-get dist-upgrade -y && \ apt-get install -y --no-install-recommends qt5-default # these are extra # RUN apt-get update && apt-get dist-upgrade -y && \ # apt-get install -y --no-install-recommends qbs-doc qml-module-org-kde-activities qml-module-org-kde-analitza qml-module-org-kde-bluezqt qml-module-org-kde-draganddrop qml-module-org-kde-extensionplugin qml-module-org-kde-games-core qml-module-org-kde-kaccounts qml-module-org-kde-kconfig qml-module-org-kde-kcoreaddons qml-module-org-kde-kholidays qml-module-org-kde-kio qml-module-org-kde-kirigami qml-module-org-kde-kirigami2 qml-module-org-kde-kquickcontrols qml-module-org-kde-kquickcontrolsaddons qml-module-org-kde-kwindowsystem qml-module-org-kde-newstuff qml-module-org-kde-people qml-module-org-kde-purpose qml-module-org-kde-runnermodel qml-module-org-kde-solid qml-module-org-kde-telepathy qml-module-qt-labs-calendar qml-module-qt-labs-folderlistmodel qml-module-qt-labs-platform qml-module-qt-labs-settings qml-module-qt-labs-sharedimage qml-module-qt-websockets qml-module-qt3d qml-module-qtaudioengine qml-module-qtbluetooth qml-module-qtfeedback qml-module-qtgraphicaleffects qml-module-qtgstreamer qml-module-qtlocation qml-module-qtmultimedia qml-module-qtnfc qml-module-qtpositioning qml-module-qtpurchasing qml-module-qtqml-models2 qml-module-qtqml-statemachine qml-module-qtquick-controls qml-module-qtquick-controls-styles-breeze qml-module-qtquick-controls2 qml-module-qtquick-dialogs qml-module-qtquick-extras qml-module-qtquick-layouts qml-module-qtquick-localstorage qml-module-qtquick-particles2 qml-module-qtquick-privatewidgets qml-module-qtquick-scene2d qml-module-qtquick-scene3d qml-module-qtquick-sharedimage qml-module-qtquick-templates2 qml-module-qtquick-virtualkeyboard qml-module-qtquick-window2 qml-module-qtquick-xmllistmodel qml-module-qtquick2 qml-module-qtsensors qml-module-qtsysteminfo qml-module-qttest qml-module-qtwayland-compositor qml-module-qtwebchannel qml-module-qtwebengine qml-module-qtwebkit qml-module-qtwebsockets qml-module-qtwebview qt-at-spi-doc qt3d5-doc qtconnectivity5-doc qtgraphicaleffects5-doc qtgstreamer-doc qtiplot-doc qtlocation5-doc qtmultimedia5-doc qtnetworkauth5-doc qtpim5-doc qtpositioning5-doc qtscript5-doc qtsensors5-doc qtserialport5-doc qtspeech5-doc qtwayland5-doc qtwebchannel-doc qtwebchannel5-doc qtwebengine5-doc qtwebkit5-doc qtwebkit5-examples-doc qtwebsockets5-doc qtwebview5-doc qtx11extras5-doc qtxmlpatterns5-doc RUN useradd neon USER neon WORKDIR /home/neon CMD ["/bin/bash", "-l"] diff --git a/tutorial/time_for_rust_and_qml.md b/tutorial/time_for_rust_and_qml.md index cb33771..07f2376 100644 --- a/tutorial/time_for_rust_and_qml.md +++ b/tutorial/time_for_rust_and_qml.md @@ -1,415 +1,417 @@ # Time for QML If you are new to QML, I can recommend the [QML book](https://qmlbook.github.io/). It walks you through many wonderful examples. In addition, Qt Creator comes with [many more](https://doc.qt.io/qt-5/qtexamples.html). It is a tradition in KDE to use clocks as examples. I will follow this tradition and create a widget that shows the time. We'll start without any Rust at all. The first code is only QML. It uses the Rust logo as SVG image for the background, [`rust-logo-blk.svg`](https://www.rust-lang.org/logos/rust-logo-blk.svg). So download this file (or pick another SVG image and store it with that name). The syntax of QML is declarative. The rotation of the Rust logo is given by the statement `angle: time.second * 6`. This is a binding. The value of `angle` updates automatically whenever `time.second` changes. The rotation of the logo changes every second because of this declarative binding. Another example is `anchors.fill: parent` on the `Image` item. This means that the image takes up the same rectangular space as the parent item. If that item is resized, the image will scale along with it. In this file, we added a temporary `QtObject` with properties `hour`, `minute` and `second`. The values in this object are updated every second by the `Timer` item. The JavaScript code between `{}` runs every second and updates the values in the `QtObject`. This object has `id: time` and the logo and hands are bound to this object. The `QtObject` is a functional placeholder for the Rust code that we are going to write later. ```qml // just_qml.qml import QtQuick 2.5 import QtQuick.Window 2.2 Window { width: 512 height: 512 visible: true // A mock-up of the time object that we will // implement in Rust QtObject { id: time property int hour property int minute property int second } // This timer will also become Rust code Timer { interval: 1000; running: true; repeat: true onTriggered: { var date = new Date() time.hour = date.getHours() time.minute = date.getMinutes() time.second = date.getSeconds() } } // the clock face Image { anchors.fill: parent source: "rust-logo-blk.svg" sourceSize: Qt.size(width, height) // ensure rendered SVG canvas matches used size fillMode: Image.PreserveAspectFit transform: Rotation { origin.x: width / 2 origin.y: height / 2 angle: time.second * 6 // convert seconds to degrees } } // the minute hand Rectangle { id: minute x: (parent.width - width) / 2 y: 0 width: parent.width / 100 height: parent.height / 1.8 radius: width color: "#3daefd" transform: Rotation { origin.x: hour.width / 2 origin.y: height / 2 // convert minutes to degrees angle: time.minute * 6 } } // the hour hand Rectangle { id: hour x: (parent.width - width) / 2 y: parent.height / 6 width: parent.width / 50 height: parent.height / 2.8 radius: width color: "#3daefd" transform: Rotation { origin.x: hour.width / 2 origin.y: height / 3 // convert hours to degrees angle: time.hour * 30 + time.minute / 2 } } } ``` You can run the plain QML file with the tool `qmlscene`. ``` $ qmlscene just_qml.qml ```
Time for Rust and QML
Time for Rust and QML
`qmlscene` can run any plain QML files. If you have QML plugins installed, these can be used too. You can make plugins that are implemented in Rust, but we'll not go into that now. ## Set up a QML project with Rust Before we can replace the `QtObject`, we have to set up a project. Rust Qt Binding Generator comes with a template project for QML in the folder [`templates/qt_quick`](https://commits.kde.org/rust-qt-binding-generator?path=templates/qt_quick). You can get set up like so. You will need to have Qt, Rust and CMake installed. First build `rust_qt_binding_generator`. ``` $ git clone git://anongit.kde.org/rust-qt-binding-generator $ mkdir build $ cd rust-qt-binding-generator/build $ cmake .. $ make rust_qt_binding_generator $ export PATH=$PATH:$PWD/src ``` +`cmake ..` uses `make` by default, but you can use another build tool, for example Ninja, like this: `cmake -GNinja ..`. + Now build and run the template project. ``` $ mkdir ../templates/qt_quick/build $ cd ../templates/qt_quick/build $ cmake .. $ make $ ./MyExe ``` You will be greeted with a 'Hello World' application. ## Starting from a template So what just happened? The template project is based on CMake. CMake is the build system that most KDE projects use. A template in CMake is an example of how to add Rust code to KDE programs. It is possible to use another build system. CMake performs four steps as instructed by the `CMakeLists.txt` file. It 1) generates Rust and C++ from `bindings.json` by calling `rust_qt_binding_generator`, 2) compiles the Rust code in `rust/` into a static library by calling `cargo`, 3) compiles the C++ code, 4) links the C++ objects, the QML files, and the Rust library into an executable. If you prefer to use only `cargo`, you'll have to tell it to perform steps 1, 3 and 4 in a `build.js` file. ## Adding some Rust Now let's turn this clock into the [Antikythera mechanism](https://en.wikipedia.org/wiki/Antikythera_mechanism) by adding some Rust. We want the Rust code to have a Time object that indicates the hour, the minute and the second. We write this interface into `bindings.json`. ```json { "cppFile": "src/Bindings.cpp", "rust": { "dir": "rust", "interfaceModule": "interface", "implementationModule": "implementation" }, "objects": { "Time": { "type": "Object", "properties": { "hour": { "type": "quint32" }, "minute": { "type": "quint32" }, "second": { "type": "quint32" } } } } } ``` Now if we run `make` again, three files will be updated: `src/Bindings.h`, `src/Bindings.cpp`, and `rust/src/interface.rs`. And then we'll get a compile error from `cargo`. That is because we have to adapt `rust/src/implementation.rs` to the new `interface.rs`. `interface.rs` specifies a trait that must be implemented in `implementation.rs`. This is the generated trait: ```rust // rust/src/interface.rs pub trait TimeTrait { fn new(emit: TimeEmitter) -> Self; fn emit(&self) -> &TimeEmitter; fn hour(&self) -> u32; fn minute(&self) -> u32; fn second(&self) -> u32; } ``` Note that the trait has getters, but no setters. With `"write": true`, you can add setters on properties. For now, we implement a fixed time in our new `implementation.rs`. ```rust // rust/src/implementation.rs use interface::*; pub struct Time { emit: TimeEmitter } impl TimeTrait for Time { fn new(emit: TimeEmitter) -> Self { Time { emit } } fn emit(&self) -> &TimeEmitter { &self.emit } fn hour(&self) -> u32 { 1 } fn minute(&self) -> u32 { 52 } fn second(&self) -> u32 { 0 } } ``` Now whenever the QML application wants to know the time, it can ask the Rust code. Well, almost. We have to change three more files and one of them is a C++ file. It is a very simple change and it is needed to tell the QML code about the Rust QObject. In `src/main.cpp`, change this line: ```c++ // src/main.cpp qmlRegisterType("RustCode", 1, 0, "Simple"); ``` to this ```c++ // src/main.cpp qmlRegisterType