forum.boolean.name

forum.boolean.name (http://forum.boolean.name/index.php)
-   C++ (http://forum.boolean.name/forumdisplay.php?f=22)
-   -   Qt QML : декларативный интерфейс, императивная логика (http://forum.boolean.name/showthread.php?t=13392)

jimon 23.09.2010 02:05

Qt QML : декларативный интерфейс, императивная логика
 
Qt захватит мир ! >>:(

при разработке интерфейсов мы очень часто сталкиваемся с огромным количеством рутины, обычно нужно работать со сложным конечным автоматом, кучей асинхронных анимаций и отзывчивостью к юзеру

императивный путь работы с интерфейсом тернист, а звёзд редко кто достигает, а если и достигает то следующий раз опять все тернии надо проходить (редко какой интерфейсный проект проще поддерживать чем переписать заново), а вот декларативный способ задания интерфейса всё меняет местами, мы задаем только то что хотим получить, всё остальное на себя берет декларативный движок

добавим к этому всему что Qt имеет систему сигналов-слотов и свой препроцессор (в общем-то свою билд-систему, и свой IDE :-) ), и что же мы получим ? мы получим QML, декларативный язык задания интерфейса со сквозным двухсторонним биндингом с C++ кодом ! другими словами мы теперь пишем интерфейс на языке подобном html и он работает напрямую с нашей C++ логикой (слава чистому MVC, слава)

перейдем к примеру :

обычный класс на Qt :
Код:

class Stopwatch : public QObject
 {
    Q_OBJECT
 public:
    Stopwatch();

    Q_INVOKABLE bool isRunning() const;

 public slots:
    void start();
    void stop();

 private:
    bool m_running;
 };

к обычным методам (не сигналам и не слотам) добавляется только Q_INVOKABLE перед декларацией метода, что в сотни-тыщи-миллионы-раз проще и лучше любого биндинга к скриптовым системам, собственно слоты start и stop уже можно использовать из QML

что мы пишем в qml :
Код:

import Qt 4.7

 Rectangle {
    width: 300
    height: 300

    MouseArea {
        anchors.fill: parent
        onClicked: {
            if (stopwatch.isRunning())
                stopwatch.stop()
            else
                stopwatch.start();
        }
    }
 }

как видим тут объявляется область 300 на 300, в ней создается область работы с мышкой на весь размер родительской области, а по нажатию вызываются наши C++ методы ! (так же возможно наоборот - вызывать QML функции из C++ кода)

сам QML очень гибкий и базируется на JavaScript, что гарантирует некую лёгкость и гибкость изложения кода, любой валидный JavaScript код можно использовать как значение для параметров объектов, сам QML имеет встроенные средства для задания стейтов (кнопка имеет нажатый и не нажатый стейт к примеру), можно задавать любые параметры в стейтах, и имеет средства анимации между стейтами :wild: так же имеются встроенные средства для анимации параметров

к примеру :
Код:

Rectangle {
    id: rect
    width: 120; height: 200

    Image {
        id: img
        source: "pics/qt.png"
        x: 60 - img.width/2
        y: 0

        SequentialAnimation on y {
            loops: Animation.Infinite
            NumberAnimation { to: 200 - img.height; easing.type: Easing.OutBounce; duration: 2000 }
            PauseAnimation { duration: 1000 }
            NumberAnimation { to: 0; easing.type: Easing.OutQuad; duration: 1000 }
        }
    }
 }

реализация QML называется Qt Declarative, доступна в релизе Qt 4.7, визуальный редактор для QML будет встроен в Qt Creator 2.1 (сейчас в релизе 2.0.1, 2.1 скоро будет :))

Qt 4.7 доступен на огромнейшем количестве разнообразных платформ, написал один раз и оно работает на симбиане, meego, windows, linux, mac и тд и тд (собсно сам Qt под LGPL лицензией)

ps. сигналы - слоты это механизм передачи вызовов сигналов в вызов слота, ближайшая аналогия - визуальное программирование логики, там тоже стрелочки между блоками таскаем, откуда тащим - сигнал, куда - слот, вызывая один из нескольких сигналов (физически декларируем метод, реализует сам Qt), вызывается слот к которому сигналы привязаны (физически описывается как метод только в секции slots)

jimon 27.09.2010 02:21

Ответ: Qt QML : декларативный интерфейс, императивная логика
 
Вложений: 1
чтобы опробовать инструмент решил написать понг чисто на QML, в принципе результирующий код вышел немного громоздким из-за асинхронного javascript кода и qml анимаций

мышкой управляются обе платформы, скорость игры постепенно увеличивается, необходимо набрать наибольшее количество очков :)

Код:

// imports
import Qt 4.7

// root object
Rectangle
{
        id:screen; width:1024; height:768; color:"#DDDDDD"

        // game text, show status of the game
        Text
        {
                id:gameText;
                anchors.top:screen.top; anchors.horizontalCenter:screen.horizontalCenter
                text:"QML pong by jimon"
        }

        // score text
        Text
        {
                id:gameScoreText;
                anchors.top:screen.top; x:5;
                font.family: "Courier New"; font.pointSize: 16; color: "#000000"

                function zeroPad(num, count)
                {
                        while(num.length < count) num = "0" + num;
                        return num;
                }

                function setScore(score)
                {
                        text = "SCORE : " + zeroPad(score.toString(), 7)
                }

                Component.onCompleted:setScore(0);
        }

        // ball
        Rectangle
        {
                id:ball; width:64; height:64; color:"#000000"
                x:(screen.width - ball.width) / 2; y:(screen.height - ball.height) / 2

                property variant wayX: 0 // ball way X
                property variant wayY: 0 // ball way Y
                property double speed: 0
                property double speedDefault: 0.25 // in pixels per millisec

                // moving, use number animation on x and y, calculate duration by hands
                // moving by javascript and etc is a really BAD idea

                NumberAnimation on x {id:ballAnimX}
                NumberAnimation on y {id:ballAnimY}

                function moveTo(newV, oldV, anim)
                {
                        anim.stop();
                        anim.duration = Math.abs(oldV - newV) / speed;
                        anim.from = oldV;
                        anim.to = newV;
                        anim.start();
                }

                function moveToX(newX)
                {
                        moveTo(newX, ball.x, ballAnimX)
                }

                function moveToY(newY)
                {
                        moveTo(newY, ball.y, ballAnimY)
                }

                function stopAnimations()
                {
                        ballAnimX.stop();
                        ballAnimY.stop();
                }

                function setWayX(newWayX)
                {
                        if(newWayX != wayX)
                        {
                                ball.moveToX(newWayX == 1 ? screen.width - ball.width : 0);
                                wayX = newWayX;
                        }
                }

                function setWayY(newWayY)
                {
                        if(newWayY != wayY)
                        {
                                ball.moveToY(newWayY == 1 ? screen.height - ball.height : 0);
                                wayY = newWayY;
                        }
                }
        }

        // left platform
        Rectangle
        {
                id:left; width:32; height:128; color:"#5555FF"; anchors.left:screen.left
                y:(screen.height - left.height) / 2
        }

        // right platform
        Rectangle
        {
                id:right; width:32; height:128; color:"#5555FF"; anchors.right:screen.right
                y:(screen.height - right.height) / 2
        }

        // game logic timer
        Timer
        {
                interval:20; running:true; repeat:true
                onTriggered:gameTick();
        }

        // work with mouse
        MouseArea
        {
                id:mouse;
                anchors.fill:screen; hoverEnabled:true
                onPositionChanged:gameUpdateMouse();
                onClicked:gameRestart();
        }

        property variant gameCanRestart:1;
        property variant gameScore:0;

        function gameTick()
        {
                if(!gameCanRestart) // if game running
                {
                        gameScore = gameScore + 10;
                        gameScoreText.setScore(gameScore);

                        ball.speed = ball.speed * 1.001;
                }

                if(gamePlatformCheck(left)) // horizontal moving
                        ball.setWayX(1);
                if(gamePlatformCheck(right))
                        ball.setWayX(-1);
                else if(ball.x <= 0 || ball.x >= screen.width - ball.width)
                        screen.gameLose();

                if(ball.y >= screen.height - ball.height) // vertical moving
                        ball.setWayY(-1);
                else if(ball.y <= 0)
                        ball.setWayY(1);
        }

        function gamePlatformCheck(platform)
        {
                return (Math.abs(platform.x - ball.x + (platform.width - ball.width) / 2) < (platform.width + ball.width) / 2) &&
                  (Math.abs(platform.y - ball.y + (platform.height - ball.height) / 2) < (platform.height + ball.height) / 2);
        }

        function gameUpdateMouse()
        {
                left.y = mouse.mouseY - left.height / 2;
                right.y = mouse.mouseY - right.height / 2;
        }

        function gameLose()
        {
                gameText.text = "Game Over !";
                ball.stopAnimations();
                ball.x = (screen.width - ball.width) / 2;
                ball.y = (screen.height - ball.height) / 2;

                gameCanRestart = 1;
        }

        function gameRestart()
        {
                if(gameCanRestart)
                {
                        gameScore = 0;
                        gameScoreText.setScore(gameScore);

                        ball.speed = ball.speedDefault;

                        ball.wayX = 0;
                        ball.wayY = 0;
                        ball.setWayX(-1);
                        ball.setWayY(-1);
                        gameText.text = "Game Started !";
                        gameCanRestart = 0;
                }
        }
}

основная громоздкость в необходимости хранить направление движения, потому что функция moveTo выполняется асинхронно с анимациями и может банально не дать анимации стартовать

подход выполнения логики в javascriptе мне совсем не понравился, он слишком сырой сейчас, попробую еще реализовать логику на C++

Morganolla 27.09.2010 20:46

Ответ: Qt QML : декларативный интерфейс, императивная логика
 
Один заголовок темы тянет на статью в солидном западном журнале:cool:
Дядя Дима не пугай детей...:4to:
ЗЫ Отвлеченный вопрос, сорри. А что такое explicit? Это термин из С++ или...?

jimon 27.09.2010 21:56

Ответ: Qt QML : декларативный интерфейс, императивная логика
 
Morganolla
Цитата:

А что такое explicit?
запрет на автоприведение типов при вызове конструктора

к примеру :
Код:

class A
{
A(int V){ ... }
};
...
A foo = 5;    // работает
A foo = A(5); // работает

Код:

class A
{
explicit A(int V){ ... }
};
...
A foo = 5;    // не работает
A foo = A(5); // работает


jimon 05.01.2013 04:10

Ответ: Qt QML : декларативный интерфейс, императивная логика
 
вышел qt quick 2, намного более шустрый фреймворк для qml

в итоге 1200 прямоугольников, которые случайно перемещаются по экранму и меняют цвет, выдает 60 фпс
Код:

import QtQuick 2.0
 
Rectangle
{
        width: 1024
        height: 768
        focus: true
 
        Repeater
        {
                id: rep;
                model: 1200
 
                Rectangle
                {
                        id: rectt;
                        width: 16; height: 16;
                        color: "black";
 
                        SequentialAnimation
                        {
                                running: true; loops: -1;
                                ColorAnimation { target: rectt; property:"color"; from: "black"; to: "yellow"; duration: 1000;}
                                ColorAnimation { target: rectt; property:"color"; from: "yellow"; to: "black"; duration: 1000;}
                        }
                }
 
        }
        Timer
        {
                interval: 16; running: true; repeat: true;
                onTriggered:
                {
                        for(var i = 0; i < rep.count; i++)
                        {
                                var o = rep.itemAt(i);
                                o.x = Math.random()*1024;
                                o.y = Math.random()*768;
                        }
                }
        }
 
        Keys.onPressed: { if(event.key == Qt.Key_Escape) Qt.quit(); }
}

а сколько html5 на таком коде выдаст ? :crazy:

еще одна хорошая новость, Desktop Components for QML почти готовы к релизу и будут включены в qt 5.1
посмотрите насколько просто и быстро создавать настольные приложения : http://www.youtube.com/watch?v=Y1pqL5bXe0A

moka 05.01.2013 05:46

Ответ: Qt QML : декларативный интерфейс, императивная логика
 
http://moka.co/rects/
Chrome: 150
FF: 116
1200 прямоугольников, цвет циклируется.
Шустро же ;)

Skaner 05.01.2013 10:28

Ответ: Qt QML : декларативный интерфейс, императивная логика
 
Моя опера выдала ~20 фпс... Chrome (Яндекс.Б) ~20, Ыфафкш (Safari то есть под винду) ~65 фпс...

jimon 05.01.2013 14:05

Ответ: Qt QML : декларативный интерфейс, императивная логика
 
Цитата:

Сообщение от MoKa (Сообщение 248349)
http://moka.co/rects/
Chrome: 150
FF: 116
1200 прямоугольников, цвет циклируется.
Шустро же ;)

попробуй не canvas, а на dom дереве, а то не интересно : в qml тесте создаются честные 1200 объектов, я кстати чуть модифицировал тест вчера и с 60 фпс рендерило уже 1700 объектов

ps. модифицированый тест http://pastebin.com/ATdvC6PK, фпс мерялся фрапсом

Randomize 05.01.2013 15:50

Ответ: Qt QML : декларативный интерфейс, императивная логика
 
Цитата:

Сообщение от jimon (Сообщение 248371)
попробуй не canvas, а на dom дереве, а то не интересно : в qml тесте создаются честные 1200 объектов, я кстати чуть модифицировал тест вчера и с 60 фпс рендерило уже 1700 объектов

Попробуй игры в экселе писать.
HTML нужен только для представления данных, а СSS для их стилизации.
Для динамики используется canvas/webgl.

jimon 05.01.2013 16:05

Ответ: Qt QML : декларативный интерфейс, императивная логика
 
Цитата:

Сообщение от Randomize (Сообщение 248382)
Попробуй игры в экселе писать.
HTML нужен только для представления данных, а СSS для их стилизации.
Для динамики используется canvas/webgl.

what ? что же еще все сайты на canvas не делают ? ведь у некоторых анимаций больше чем данных
я ведь не выложил тест где 100500 прямоугольников рисуются шейдером за 1 дип, а то он бы выдал вам 100500 тыс фпс и было бы что canvas в 100500x раз медленее, я выложил честный тест с честными отдельными объектами, они эквивалентны dom, можно такой же тест с xaml провести

Randomize 05.01.2013 16:38

Ответ: Qt QML : декларативный интерфейс, императивная логика
 
Цитата:

Сообщение от jimon (Сообщение 248384)
what ? что же еще все сайты на canvas не делают ? ведь у некоторых анимаций больше чем данных

Потому что есть Internet Explorer, Windows XP, Intel GMA, Opera а так же ряд ущербных и беспросветных мобильных калькуляторов


Часовой пояс GMT +4, время: 20:25.

vBulletin® Version 3.6.5.
Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Перевод: zCarot