https://habrahabr.ru/post/326006/Вступление
Недавно мы открылись миру (совершили coming out, так сказать) и опубликовали
статью про наш скромный
фреймворк (исходники на
GitHub). После общения с заинтересовавшимися участниками (большое им спасибо!) мы пришли к выводу, что для раскрытия темы необходимо написать подобие туториала на каком-нибудь реальном примере. На сайте проекта есть
раздел с уроками, но эти уроки скорее описывают специфические ситуации, нежели картину в целом. Вот почему мы решили написать небольшой гайд. Для реалистичности, по шагам опишем создание простого, но реального, проекта, который хорошо показывает портируемость решений из веба в SmartTV. И да, результат этого гайда уже доступен в
LG Smart World для телевизоров на базе WebOS (вы можете найти это приложение по названию «Earth Online»). В этой статье мы описываем создание ровно такого же приложения для десктопных и мобильных браузеров.
Идея
Идея довольно проста: показывать виды Земли с борта МКС (за эту возможность надо благодарить NASA, а именно, проект
High Definition Earth-Viewing System). Помимо видео, приложение будет показывать текущее положение МКС над Землей. Для этих целей мы воспользуемся сервисом
Where the ISS at?. Приступим!
Начало
Создаем папку для нашего проекта:
$ mkdir earth-online
$ cd earth-online
Чтобы создать новый проект, нужно скопировать
qmlcore в папку с проектом. Можно просто скачать и скопировать содержимое или склонировать с помощью GIT:
$ git clone git@github.com:pureqml/qmlcore.git
Чтобы поменьше писать руками, сгенерируем каркас приложения:
$ qmlcore/build --boilerplate
После выполнения команды в папке проекта появится два новых файла:
- src/app.qml — PureQML код приложения
- .manifest — минимальный манифест, описывающий приложение
Папка
src используется для хранения qml исходников проекта.
В
app.qml, оставим только такой код:
Item {
anchors.fill: context; // растягиваем Item по размерам всего окна
}
Чтобы собрать приложение, нужно выполнить команду:
$ qmlcore/build
и если все прошло успешно, то в папке проекта появится новая папка
build.web с результирующими файлами.
$ ls build.web/
flashlsChromeless.swf index.html modernizr-custom.js qml.app.js
На секунду отвлечемся и посмотрим, что это за файлы:
- index.html — полученная html-страница
- qml.app.js — полученный js файл, который загружается в index.html
- modernizr-custom.js — используемый кастомный modernizr файл
- flashlsChromeless.swf — файл, который используется для проигрывания видео с помощью флеша (буэ), в нашем случае он не нужен, но он автоматически добавляется в проекты web платформы.
Controls
Помимо qmlcore, необходимой и наиболее стабильной части проекта, есть еще отдельный модуль
controls, в котором найдется много полезных компонент уже готовых к использованию. Для подключения этого модуля его также достаточно скопировать в
src папки проекта или склонировать:
$ cd src
$ git clone git@github.com:pureqml/controls.git
Для нашего приложения как раз понадобится один компонент из controls, который позволит играть live stream видео с youtube, он так и называется — YouTube. Добавим его в
app.qml:
Item {
anchors.fill: context; // растягиваем Item по размерам всего окна
YouTube {
anchors.fill: parent;
source: "https://www.youtube.com/embed/ddFvjfvPnqk?autoplay=1&controls=0&showinfo=0";
}
}
В результате, если открыть Index.html в браузере, то можно увидеть видео с МКС:
Карта
С видео разобрались. Теперь перейдем к местоположению станции. Так как для задачи достаточно отобразить позицию станции над Землей, то можно использовать просто картинку с развернутой поверхностью планеты и двигать точку по ней согласно текущим координатам (хотя в controls есть компонента YandexMap для работы с картами).
Мы подготовили картинку с картой Земли. Чтобы наша картинка, или любые другие ресурсы, попали в сборку, нужно создать специальную папку в корне проекта —
dist:
$ mkdir dist
Все содержимое этой папки будет копироваться после сборки в
build.web. Добавим в
dist картинку с картой:
$ mkdir dist/res
$ cp <path-to-map-image> dist/res
Если попробуете собрать проект сейчас, то увидите как в
build.web появилась папка
res с добавленной картинкой:
$ ls build.web/res/
map.png
Логика работы с карта у нас будет в отдельной компоненте, создадим новый файл для нее
IssMap.qml в
src и напишем следующий код:
Item {
anchors.fill: context; // компонента растягивается на весь экран
// картинка с картой
Image {
anchors.fill: parent; // растягиваем по размерам родителя
source: "res/map.png"; // путь к картинке
fillMode: Image.Stretch; // тип заливки, Stretch растягивает по размерам Image
// точка для обозначения позиции станции
Rectangle {
id: station; // id, по которому можно обращаться к компоненте
width: 30;
height: width; // высота равна ширине
radius: width / 2; // радиус, чтобы скруглить прямоугольник до круга
visible: false; // по умолчанию точку не видно
color: "red";
}
}
// метод для установки точки соответственно долготе long и широте lat
setPos(long, lat): {
// делаем точку видимой
station.visible = true
// все константы используются для преобразования из координат
// широты, долготы в координаты карты
station.x = (long + 180) * this.width / 360 - (this.width / 28.4)
station.y = (90 - lat) * this.height / 180 + (this.height / 19.45)
}
}
Теперь нам нужно получить координаты станции и установить точку в нужную позицию. Для этого воспользуемся
API, о котором говорилось выше.
Для выполнения HTTP запросов в controls есть специальный объект Request, сделаем на его базе контрол для асинхронного запроса координат в файле
IssRequest.qml в папке
src:
Request {
// метод запроса координат
// callback - функция, которая будет выполнена в случае успешного запроса
call(callback): {
this.ajax({
url: "https://api.wheretheiss.at/v1/satellites/25544",
done: callback,
withCredentials: true
})
}
}
Теперь нужно сложить все в одно место и добавить еще текст с координатам станции. Назовем этот контрол
OSD и создадим для него файл
Osd.qml с кодом:
// WebItem - это обычный Item
// но с возможностью отслеживать hover и click события у мыши
WebItem {
property bool active: false; // объявим bool свойство - флаг отображения OSD
anchors.fill: parent;
opacity: active ? 1.0 : 0.0; // прозрачность в зависимости от флага active
// инстанциируем протокол
IssRequest { id: request; }
// инстанцируем нашу карту из IssMap.qml
IssMap { id: map; }
// текст о видимости Земли (на темной стороне или освещенной)
// прижимаем к правому нижнему краю
Text {
id: visibilityText;
anchors.right: parent.right;
anchors.bottom: parent.bottom;
anchors.margins: 10;
font.pixelSize: 24;
color: "#fff";
text: "Earth visibility: -";
}
// текст с координатами: долготой и широтой
// текст прижимаем к левому нижнему краю
Text {
id: positionText;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
anchors.margins: 10;
font.pixelSize: 24;
color: "#fff";
text: "Lon: -<br>Lat: -"; // в текст можно вставлять html теги
}
// таймер для повторения запросов
Timer {
running: parent.active; // таймер работает пока активен OSD
triggeredOnStart: true; // таймер срабатывает при старте
interval: 10000; // интервал повторения 10 секунд
repeat: true; // таймер повторяется
// обработчик срабатывания, зовем метод updatePositionRequest
onTriggered: { this.parent.updatePositionRequest() }
}
// делаем запрос координат, в случае успеха парсим результат
// передаем его в doUpdatePosition
updatePositionRequest: {
var self = this
request.call(function(res) {
var data = JSON.parse(res.target.response)
if (data)
self.doUpdatePosition(data)
else
log("Request error")
})
}
// заполняем полученные данные
doUpdatePosition(data): {
var long = parseFloat(data.longitude) // долгота
var lat = parseFloat(data.latitude) // широта
// заполняем текст с координатами станции
positionText.text = "Lon: " + Number((long).toFixed(1)) + "<br>Lat: " + Number((lat).toFixed(1))
// заполняем текст видимости data.visibility возвращает строку 'eclipsed' или 'daylight'
visibilityText.text = "Earth visibility: " + data.visibility
map.setPos(long, lat) // передаем координаты станции карте
}
// объявляем анимацию на изменение прозрачности
// время анимации 300 мс
Behavior on opacity { Animation { duration: 300; } }
}
Теперь нужно разместить OSD в нашем приложении поверх видео и отображать его, скажем, по клику мыши на экране и также (по клику) закрывать. Теперь
app.qml примет следующий вид:
Item {
anchors.fill: context;
Youtube {
anchors.fill: parent;
source: "https://www.youtube.com/embed/ddFvjfvPnqk?autoplay=1&controls=0&showinfo=0";
}
Osd {
// обработчик клика мыши
// инвертируем флаг отображения OSD по клику
onClicked: { this.active = !this.active }
}
}
Собираем проект, открываем в браузере
build.web/index.html, кликаем по экрану и OSD появляется с анимацией по прозрачности!
Последние штрихи
Попробуем внести еще небольшое улучшение: было бы здорово, если бы текст с координатами был выровнен:
для решения проблемы можно использовать какой-нибудь
monospace шрифт.
Чтобы добавить новый шрифт, нужно подредактировать выходной
index.html файл. Помните, как мы использовали папку
dist, чтобы добавлять файлы в
build.web после сборки? Важным моментом является именно тот факт, что копирование происходит после сборки, и таким образом, мы можем переопределить
index.html. За основу возьмем текущий
index.html:
$ cp build.web/index.html dist
и добавим линк на новый
шрифт. После небольших правок
dist/index.html будет выглядеть вот так:
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link href="https://fonts.googleapis.com/css?family=Space+Mono" rel="stylesheet" type='text/css'>
<script src="modernizr-custom.js"></script>
</head>
<body style="font-family: ’Space Mono’">
<script src="qml.app.js"></script>
</body>
</html>
Текст с координатами примет вот такой вид:
Заключение
Конечно же, это приложение не раскрывает всех возможностей фреймворка, но дает наглядное представление о том, как быстро и непринужденно написать single page приложение на QML, код которого можно портировать на SmartTV платформы.
Ссылки