Игродельство

Чего-то зачесалось у меня в старые игрушки поиграть. Но быстрый поиск показал, что фиг вам там. Либо надо ставить эмуляторы и искать образы, либо идти и платить непонятно за что с каким качеством (у меня до сих пор в голове свежи воспоминания, как угробили pac-man, mario и boulder dash).

Ну если оно не получается, то почему бы не взять и не написать самому? В тех играх не ни графики, ни звука, ни работ на 100500 мульонов денег. А интерес-то тот же …

В общем, отряхнул я руки и за пол-дня написал первую игру: Пинг-Понг.

Получилась простая и тупая до безобразия игрушка. Но по-прежнему захватывает и дальше 20 я уйти не смог 🙂

Брать тут https://github.com/kiltum/games/tree/master/pingpong

Raspberry PI 3 и QT 5.8

Рабочая инструкция. На выполнение требуется примерно 30 часов


sudo apt-get update
sudo apt-get upgrade

sudo apt-get install libfontconfig1-dev libdbus-1-dev libfreetype6-dev libudev-dev libicu-dev libsqlite3-dev libxslt1-dev libssl-dev libasound2-dev libavcodec-dev libavformat-dev libswscale-dev libgstreamer0.10-dev libgstreamer-plugins-base0.10-dev gstreamer-tools gstreamer0.10-plugins-good gstreamer0.10-plugins-bad libraspberrypi-dev libpulse-dev libx11-dev libglib2.0-dev libcups2-dev freetds-dev libsqlite0-dev libpq-dev libiodbc2-dev libmysqlclient-dev firebird-dev libpng12-dev libjpeg9-dev libgst-dev libxext-dev libxcb1 libxcb1-dev libx11-xcb1 libx11-xcb-dev libxcb-keysyms1 libxcb-keysyms1-dev libxcb-image0 libxcb-image0-dev libxcb-shm0 libxcb-shm0-dev libxcb-icccm4 libxcb-icccm4-dev libxcb-sync-dev libxcb-render-util0 libxcb-render-util0-dev libxcb-xfixes0-dev libxrender-dev libxcb-shape0-dev libxcb-randr0-dev libxcb-glx0-dev libxi-dev libdrm-dev libssl-dev

sudo apt-get install libxcb-xinerama0-dev

Install libpng16-16! Default libpng is too old.

mkdir ~/opt
cd ~/opt

git clone git://code.qt.io/qt/qt5.git

cd qt5

./init-repository

./configure -v -opengl es2 -device linux-rasp-pi-g''+ -device-option CROSS_COMPILE=/usr/bin/ -opensource -confirm-license -optimized-qmake -reduce-exports -release -qt-pcre -qt-libpng -make libs -prefix /usr/local/qt5 &> output

make -j4 &> output_make

sudo make install &> output_make_install

pi@raspberrypi:~ $ cat >> .bashrc
export LD_LIBRARY_PATH=/usr/local/qt5/lib/
export PATH=/usr/local/qt5/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
pi@raspberrypi:~ $ source .bashrc

Программируем микроконтроллеры в QtCreator

Это копия моей статьи, опубликованной на хабре

qbs03
Почему-то в интернете мало документации про qbs, пора немного исправить эту ситуацию.

Как-то у меня сложилось так, что практически исчезли проекты, в которых надо сделать что-то одно. Обычно надо и написать прошивку для микроконтроллера и управляющую программу для смартфона или десктопа. Можно делать все по старинке: писать код для каждого устройства в родной для него среде разработки.

Но поверьте, это довольно быстро задалбывает. Под виндовс – MSVC, под ARM – CooCox или Keil (приношу свои соболезнования вынужденным работать под IAR), под MSP – CCS, под андроид – eclipse, под ios – Xcode, под пики – MPLAB. И ладно бы, со всем этим работать можно было бы, но ведь фиг: везде свои заморочки, тонкости и неписанные правила. Все это накладывается на общую тормознутость так популярного эклипса помноженную на криворукие дополнения от производителей.

Некоторое время назад я начал сводить все свои разработки под мобильные и десктопные приложения под одну платформу. После довольно долгого чеса по интернетикам мой выбор остановился на Qt. Есть все, что надо, при необходимости можно подцепить нативный код. В общем, задача закрылась.

Но вот с микроконтроллерами ситуация не желала складываться категорически. В основном из-за того, что везде свои хотелки и желалки. Ну ладно, про это я уже жаловался. Я бы еще долго мучался, пока внезапно не наткнулся на краткое описание qbs.

Желающие могут пошариться по инету сами, но если кратко, то это заменитель всяких make и cmake, использующая нормальный (тут должен быть смаил) язык программирования. И сам QtCreator собирается с ее помощью, значит она уже вылезла из штанишек …

Да, документации по ней как обычно кот наплакал, но исходники еще никто не отменял, поэтому довольно быстро ко мне пришло понимание, что это практически то, что я искал. Посудите сами: сидишь в одной среде разработки (самой по себе очень приятной и быстрой) и спокойно пишешь и редактируешь файлы под несколько платформ. И тебе, как кодописателю, пофиг на наличие всяких там заморочек с “родными” средами.

Хватит излияний, пора попробовать. Создадим очень простой проект, в котором у нас будет десктопная и микроконтроллерная составляющая.

Открываем QtCreator, выбираем создать Non-Qt Project (что бы пока не заморачиваться сильно) и далее выбираем то, где присутствует C и Qbs. Обратите внимание на приятные взгляду слова Platform independent

qbs01

В результате получаем один main.c и qbs. Можно уже нажать “build” и получить вывод Hello World.

Открываем qbs и ничего не понимаем. Поэтому все стираем, вооружаемся интернетом и начинаем писать. Яваскрипт и все такое.

import qbs

Так, тут вроде понятно. Импортируем всякое необходимое для работы самого qbs.

Project {
name: "simple"
}

Сохраняем и наблюдаем исчезновение main.c с левой панели. При попытке запустить проект QtCreator спросит: а чего пускать-то? В принципе пока все логично.

Из чего состоит проект в терминологии qbs? Из продуктов. И их может быть несколько, но пока я сделаю один.

Project {
name: "simple"
Product {
name: "desktop"
}
}

Теперь для нашего “десктопного” укажем исходник.

Project {
name: "simple"
Product {
name: "desktop"
files: "main.c"
}
}

При попытке скомпилировать не изменится ровным счетом ничего. Подсмотрим в некоторые обучалки и добавим зависимость от cpp и укажем, что вообще-то это приложение.

Project {
name: "simple"
Product {
name: "desktop"
files: "main.c"
Depends {name: "cpp"}
type: "application"
}
}

И вот теперь при попытке собрать приложение QtCreator пошуршит немного диском и в панельке Application Output появится искомое

qbs02

Ага, значит мы на верном пути. Осталось разобраться, что делают те самые две магические строки.

Depends {name: "cpp"}

Читаю документацию и понимаю, что данной командой я устанавливаю зависимость проекта от какого-то модуля с именем cpp. Понятней стало? Мне нет.

Простым поиском нахожу что-то подобное в /usr/share/qtcreator/qbs/share/qbs/modules/ (Если у вас другая операционка, то скорее всего аналогичное лежит где-то неподалеку от QtCreator). Если говорить коротко, то там куча яваскрипта, которая в зависимости от платформы подбирает компилятор под эту платформу. Полностью повторять подобное мне смысла нет, поэтому оставляю как есть.

type: "application". Из документации: The file tags matching the product’s target artifacts. Артефакт … An Artifact represents a single file produced by a Rule or Transformer. … ерр .. Rule? Creates transformers for input tags. Напоминает ситуацию про сепулькарий .. Transformer? Creates files, typically from other files.

Ползаю по доступному и понимаю, что примерно это некий набор правил, который говорит системе сборки, как надо собирать скомпилированное. Ну грубо говоря, на выходе надо получить приложение, библиотечку или вообще что-то иное. Опять же, хоть и стало немного понятней, но ненамного. Опять пока примем за данность.

Но вернемся к нашему проекту. Давайте добавим еще один продукт, только уже для микроконтроллера

Project {
name: "simple"
Product {
name: "desktop"
files: "main.c"
Depends {name: "cpp"}
type: "application"
}
Product {
name: "micro"
files: "blink.c"
}
}

При попытке что-то сделать, нам сразу выскочит сообщение, что вообще-то файла blink.c нет. Ну, ок, добавим в проект фаил blink.c. Как видно из названия, это тот же HelloWorld, только для микроконтроллеров. Я взял из примеров для микроконтроллера семейства msp430.

#include <msp430.h>

int main(void)
{
WDTCTL = WDTPW + WDTHOLD;
P1DIR |= 0x01;

while (1) {
P1OUT ^= 0x01;
__delay_cycles(1000000); // 1 second @ 1MHz
}

return 0;
}

Будучи скомпилированным и залитым, он начнет дергать ножкой P1.0 с интервалом в одну секунду. А так как на этой ножке у большинства демо- и девелоперских плат висит светодиодик, то он замигает.

Теперь QtCreator не ругается, но и в микроконтроллер ничего не заливается. Странно, да?

Добавлять Depends {name: “cpp”} смысла нет, потому что установленный в системе родной gcc не в курсе про существование такой платформы, да и в дальнейшем пригодится, например для пиковских контроллеров, где вообще все свое.

Теперь воспользуемся обрывками тех сакральных буковок, что встретились раньше.

Для начала, я предпочитаю в микроконтроллерных проектах расписывать каждый функционал в своем файле. Записывать каждый файл руками? Лень. Подсматриваем решение и переписываем блок

Product {
name: "micro"
Group {
name: "msp430 sources"
files: 'src/*.c'
fileTags: ['c']
}
}

Тут создаем группу файлов, которые обзываем “msp430 sources” и тупо включаем в нее все файлы, которые подходят под маску src/*.c. Для дальнейшей работы с ними тегируем их буквой С.

Что с ними делать? У qbs есть на этот случай две штуки – Rule и Transformer. По сути они близки, но немного разные. Счас попробую описать на пальцах разницу.

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

Transformer предназначен для срабатывания только на один фаил, с заранее определенным именем. Например, прошивальщик или какой-нибудь хитрый скрипт.

Ок, добавляем правило, которое должно будет сработать на все наши файлы, промаркированные как “с”.

Product {
name: "micro"
Group {
name: "msp430 sources"
files: 'src/*.c'
fileTags: ['c']
}
Rule {
inputs: ["c"]
prepare: {
var cmd = new JavaScriptCommand();
cmd.description = "file passing"
cmd.silent = false;
cmd.highlight = "compiler";
cmd.sourceCode = function() {
print("Nothing to do");
};
return cmd;
}
}
}

В принципе из синтаксиса уже все понятно. Есть inputs, есть prepare, в который засовывается яваскрипт, который выполняет необходимое. В данном случае он должен в окошке Compile Output показать file passing, и куда-то вывести Nothing to do. Ну по документации вроде так.

Запускаем перекомпиляцию всего и смотрим. Не знаю, как у вас, но я ничего не вижу. Почему? Потому что qbs больно умный, а документация к нему страдает лакунами.

Правило не срабатывает, потому что qbs считает, что оно не производит никаких действий в системе и от него ничего не зависит. В принципе это соответвует реальности, но провести проверку мешает.

Ок, за это отвечают те самые артефакты. Под ними подразумеваются результаты деятельности Rule или Transformer. Лучше всего это объяснить на примере компиляции. Когда мы компилируем .с файл, то на выходе мы получим объектный файл .о. Он нам нужен для дальнейшей линковки, но с другой стороны, мы его можем удалить, так как потом спокойно сможем сгенерировать заново.

Опять копируем пример из документации и чуть-чуть модернизируем.

Rule {
inputs: ["c"]
Artifact {
fileTags: ['obj']
filePath: '.obj/' + qbs.getHash(input.baseDir) + '/' + input.fileName + '.o'
}
prepare: {
var cmd = new JavaScriptCommand();
cmd.description = "Compiling "+ input.fileName
cmd.silent = false;
cmd.highlight = "compiler";
cmd.sourceCode = function() {
print("Nothing to do");
};
return cmd;
}
}

Теперь мы говорим, что после нашей деятельности останутся артефакты в каталоге .obj (ну и я добавил вывод того, над каким файлом мы сейчас работаем). Запускаем. Опять ничего в ответ. Почему? Ответ тот же – никому не нужны файлы с тегом ‘obj’.

Хорошо, для проверки сделаем так, что они нужны нам. И вообще, наше приложение – это один сплошной obj.

Product {
name: "micro"
type: "obj"
Group {
name: "msp430 sources"
files: 'src/*.c'
fileTags: ['c']
}
Rule {
inputs: ["c"]
Artifact {
fileTags: ['obj']
filePath: '.obj/' + qbs.getHash(input.baseDir) + '/' + input.fileName + '.o'
}
prepare: {
var cmd = new JavaScriptCommand();
cmd.description = "Compiling "+ input.fileName
cmd.silent = false;
cmd.highlight = "compiler";
cmd.sourceCode = function() {
print("Nothing to do");
};
return cmd;
}
}
}

Пробуем, и удача! В окошке появился заветный “Compiling blink.c”. Теперь давайте добавим, что бы оно реально компилировало и сразу по-быдлокодерски, то есть тупо забив все необходимое в одну кучу.

prepare: {
var args = [];
args.push("-mmcu=cc430f5137")
args.push("-g")
args.push("-Os")
args.push("-Wall")
args.push("-Wunused")
args.push('-c');
args.push(input.filePath);
args.push('-o');
args.push(output.filePath);
var compilerPath = "/usr/bin/msp430-elf-gcc"
var cmd = new Command(compilerPath, args);
cmd.description = 'compiling ' + input.fileName;
cmd.highlight = 'compiler';
return cmd;
}

Перекомпилируем все с нуля и смотрим в каталог .obj

$ ls -R1
.:
f27fede2220bcd32

./f27fede2220bcd32:
blink.c.o

Ура! Файлик появился. Теперь, для проверки я делаю еще один файлик, с хитрым названием hz.с. Если я прав, то после перекомпиляции рядом появится еще один объектный файл.

В выводе появилось

compiling blink.c
compiling hz.c

а в каталоге

./f27fede2220bcd32:
blink.c.o
hz.c.o

Все вроде ок. Теперь необходимо все это дело слинковать. А значит опять правило, только теперь для линковки.

Rule {
multiplex: true
inputs: ['obj']
Artifact {
fileTags: ['elf']
filePath: project.name + '.elf'
}
prepare: {
var args = [];
args.push("-mmcu=cc430f5137")

for (i in inputs["obj"])
args.push(inputs["obj"][i].filePath);

args.push('-o');
args.push(output.filePath);
var compilerPath = "/usr/bin/msp430-elf-gcc"
var cmd = new Command(compilerPath, args);
cmd.description = 'linking ' + project.name;
cmd.highlight = 'linker';
return cmd;
}
}

Где отличия? Во-первых, добавился флаг multiplex, который говорит о том, что это правило обрабатывает сразу все файлы данного типа скопом. А во-вторых, во входных параметрах исчез input. Появился inputs, который является массивом файлов данного типа. Ну и я вопользовался именем продукта, что бы брать имя для финальной прошивки.

Ставим тип приложения elf и пробуем собрать. Через некотрое время мы в каталоге для сборки обнаружим файл simple.elf

$ file simple.elf
simple.elf: ELF 32-bit LSB executable, TI msp430, version 1, statically linked, not stripped

То, что нам и необходимо. Его можно уже заливать в плату и наслаждаться мигающим светодиодиком.

Исходная цель достигнута: мы в одной среде разработки делаем все: и редактирование и компиляцию.

На всякий случай конечный qbs

import qbs
Project {
name: "simple"
Product {
name: "desktop"
files: "main.c"
Depends {name: "cpp"}
type: "application"
}
Product {
name: "micro"
type: "elf"
Group {
name: "msp430 sources"
files: 'src/*.c'
fileTags: ['c']
}
Rule {
inputs: ["c"]
Artifact {
fileTags: ['obj']
filePath: '.obj/' + qbs.getHash(input.baseDir) + '/' + input.fileName + '.o'
}
prepare: {
var args = [];
args.push("-mmcu=cc430f5137")
args.push("-g")
args.push("-Os")
args.push("-Wall")
args.push("-Wunused")
args.push('-c');
args.push(input.filePath);
args.push('-o');
args.push(output.filePath);
var compilerPath = "/usr/bin/msp430-elf-gcc"
var cmd = new Command(compilerPath, args);
cmd.description = 'compiling ' + input.fileName;
cmd.highlight = 'compiler';
return cmd;
}
}
Rule {
multiplex: true
inputs: ['obj']
Artifact {
fileTags: ['elf']
filePath: project.name + '.elf'
}
prepare: {
var args = [];
args.push("-mmcu=cc430f5137")

for (i in inputs["obj"])
args.push(inputs["obj"][i].filePath);

args.push('-o');
args.push(output.filePath);
var compilerPath = "/usr/bin/msp430-elf-gcc"
var cmd = new Command(compilerPath, args);
cmd.description = 'linking ' + project.name;
cmd.highlight = 'linker';
return cmd;
}
}
}
}

PS Вынос “захардкоденных” переменных в более удобное место оставлю на вашей совести, ибо это уже к обучалке по яваскрипту.

Показометры в студию!

Что первым делом должна делать программа для учета личных финансов? Конечно, показывать нам эти самые финансы. Или говоря другими словами, показывать состояние счета и транзакции по нему.

vsemo_stage3

Счета у нас внутри базы представляют собой классическое дерево, где у каждой веточки есть свой id и id ветки, из которой она растет. Или id и parent_id. Если parent_id=0, то считаем, что этот счет “корневой” и находится на самом верху.

Методов отображения таких деревьев – уйма. Но я на свою голову решил попробовать применить QTreeView. Обложился документацией, нашел парочку хороших статей на хабре … Казалось бы, бери и делай.

Но фигу. QTreeView в Qt – один из немногих компонентов, который сделан “наотъебись”. Практически нулевая документация, какие-то шаманские пляски для выполнения тривиальнейших вещей и так далее и тому подобное. А практически все примеры в интернете написаны в духе “это просто, берем готовую модель и вуаля!”. А как работает эта модель и почему надо это делать так – ноль.

В общем, потратив впустую пару дней с этими морделями, я плюнул на эту хренотень, взял обычный QTreeWidget и банальным рекурсивным поиском заполнил виджет. На всякие заморочки со скоростью я решил начихать, ибо я не верю, что у обычного пользователя будет больше 100-200 счетов, а на этом уровне сходится все. На тестах виджет вполне нормально крутил 10000 счетов без каких-либо пожираний памяти или тормозов процессора.

Что касается размазывания логики по коду, то вроде этого удалось избежать. Все необходимое уместилось в одном классе. В общем, с моделью тут гораздо больше мороки и код получается гораздо более нечитаемым и не поддерживаемым.

Из того, что не было в предыдущих стадиях так это только то, что добавилась синхронизация таблички rate, в которой хранятся курсы валют. Тут же всплыла проблема: при синхронизации на немощных компьютерах программа сваливается в Application Not Responding. Надо будет куда-нибудь в синхронизацию воткнуть вызов диспетчера очередей. Сменить алгоритм не предлагать, ибо потом некоторые таблички будут расти и расти …

А вот с окошком отображения транзакций получилось все как по книжкам. Там обычная плоская таблица, поэтому QSqlTableModel пришлась впору. И так как документации, примеров и прочего на порядок больше, то и проблем никаких не было. Почти, но об этом позже.

Через некоторое время я переехал на QSqlRelationalTableModel по одной простой причине: у каждой транзакции есть currency_id, которое отвечает за валюту, в которой сделана транзакция. И что бы не мучаться, проще заставить модельку саму выбирать с помощью join нужные значения из таблицы currency.

Во всем этом великолепии меня поджидало только две засады.

Первая заключалась в именах таблиц. Все-таки transaction является ключевым словом и просто так qtшный sql не хотел использовать эту таблицу. Попытка заэскепить имя таблицы как [transaction] не увенчалась успехом. Но на мою радость оказалось, что “transaction” вполне себе хороший идентификатор для qt.

Вторая заключалась в том, что поле для join задается его порядковым номером. То есть мне надо считать, под каким порядковым номером выводится нужное мне поле и задавать его. А функции типа findColumn(name) я не нашел. Можно конечно поизвращаться самому (db.tables никто не отменял), но пока оставлю в качестве todo на будущее.

Наконец, сделал последний штрих: заставил выводить только те транзакции, которые относятся к выделенному счету. Мелочь через setfilter.

Как обычно, исходники тут

В принципе, максимально минимальный клиент готов. Он умеет логиниться и показывать необходимое. Да, коряво, да, даже без капельки дизайна, но умеет.

Что следующее? А следующим будет причесывание внешнего вида и начало эпопеи по разработке интерфейса. Например, надо сделать так, что бы на планшетах, десктопах и телефонах в ландшафтной ориентации отображалось как “счета-транзакции”. А вот на телефонах в портретной ориентации надо выводить “счета”, при тапе на счете выводить отдельное окно с транзакциями. И реализовать “назад”.

И тулбарчик на андроидах сверху, а на айфонах снизу! И еще ..

Короче, в следующей серии будет гламуризация полученного.

Продолжаю всёмоёшить …

Итак, продолжаем делать клиента.

Как ни обидно будет читать некоторым, но мне пришлось отказаться от QML. Слишком сырая штука для подобных проектов. Во-первых, делать связку между C++ и QMLной частью – это еще тот секс, а без подобных связок никак. Во-вторых, отсутствие нормальных layout менеджеров ставит полностью крест на кросс-платформенной разработке. Это я понял после того, как для странички логина у меня получился огромный и безформенный кусок спагетти, отвечающий за положение элементов в разных средах и формах. И наконец, вся эта машинерия у меня внезапно сглючила. Например, у меня поля ввода перестали убирать при вводе placeholder и стали оставлять после себя артефакты. И это даже при использовании специальной “чистой” сборочной виртуалки. И под виндовсом и под OS X.

В общем, плюнул я на борьбу с мельницами и вернулся к обкатанным QWidgets.

Сейчас в Stage2 реализовано следующее:

stage2

Желтеньким выделено реализованное. Белое я оставил, ибо после размышления я решил, что нефиг этому функционалу делать на окне логина. За кадром остался функционал работы с сетью, базами данных и синхронизацией.

И что больше всего радует, на всех платформах оно работает! И работает довольно шустренько. К примеру, скорость процесса синхронизации не сильно отличается от десктопной.

Что вскрылось в процессе написания и что может быть вам полезно.

Во-первых, забудьте про всякие QDialog и все связанное с ним (QMessageBox и теде). Только QMainWindow для всего. Причина простая – на андроиде и айпаде окошко зажимается в левом верхнем углу и никак его оттуда не вытащить без трахтибидоханья. QMainWindow ведет себя гораздо более приличней и плюс позволяет с собой поделать всякие кунштюки типа сохранения координат-размеров. Да, придется немного помучаться, зато потом оно летает без геморроя.

Во-вторых, вам придется переопределить все методы show и вручную написать для каждого элемента что-то типа ui->element->adjustSize(); без них вы на мобильных платформах с большим dpi получите малюсенькие элементы управления, в которых будут проглядывать надписи.

И наконец, забудьте про пикселы и прочие ldpi. Все в платформонезависимых единицах, типа пунктов для шрифтов. Всякие кнопки и прочие элементы управления – загоняйте в layout. Отдайте это на откуп Qt – у него очень неплохо получается.

Как обычно, полученное доступно тут. Дизайну ноль, зато весь функционал работает.

Теперь пора переходить к главному окну приложения. Но это в следующих постах.

Логика ты моя всёмоёшная …

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

Я позапускал приложение на всех доступных мне устройствах и вынужден сказать, что Qt 5.2 вполне справляется со своими обязанностями. Приложение запускается, крутится и вообще ведет себя как взрослое.

Но сразу же выяснилось, что просто так использовать правила “размер в 1/10 экрана” нельзя. Иначе на устройствах с большим экраном (iPad или 27″ монитор) элементы управления начинают напоминать игрушечные: такие же больше и аляповатые. В общем, необходимо размеры некоторых элементов привязать к реальному размеру.

Qt тут помогает. В нем есть Screen.pixelDensity (берем из QtQuick.Window 2.1), который говорит о числе точек экрана на миллиметр. И если нам нужен размер чего-то в сантиметр, то просто ставим Screen.pixelDensity*10 и проблема исчезает.

В том же Screen есть width и height, только они вовсе не про размер экрана. Они описывают размеры прямоугольника, который доступен приложению. За этим прямоугольником располагаются всякие строки статуса, таскбары и прочая системная мишура. Поэтому использовать те же переменные из Window вообще-то не правильно.

Но я отвлекся. Итак, у нас есть формочка с двумя полями ввода, чекбоксом и кнопкой “войти”. Как все это дело обрабатывать? Ну что бы было удобно пользователю и программа не мозолила ему мозги со всякой дрянью?

Берем первый попавшийся под руку Visio и подумав, рисуем вот такенную блок схему. Если чего, то картинка кликабельна.

Запуск VseMoe

Вот такая вот небольшая схемка, которая показывает логику приложения от запуска до появления основного окна.

Что я преследовал, рисуя кучу этих линий?

– Если пользователь поставил галочку “запомнить меня”, приложение должно запускаться без каких-либо вопросов, запросов и прочего. Всякие блокировки оставим операционной системе.
– Однако пользователь может быть немного параноидальным и не хочет, что бы кто-то, кто имеет право пользоваться телефоном (ребенок к примеру), что-то там понатыкал. Защита обычным пин-кодом.
– Если пользователь специально на каком-то устройстве снял галочку “запомни меня”, засинхронизировался и перелогинился, то все остальные устройства при первом же подключении к сети должны заново запросить пароль и засинхронизироваться заново. Это защита от утери устройства/утечки токена.
– Ну и устройство не должно при обычной работе лезть в интернет за синхронизацией, если пользователь не разрешил. Говоря другими словами, настройки “синхронизироваться только через wifi” и “синхронизироваться только вручную”.

Как-то так. Про реализацию в следующем посте.

Клиент ВсеМое.ру – логин

Скорей, надо скорей писать код! Как театр начинается с вешалки, так и любой клиент ВсёМоё начинается с окошка логина. Надо его нарисовать!

Для начала выдираем скриншотом, прямо из эмулятора, работащий логин.

Screen Shot 2014-03-30 at 14.57.55

В оригинале всё, кроме полей ввода и чекбокса, нарисовано прямо на бэкграунде. С одной стороны, это хорошо и уменьшает объем кода. А с другой стороны, ограничивает нарисованным. А ведь охота нарисовать сразу для всех платформ. Ну или практически для всех …

Разбиваем окошко на следующие элементы: бэкграунд, “банка”, логотип, и два поля ввода. Для десктопа еще должна добавиться кнопка “Войти”, ибо на мобильных её функцию выполняет кнопка на виртуальной клавиатуре. Кнопку “регистрация” … а нехай будет

В общем, как-то так получается в реальности.

Screen Shot 2014-03-30 at 15.52.12

Берем Qt Creator и создаем в нем проект, который использует QtQuick.Controls. Мне он поугрожал, что использовать полученное можно будет только с версией iOS больше 5.1 … но найдите мне такую версию живьем?

Создаем в нем фаил ресурсов и загоняем туда картинки, тупо выдранные из оригинала (красоту будем наводить потом).

Для начала нам надо добиться, что бы один и тот же код выполнялся и под десктопом и под айфончиком.

Для этого надо:

а) загнать qml в ресурсы
б) нафиг сменить процедуру загрузки, иначе будут ошибки про not ready и прочее. Короче QTBUG-29873

main.cpp сейчас у меня выглядит так

Screen Shot 2014-03-30 at 17.24.36

Красивая картинка? Полностью полученное можно посмотреть где положено (то есть в git и нефиг ныть!), а у меня получилось следующее:

Screen Shot 2014-03-30 at 17.21.06

Поверьте, на андроиде примерно тоже самое. Теперь необходимо осуществить … гламуризацию всего этого дела. Например, где кнопочка “регистрация” (и нужна ли она)? И все криво при повороте набок. И пароль набирается видимыми буквами. И нету чекбокса. И вообще, все криво.

В общем, надо сделать так, что бы элементы меняли свое положение относительно друг друга при изменении соотношения сторон. Это происходит либо когда окошко у десктопа таскают мышкой, либо мобильник переворачивают.

Для начала убираем всякие Column { и тупо прописываем все подряд. Затем добавляем State почти по инструкции http://doc.qt.digia.com/qtquick-components-symbian-1.1/qt-components-scalability-guidelines-orientation-specific-layouts.html

Так как там немного древнее, я заиспользовал одну StateGroup, без отдельной с when:true


StateGroup {
states: [
State {
when: (main.width / main.height) < 1.00

PropertyChanges {
target: loginEmail; text: "portrait"
}

AnchorChanges { target: logoImage; anchors.top: parent.top }
AnchorChanges { target: loginEmail; anchors.top: logoImage.bottom }
// AnchorChanges { target: userPassword; anchors.top: loginEmail.bottom }
},
State {
when: (main.width / main.height) > 1.00

PropertyChanges {
target: loginEmail; text: "landscape"
}
AnchorChanges { target: loginEmail; anchors.top: logoImage.bottom }
// AnchorChanges { target: userPassword; anchors.left: loginEmail.right }
}
]
}

Для проверки я тупо убрал лишнее и в поле для логина начал выводить, какой же State используется. И потом, с помощью PropertyChanges и AnchorChanges я меняю свойства и привязки элементов. В итоге получается “адаптивный” дизайн.

Вот теперь можно и добавить кнопки про регистрацию и вперде!

Правда, вперде прямо сразу не получится. По двум причинам.

Во-первых, для десктопа и мобильного необходимо разные расположения элементов. На мобильном половину (ну или часть) экрана занимает клавиатура, а на десктопе нет. Виндовсные планшеты пока не рассматриваем, их слишком мало, что бы быть принятыми во внимание. И если использовать одну и ту же схему размещения элементов, то либо на мобильном будет криво, либо на десктопе. Некрасиво.

Во-вторых, это долбанное разнообразие разрешений и форматов экрана. У кого-то 4:3, а у кого-то 16:9. Кому-то хватает 640х480 или даже 320 на 240, а кому-то и 1920 на 1080 не хватает.

Что делать?

После некоторого раздумья я решил, что фиг с этими пропорциями. Пусть будет просто две ориентации экрана: горизонтальная и вертикальная.

Для определения платформы (мобильная или десктопная) проще всего использовать предопределенные константы прямо из Qt. напрямую из QML не получится, слишком кроссплатформенный он получился …

Итак, что имеем?

1. ориентацию экрана легко узнать из самого QML. Просто по соотношению высоты и ширины экрана.
2. Ориентацию элементов управления (для мобильного прижимаем кверху, а для десктопа – центрируем) передаем в QML снаружи. Делать свой QML для каждой платформы – моветон.
3. Для удобства позиционирования волевым решением принимаю, что экран в горизонтальной ориентации в реальности имеет размеры 40х30. Кто-то это называет device independed pixels, кто units, кто еще как. И все координаты будут преобразовываться в реальные пикселы отдельной функцией. Сначала засуну ее в QML, а там посмотрим.

Ну, пункт 1 сделан еще на подготовительном этапе.

Теперь надо передать платформу в QML. Обычно народ сразу хватается за классы, но нам-то по идее хватит что-нибудь типа 0 – по центру, 1 – прижимать все вверх.

Для этого тупо устанавливаем переменную для QML

engine.rootContext()->setContextProperty(“platform”,31);

Теперь везде можно использовать platform. Ну и для порядку неплохо бы обернуть все это в обертку из #define

int platform=0;

#ifdef Q_OS_ANDROID
platform=1;
#endif

#ifdef Q_OS_IOS
platform=1;
#endif

#ifdef Q_OS_WINPHONE
platform=1;
#endif

Потом как-нибудь можно будет сделать более тонкое разделение по платформам.

Что касается пикселов, то это решается еще проще.

Где-нибудь в начале QML, сразу после ApplicationWindow вставляю следующее:

property int guxSize: width/40
property int guySize: height/30

function gux(c)
{
return guxSize*c;
}
function guy(c)
{
return guySize*c;
}

Потом везде, где надо привязаться к координатам, использую gux для х/ширины и guy для y/высоты. Опять же, потом мне ничего не мешает переопределить подсчет этих значений для более тонкого соответствия какому-нибудь хитровыделанному дисплею.

Ну а теперь пора переходить к определению элементов и их размеров. При этом крайне рекомендую внутри элемента описаться на его ширину/высоту, а не на guy/gux. Просто потом будет гораздо легче. И никаких pointSize! Только pixelSize.

Как это выглядит в коде, вы можете увидеть в Stage1. В реальности это выглядит так:

Для “десктопа”

landscape

Для “портретного” режима

portrait

Можете поизменять размеры окошка и увидеть, как меняется местоположение элементов. Конечно, потом это надо отдать дизайнеру, что бы он своим дизайнерским чутьем подвигал всё туда-сюда, поменял цвета и поделал прочие штуки. На данном этапе важнее функционал.

И да, можно загрузить полученное в телефончик/планшетик и посмотреть, как оно будет выглядеть там. Ну и похвастаться кому-нибудь из знакомых. Всё-таки у вас в руках по настоящему кросс-платформенное приложение.

Клиент ВсёМое.ру – начало

Давно известна старая фраза “если хочешь, что бы было сделано как надо – сделай это сам”. Правда, мало кто продолжает это фразой “ну или по крайней мере начни”.

Из-за чего все это началось? Давным давно (лет 5 наверное назад, если не больше), я озаботился учетом личных финансов. Ну как-то деньги вроде и были, но почему-то сразу и быстро заканчивались. Я перепробовал кучу всяких разных программ для учета финансов и поначалу остановился на GnuCash. Вроде всем хороша и позволяет делать все, что мне необходимо.

Но через некоторое время она стала меня раздражать. Сделав покупку в магазине, мне необходимо было взять чек с собой, потом не потерять его и затем ввести его в программу. Сначала я делал это каждый вечер, потом стал делать по выходным … а потом забил. Все мое существо протестовало против использования меня в качестве тупого приложения к вобщем-то простой программе. Я стал искать варианты для облегчения своей участи … И не нашел. Нет, в сети есть куча сервисов по учету финансов, но все эти сервисы были или рассчитаны на блондинок, или настолько явно использовали мои данные да еще и за мои деньги, что мне становилось нехорошо.

На тот момент я видел единственный выход – создание своего сервиса, с игрищами и блудницами. Подходящий домен у меня был давно заначен и довольно быстро появился vsemoe.ru. Сервис по учету личных финансов. Поначалу появился клиент под iOS, потом постепенно стал проявляться веб-клиент … и тут ситуация застопорилась. Программисты, которым мы давали писать, постепенно его ушатали до безобразия. С каждой новой версией он жирел и обрастал багами вместо улучшения функционала. В общем, ситуация знакомая практически всем. Ситуация осложняется тем, что Objective-C мне категорически не нравится и я попросту часто не врубаюсь в его логику.

Плюс версия для десктопа тоже постоянно мутировала (у меня в репозитории лежит уже 6й или 7й вариант), но до варианта “мне нравится” так и не дотянула.

Что делать? С одной стороны, по хорошему надо сделать рефакторинг кода и вычистить большую часть наслоений. А с другой стороны, мне очень охота сделать логику одинаковой для всех платформ. Иначе версию для андроида я так никогда и не увижу.

В результате долгих раздумий, проб, макетов и прочих штук, у меня появилось стойкое ощущение, что это можно сделать. В одиночку или с небольшой помощью. Ну и дальше я постараюсь выложить свои шаги по реализации клиента для сервиса vsemoe.ru со своими комментариями.

Скажу сразу: я далеко не профессиональный программист. Говоря нынешними словами, я скорее DevOps с уклоном в сторону администрирования. И я прекрасно сознаю, что кое-какие вещи можно (или даже полагается) делать по-другому. Если знаете как, то помогайте. В общем, начнем.

Для начала я создал аккаунт на гитхабе (https://github.com/kiltum/VseMoe/), в котором и буду выкладывать свои наработки. Опять же, я нагло буду использовать куски уже опубликованного и работающего приложения под iOS (ВсёМоё). Для разработки будут использоваться последние версии Qt/NDK и прочих вещей. Основная работа будет вестись из-под мака (мне так проще).

Итак, что должно получиться в конце?

Screen Shot 2014-03-30 at 15.34.09

Приложение-клиент, работающее под Windows, Linux, OS X, iOS, Android и MobileWindows. Короче, под всеми распространенными платформами. Веб-клиент пишется отдельно и я в него не полезу по простой причине: я не понимаю людей, которые все тащат в браузер, словно дети.

В общем, вперед!