Файл qml что это
QML и C++. Простой пример связки
Задача. Нужно написать программу QML в связке с С++, где
1. На форме располагается кнопка, строка ввода, и поле вывода.
2. Требуется считать из строки ввода число, прибавляется 1, и ответ выводится в поле вывода.
3. Интерфейс написан на QML.
4. Функционал на С++, то есть нам нужно обеспечить взаимосвязь между QML и C++: кнопка QML вызывает С++ функцию, а функция меняет свойства QML объектов.
Создание базового приложение QML
Создание QML приложения
2. В следующем окне выбираем название нашего проекта (без пробелов и русских букв). Например, в нашем случае это «Example«.
3. В следующем окне у Вас должна стоять галочка у «Desktop«. Если ее у Вас ее нет, то Вы неверно установили Qt Creator (или Вы намерено не хотите создавать десктопные приложения). Та сборка Qt Creator официальная, которую я ставил (2.3), по умолчанию почему-то десктопные части не устанавливала.
4. В следующем окне снимите галочку с пункта «Создать форму«.
5. В следующем окне можно ничего не менять. И жмем кнопку «Завершить«.
Редактирование файла проекта
6. Отредактируем файл проекта (у нас это Example.pro):
И добавим к строчке «QT += core gui» слово «declarative«. В итоге получим строчку:
Создание QML проекта
7. По папке с проектом в Qt Creator щелкаем правой кнопкой и идем к пункту «Добавить новый. »
8. Выбираем слева «QML«, а справа «Файл QML«.
9. Назовем его «main«.
10. Следующее окно без изменений.
11. В результате получим файл «main.qml» с текстом:
Создаем файл ресурсов
12. По папке с проектом в Qt Creator щелкаем правой кнопкой и идем к пункту «Добавить новый. «
13. Выбираем слева «Qt«, а справа «Файл ресурсов Qt«.
14. Назовем его «res«.
15. Следующее окно без изменений.
В результате получим файд «res.qrc«
16. Добавим префикс. Для этого щелкнем по кнопке «Добавить«, а там щелкнуть «Добавить префикс«.
17. Измените текст префикса на «/«.
18. Добавим наш QML файл. Для этого щелкнем по кнопке «Добавить«, а там щелкнуть «Добавить файлы«.
19. И выберем наш файл «main.qml«. И файл добавится к ресурсам нашего приложения:
Если сейчас запустим наше приложение, то qml пока не увидим:
Редактирование исходников
Теперь займемся подключением qml, чтобы он заработал.
20. Перейдем к редактированию файла «mainwindow.h» (находится в заголовочных). Он имеет пока вид:
Поменяем его на такой вид:
21. Теперь займемся редактированием файла mainwindow.cpp. Он имеет пока вид:
22. Запускаем наше приложение и получаем белое окно. Наш QML заработал.
Написание приложения
Теперь мы можем перейти к написанию непосредственно приложения, которое будет решать нашу задачу.
Построение интерфейса.
23. На данный момент main.qml выглядит так:
Отредактируем его, изменив главный прямоугольник-окно:
24. Добавим простейшую кнопку на нашу форму.
В результате main.qml примет вид:
Если мы запустим приложение,
то получим следующее:
25. Добавим строку ввода, куда пользователь будет вводить информацию с именем textinput.
В результате main.qml примет вид:
При запуске получим следующее:
26. Добавим поле вывода, куда программа будет выводить ответ с именем memo.
В результате main.qml примет вид:
При запуске получим следующее:
Итак, мы описали интерфейс нашей программы.
C++ часть
27. При нажатии на кнопку пока ничего не происходит. Исправим это. Для начала установим взаимосвязь между QML моделью и C++ кодом. Для этого отредактируем файл mainwindow.cpp, а именно функцию MainWindow, добавив строчки:
В результате получим:
29. Пусть у нас кнопка из QML будет вызывать С++ функцию под произвольным именем FunctionC. Объявим ее в mainwindow.h в классе в разделе public:
В результате получим:
Обратите внимание на ключевое слово Q_INVOKABLE. Именно она делает видимой функцию для QML.
30. Теперь опишем нашу функцию в mainwindow.cpp:
31. Ну и наконец добавим нашу функцию в обработчике нашей кнопки QML в файле main.qml. Обработчик действий мыши сейчас выглядит так:
Теперь же он станет таким:
Обратите внимание на то, что функцию вызываем через window.
Вот и всё! Теперь запускаем программу и нажимаем на кнопку. Если Вы всё сделали правильно и я не допустил ошибок, то Вы увидете:
Ссылка на исходники: Скачать.
Ссылка на исполняемый файл: Скачать.
Qt + QML на простом примере
Qt является удобным и гибким средством для создания кросс-платформенного программного обеспечения. Входящий в его состав QML предоставляет полную свободу действий при создании пользовательского интерфейса.
Об удобстве использования связки Qt и QML уже говорилось не раз, поэтому не буду дальше распространяться о плюсах, минусах, а приведу, шаг за шагом, пример простого Qt приложения.
Это будет минималистичное приложение, cмысл его простой — при нажатии на любую клавишу на клавиатуре на экране будет появляться случайное изображение и будет проигрываться, опять же, случайный звуковой эффект.
Пользовательский интерфейс будет полностью реализован на QML, программная часть на Qt.
Для тех, у кого Qt еще не установлен, заходим на страницу загрузки qt.nokia.com/downloads и в разделе «Qt SDK: Complete Development Environment» скачиваем бинарники для своей платформы. И, собственно, устанавливаем.
В новом окне вводим название проекта, указываем путь к проекту, жмем Далее.
В следующем окне снимаем галочку Создать форму, она нам не пригодиться, Далее. И в последнем просто нажимаем кнопку Завершить. Все, каркас нашего приложения создан.
Первым делом добавим модуль qt-declarative к нашему проекту, для этого, в файле проекта (4Toddler.pro), к строке
class MainWindow : public QDeclarativeView
<
.
>
От реализации конструктора отрежем, ставшую ненужной, инициализацию базового класса QMainWindow(parent).
Если сейчас собрать и запустить проект то мы увидим пустое окно. Так и должно быть, т.к. мы еще не создали и не инициализировали Qml интерфейс.
Добавим в проект новый QML файл, для этого щелкаем правой клавишей по проекту
Мастер создал нам файл, содержащий один элемент Rectangle, он и будет являться основным элементом для нашего пользовательского интерфейса. Добавим несколько новых свойств и зададим их значение
Rectangle
<
// Идентификатор, по нему будет происходить
// обращение к свойствам этого элемента
id: canvas;
// Цвет фона, черный
color: «black»
// Изменять размер под размеры
// родительского элемента
anchors.fill: parent
// Будет получать фокус ввода
focus: true
>
void MainWindow::Init()
<
// Путь к папке, содержащей QML файлы
QString contentPath;
#ifdef QT_DEBUG
// В отладочной версии это абсолютный путь к папке проекта
contentPath = «D:/MyProjects/QT/4Toddler» ;
#else
// В релизе это путь к папке, в которой расположено приложение
contentPath = QApplication::applicationDirPath();
#endif
setFocusPolicy(Qt::StrongFocus);
// Изменять размеры QML объекта под размеры окна
// Возможно делать и наоборот,
// передавая QDeclarativeView::SizeViewToRootObject
setResizeMode(QDeclarativeView::SizeRootObjectToView);
// Загрузить QML файл
setSource(QUrl::fromLocalFile(contentPath + «/main.qml» ));
>
Окно будет разворачиваться на весь экран. Прежде, чем запускать приложение, давайте добавим кнопку, с помощью которой можно будет закрыть окно. Забегая вперед скажу, кнопок в окне будет две, для того, чтобы не писать несоклько раз один и тот же код добавим к проекту новый QML файл, и назовем его WindowButton.
Элемент WindowButton мы будем использовать повторно, изменяя лишь определенные свойства у каждого экземпляра. Кнопки у нас будут выполнены в виде иконок, каждой из них мы будем задавать путь к файлу иконки и изменять обработчик нажатия левой клавишей мыши. Ниже приведен готовый код элемента с комментариями
// Область, обрабатывающая «мышиные» сообщения
MouseArea
<
// Действует в пределах всего
// элемента Image
anchors.fill: parent
// При нажатии вызвать метод callback
onClicked: callback()
>
>
// Элемент позволяющий
// распологать элементы горизонтально
Row
<
// Правый край элемента выравнивается
// по правому краю родительского элемента
anchors.right: parent.right;
// Отступ справа, 4 пикселя
anchors.rightMargin: 4;
// Верхний край эелемента выравнивается
// по верхнему краю родительского элемента
anchors.top: parent.top;
// Отступ сверху, 4 пикселя
anchors.topMargin: 4;
// Отступ между элементами
spacing: 4
WindowButton
<
// Кнопка возова диалога «О программе»
id: about;
// Путь к файлу с изображением
// в данном случае иконка лежит в той же папке,
// что и QML файл
source: «about.png» ;
// Метод, который будет вызываться
// при нажатии на кнопку левой клавишей мыши
// onClicked: callback()
function callback()
<
>
>
WindowButton
<
// Кнопка закрытия окна
id: exit;
Чтобы то, что мы сделали заработало нам осталось реализовать оба метода callback для каждой кнопки. Для закрытия окна мы вызовем метод Quit, который реализуем в классе окна. Для этого в объявление класс добавим
Обратите внимание, что вызывать можно только те методы, которые объявлены как Q_INVOKABLE, т.е. от же метод Init, главного окна вызвать не удастся.
Готово, запускаем, видим черный экран, все, что сейчас мы можем сделать это закрыть окно, нажав на кнопку exit. Нажали и видим, что состояние кнопки при наведении курсора и при нажатии никак не меняется, выглядит как «неживая». Оживим ее, добавив состояния:
Image
<
.
states:[
State
<
// Произвольное название
name: «hovered» ;
// Указание на то, когда элемент переходит в это состояние
// в данном случае когда нажата левая кнопка мыши
when: mouseArea.pressed;
// Какие свойства будут изменяться в этом состоянии
// в данном случае это будет прозрачность
PropertyChanges < target: button; opacity: 1;>
>,
State
<
name: «normal»
// В это состояние элемент будет переходить
// когда левая кнопка мыши не нажата
when: mouseArea.pressed == false ;
PropertyChanges < target: button; opacity: 0.7; >
>
]
>
Элемент может переходить в определенное состояние как автоматически при выполнении условия, указанного в when, так и вручную, путем изменения свойства state.
Запустили, нажали, прозрачность изменяется, уже лучше, но не хватает плавности. Добавим следующий код:
Image
<
.
Behavior on opacity
<
// Анимация с шагом в 100 миллисекунд
// Раз в 100 миллисекунд прозрачность будет изменяться
// на 0,1
NumberAnimation < duration: 100 >
>
>
Behavior очень полезный элемент для создания анимаций, позволяющий указать то как будет меняться указанное свойство, в данном случае прозрачность кнопки.
Запускаем и смотрим, совсем другое дело, плавный переход от полупрозрачного к непрозрачному состоянию.
Окно о программе будет реализовано полностью на QML. Это будет модальное окно, которое будет появляться при нажатии кнопки about. При щелчке левой клавишей мыши в любом месте окна оно будет исчезать. Добавим новый QML файл About.qml в проект.
Я приведу сразу весь код этого окна с пояснениями
// Главный элемент для диалогового окна
Rectangle
<
id: about
// Функция для отображения окна
// изменяет прозрачность главного элемента
function show()
<
about.opacity = 1;
>
// Функция для закрытия окна
function hide()
<
about.opacity = 0;
>
// Прозрачный задний фон
color: «transparent»
// Полностью прозрачен по умолчанию
opacity: 0
// Ширина и высота устанавливаются равными
// ширине и высоте родительского элемента
// в данном случае это элемент с id: canvas
width: parent.width
height: parent.height
// Видимым элемент будет считаться если выполняется условие
// opacity > 0
visible: opacity > 0
// Дочерний элемент, создающий полупрозрачный фон
Rectangle
<
anchors.fill: parent
// Дочерний элемент создающий который является диалогом
// «О программе. »
Rectangle
<
id: dialog
// Ширина и высота являются фиксированными
width: 360
height: 230
Text
<
text: «4 Toddler»
// Выравнивание элемента по центру
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
>
>
Behavior on opacity
<
NumberAnimation < duration: 100 >
>
MouseArea
<
// Элемент полностью заполняет родительский элемент
anchors.fill: parent;
// При клике в любом месте прячем окно
onClicked: hide();
>
>
Это не просто присвоение ширины, если в процессе отображения будет меняться ширина родительского элемента, то и ширина дочернего будет перерасчитана. Не знаю как вас, а меня в процессе «ковыряния» QML эта особенность приятно удивила. Так же интересна следующая строка:
Свойство может быть не только задано, но и вычислено.
Осталось добавить диалог и код для его отображения при нажатии кнопки about. В файл Main.qml добавим код, в конце элемента canvas
Для того, чтобы окно отображалось добавим строку
в функцию callback кнопки about
// Новое свойство объекта, необходимое
// для изменения состояния при удалении объекта
property bool remove: false
// При добавлении объекта
property bool show: false
opacity: 0;
fillMode: Image.Stretch;
states: [
State
<
// Состояние, в которое переходит объект
// тогда, когда нам нужно его удалить
name: «remove» ; when: remove == true ;
PropertyChanges < target: block; opacity: 0 >
StateChangeScript < script: block.destroy(1000); >
>,
State
<
// Состояние, в которое переходит объект
// тогда, когда нам нужно его отобразить
name: «show» ; when: show == true ;
PropertyChanges < target: block; opacity: 1 >
>
]
// Шаблон для создания новых элементов
var component = Qt.createComponent( «block.qml» );
// Максимальное количество элементов
var maxBlocksCount = 10;
// Массив, в котором будут храниться все эелементы
var blocksArray = new Array();
// Вызов функции, которая создаст новый элемент
// с указанными координатами
createNewBlock(x, y);
>
// Удалить лишние элементы
if (blocksArray.length > maxBlocksCount)
<
removeAllBlocks();
>
var newBlock = component.createObject(canvas);
if (newBlock == null )
<
return false ;
>
// Путь к файлу иконки доступен через свойство главного
// окна randomIcon
var iconFile = window.randomIcon;
newBlock.source = ( «Icons/» + iconFile);
newBlock.x = x;
newBlock.y = y;
// Переводим элемент в состояние show
newBlock.show = true ;
// Проигрываем случайный звуковой эффект
window.PlaySound();
// Удаление всех добавленных элементов
function removeAllBlocks()
<
for ( var i = 0; i true ;
>
Как видно из кода нам еще следует реализовать свойство randomIcon и функицию PlaySound главного окна.
Добавим свойство в объявление класса MainWindow
Q_PROPERTY(QString randomIcon READ RandomIcon)
И объявление функции
QString MainWindow::RandomIcon()
<
QStringList iconFilesList;
QString searchPath = m_ContentPath + «/Icons/» ;
QDir directory = QDir(searchPath);
QStringList filters;
filters «*.png» ;
directory.setNameFilters(filters);
// Получаем список файлов с расширением png
iconFilesList = directory.entryList(QDir::AllEntries);
// Получаем случайный индекс элемента
int fileIdx = qrand() % iconFilesList.count();
// Возвращаем название файла
return iconFilesList.at(fileIdx);
>
void MainWindow::PlaySound()
<
QStringList soundFilesList;
QDir directory = QDir(m_ContentPath + «/Sounds/» );
QStringList filters;
filters «*.wav» ;
directory.setNameFilters(filters);
// Получаем список файлов с расширением wav
soundFilesList = directory.entryList(QDir::AllEntries);
// Получаем случайный индекс элемента
int fileIdx = qrand() % soundFilesList.count();
// Получаем название файла
QString soundFile = m_ContentPath + «/Sounds/» + soundFilesList.at(fileIdx);
// Проигрываем файл
QSound::play(soundFile);
>
Почти все, осталось добавить обработчик нажатия клавиш в наш корневой элемент вызов фунции создания нового элемента. В начален файла main.qml сделаем видимым наш скрипт в файле main.qml
и сам обработчик внутри элемента canvas
На этом все — можем запускать и любоваться.
Как я и обещал программа является простой, но, на мой взгляд, это достаточно интересная «игровая» площадка для тех, кто только начинает изучать QML. Нет предела совершенству, кто знает, может кто-нибудь разовьет ее во что-то более стоящее.
QML — больше, чем просто GUI
Пятница, как известно, — конец рабочей недели, лучшее время для развлечения и игр. А лучшие игры — это те, которые увлекательны, в которых надо немножко подумать, и в которые можно сыграть с друзьями. Одну из таких игр я и решил написать.
Этот пост не является очередным переводом или вольным изложением разнообразных QML Howto и Quick Start. Скорее, это описание подводных камней, с которыми можно столкнуться при написании реального приложения.
Когда Qt Quick/QML только было заявлено, от Нокии звучали слова, что «в перспективе не только пользовательский интерфейс будет писаться на Qt Quick, но и вся логика несложных приложений будет написана на яваскрипте, программистам не потребуется написать ни строчки кода на плюсах». Заявление было ещё более провокационное, чем мой заголовок, и сразу меня заинтересовало: я решил попробовать написать несложную игру без единой строчки кода на плюсах.
Об остальном мы узнаем под катом.
Версия первая, или война с драг-н-дропом
В качестве отправной точки была выбрана игра Цаар — настольная логическая игра для двух игроков. Я искренне надеюсь, что моя коварная реализация данной игры не вызовет негодования правообладателей, но тем не менее рекомендую заинтересовавшимся её купить — в живом виде она куда интереснее, чем в моём исполнении.
А мы возвращаемся к разработке. Первым делом я десять минут созерцал игровое поле (на рисунке сверху): во всех приличных играх оно, как известно, прямоугольное. По истечении десяти минут я сказал: «Ага!», дополнил картинку узлами так, чтобы образовалась прямоугольная сетка, после чего написал функцию для проверки, существует ли узел на самом деле.
Я так подробно расписываю свои экзистенциальные метания и так мало привожу кода, чтобы прям сейчас добавить: это самые большие проблемы, с которыми я встретился в процессе. Это действительно так. Спозиционировать поле на форме, сгенерировать и расставить фишки, сделать самодельные кнопки и вывод статуса — нет проблем! Анимировать перемещение фишек — 2 строчки! Разрешать и запрещать игроку двигать различные фишки в зависимости от фазы хода — всё это в QML делается настолько элементарно, что я могу только предложить почитать официальный мануал с примерами. К окончанию работы, js-файл со всей основной логикой, включая пустые строки и комментарии, занимал аж 173 строки или 6 функций.
Ах нет, пожалуй, я вспомнил один момент, который меня изумил и вынудил написать костылик. Этот момент называется: drag’n’drop. Да, это звучит странно, но драг-н-дроп в чуть менее, чем полностью графическом тулките сделан хреново. «Таскать» можно, оказывается, не элемента, а MouseArea, который на нём лежит. Единственное, что мы можем — это определить, какими кнопками можно жмякать, и какие ограничения по координатам у нас есть. Нельзя, как при работе с системой, обработать событие «в меня чем-то кинули, что это», нельзя разрешить кидаться элементом только в определенные объекты. Можно только обработать события pressed и released. Дальше крутись как хочешь. А в примерах, если мне не изменяет память, такими глупостями вообще занимаются только со всякими Grid’ами и List’ами, никаких тебе произвольно спозиционированных элементов. Видимо из-за этого, кстати, сказать элементу «мне то, как тебя кинули, не понравилось, вернись на место», тоже нельзя. Я же говорю, разработчики только о разборе RSS думали.
Поэтому пришлось поступать следующим образом. Свойством элемента, очевидно, являются не его координаты на экране — x и y — а позиция на доске. Координаты высчитываются исходя из позиции. При событиях pressed и released мы запоминаем исходную позицию и вычисляем, в какую новую пытались кинуть элементом. После этого вызываем функцию, отвечающую за перемещение элемента. Если функция говорит нам, что перемещение невозможно, нам надо сделать с элементом что? Правильно, вернуть в исходную позицию. Смотрите внимательно за руками:
Видите это присваивание минус единицы? Ага. Дело в том, что в QML, если присвоить свойству то значение, которое в нём уже лежит (oX и oY), то движок считает, что свойство не изменилось, и не пересчитывает всё, то что с ним связано, в нашем случае это абсолютные координаты на экране. Приходится присваивать некоторое заведомо отличающееся значение, и только потом исходное.
Сама реализация драг-н-дропа выглядит вот так:
И вот на этом все проблемы действительно закончились. В игру можно было поиграть, выиграть за белых, выиграть за чёрных. Но — на одном компьютере. Это было скучно, я жаждал игры по сети с друзьями. И тут на сцену выходит вторая итерация разработки, гораздо более интересная.
Где в этот момент я начал задумываться и о третьей проблеме, но пока до неё ещё не дошло, мы поговорим о сети.
Как я обнаружил, в QML очень бедная и грустная поддержка работы с сетью. У нас есть Ajax-запросы из javascript-движка (причём, только асинхронные), и у нас есть активно измусоленный во всех примерах XmlListModel. Мне не хотелось бы верить, что весь QML был создан исключительно для лёгкого разбора RSS-потоков.
Как бы то ни было, забегая вперёд, скажу, что самой наглядной иллюстрацией бедности работы с сетью в QML является следующая строчка:
Если коротко, я хотел бы при закрытии игры отправлять на сервер сообщение, что я отключился и сессию можно убивать. Проблема вся в том, что при приходе сигнала я создаю асинхронный ajax-запрос, «отправляю» его, а дальше… а дальше наш цикл событий (event loop) успешно продолжает работу и штатно завершает работу программы — ведь причиной сигнала был нажатый крестик в углу окна. Вуаля! Запрос никогда на самом деле не успеет дойти до сервера. Никогда. Но я пытаюсь, я верю в лучшее.
Ну а пока это всё в будущем, сейчас я ещё смотрю на два варианта общения с сетью и, естественно, отбрасываю XmlListModel. Не будь установки использовать только QML, разумеется, можно было бы просто открыть сокет, но я решил выжать всё необходимое из яваскрипта.
С одной стороны, без этого приложение, наверное, не стало бы таким интересным, как сейчас. С другой — я поимел кучу проблем. Об этом и расскажу.
Я перерыл интернет, я обнаружил инструкцию — принудительно сменить кодировку ответа на x-custom-charset — я обнаружил, что js-движок в Qt это не позволяет, и сменил кодировку прямо на сервере. После этого я скачал готовую библиотеку BinaryReader.js, которая уверяла, что способна читать текст по байтам и попробовал читать результат ещё и ей. Всё было тщетно — JS упорно отдавал мне 0xff вместо моего байта. На своё счастье я обнаружил, что индексы всех разрешенных для фишек ячеек — чётные. Я стал делить их пополам при передаче, и это позволило читать данные, как положено. В итоге код оброс ещё всего одним компонентом на 170 строк, который взаимодействовал с сервером и позволял насладиться полноценной сетевой игрой, а я подошёл к последней проблеме — о ней сразу после рекламы.
Версия третья, или куда пихать?
Интерфейсы на QML проектируются действительно очень легко и быстро, даже такими интерфейсо-ненавистниками, как я.
На QML можно написать игру или приложение, работающее с онлайн-сервисом. Если аккуратно обогнуть спрятанные грабли, то простота и удобство вас очень приятно удивят.
При толике желания из всего кода можно сделать один приятный бинарник со всеми включенными ресурсами, который и распространять
Работа с сетью всё-таки хромает. Мне кажется, разработчики игр для мобильных устройств (особенно в свете пиара NFC и тому подобного) были бы счастливы, будь у них возможность нормально установить соединение между устройствами, не вылезая на уровень C++
Искоробочный пример в qtcreator — сломан. Это страшный минус. Когда неправильно работает приложенный к IDE хеллоуворлд, это кидает страшную тень на всю библиотеку. В данном случае это наезд в сторону разработчиков QtCreator, а не самой технологии. Тем не менее, имейте в виду. Возможно, в версиях более поздних, чем моя 2.2.1, эту проблему исправили.
Судя по отпиленному синхронному режиму в XMLHttpRequest и кривой работе с побайтовым чтением, у меня сложилось ощущение, что JS-движок в Qt местами ущербен. Будьте аккуратнее.
Желающие сыграть в эту замечательную игру могут прочитать правила (возможно, их русская версия от Игроведа будет понятнее), скомпилировать игру командой qmake && make, и сыграть — только помните, что вам нужен партнёр, который тоже подключится к серверу.
Пользователи 32-битной Ubuntu 11.10 (может, и других систем, не обещаю) могут без затей скачать архив: sorokdva.net/tzaar/tzaar.tar.gz и запустить уже собранный бинарник. Для работы нужен пакет libqtwebkit-qmlwebkitplugin.
И если кому-то вдруг показалось, что это я усиленно ругаю QML, то я напомню два момента. Первый: на QML я написал игру в свободное время за, в общей сложности, максимум 40 часов (на самом деле меньше). Второй: на традиционном Qt и с его работой с графикой я написать игру не смог. И это должно уже говорить само за себя. А я в общем-то не то чтобы неосилятор.